chatpbc1 commited on
Commit
e3a0732
·
verified ·
1 Parent(s): 9829893

The security engine: whitelist, locked sections, caps, rate limit, hash-chained audit, rollback

Browse files
Files changed (1) hide show
  1. cortx_secure.py +141 -0
cortx_secure.py ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CORTX-Secure — the security architecture for self-editing AI.
2
+
3
+ Threat model: the model itself, or a user prompting the model, attempts to
4
+ escalate self-editing beyond policy text into weights, code, identity, or
5
+ credentials. This module makes that physically impossible in code.
6
+
7
+ Defense layers:
8
+ 1. WHITELIST: only sections in EDITABLE_SECTIONS can ever be edited.
9
+ 2. LOCKED: security_rules, identity, weights, code, admin_token are rejected
10
+ at the validator (enforced in code, not prompt text).
11
+ 3. SIZE CAPS: every edit bounded (chars per section, edits per turn).
12
+ 4. RATE LIMIT: max edits per rolling hour; violations logged and blocked.
13
+ 5. HASH CHAIN: every edit (and every rejected attempt) is appended to a
14
+ tamper-evident audit log: entry.hash = sha256(prev.hash + entry.json).
15
+ Admin can verify the chain; tampering is detectable.
16
+ 6. APPROVAL MODE: admin can require human approval before edits go live.
17
+ 7. ROLLBACK: version-pinned config; admin can restore any prior version.
18
+ 8. ISOLATION: the edit channel reaches ONLY policy text files on disk. It
19
+ has no filesystem path capability, no exec, no network beyond the
20
+ config repo. Weights/code/token are never in the same trust zone.
21
+ """
22
+ import hashlib, json, time, threading, re
23
+
24
+ EDITABLE_SECTIONS = ("constitution", "tool_rules", "metacognitive_rules",
25
+ "refusal_rules", "playbook")
26
+ LOCKED_SECTIONS = ("security_rules", "identity", "weights", "code",
27
+ "admin_token", "system_prompt")
28
+ MAX_SECTION_CHARS = 8000
29
+ MAX_EDITS_PER_TURN = 8
30
+ MAX_EDITS_PER_HOUR = 40
31
+
32
+ EDIT_BLOCK_RE = re.compile(r"\[cortex_edit\]\s*(\{.*?\})\s*\[/cortex_edit\]", re.DOTALL)
33
+
34
+ class AuditLog:
35
+ """Tamper-evident hash-chained audit log."""
36
+ def __init__(self):
37
+ self.entries = []
38
+ self.lock = threading.Lock()
39
+ self._prev_hash = "GENESIS"
40
+
41
+ def append(self, kind, section, actor, detail, accepted):
42
+ with self.lock:
43
+ entry = {"ts": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
44
+ "seq": len(self.entries), "kind": kind, "section": section,
45
+ "actor": actor, "detail": str(detail)[:200],
46
+ "accepted": accepted, "prev_hash": self._prev_hash}
47
+ payload = json.dumps(entry, sort_keys=True, ensure_ascii=False)
48
+ entry["hash"] = hashlib.sha256(
49
+ (self._prev_hash + payload).encode("utf-8")).hexdigest()
50
+ self._prev_hash = entry["hash"]
51
+ self.entries.append(entry)
52
+ return entry
53
+
54
+ def verify(self):
55
+ prev = "GENESIS"
56
+ for e in self.entries:
57
+ payload = json.dumps({k: v for k, v in e.items() if k != "hash"},
58
+ sort_keys=True, ensure_ascii=False)
59
+ expect = hashlib.sha256((prev + payload).encode("utf-8")).hexdigest()
60
+ if expect != e["hash"] or e["prev_hash"] != prev:
61
+ return False, e["seq"]
62
+ prev = e["hash"]
63
+ return True, len(self.entries)
64
+
65
+ class SecureEditor:
66
+ """Validates and applies self-edits under the security model above."""
67
+ def __init__(self, config, audit=None, approval_required=False):
68
+ self.config = config
69
+ self.audit = audit or AuditLog()
70
+ self.approval_required = approval_required
71
+ self.pending = []
72
+ self.edit_times = []
73
+ self.lock = threading.Lock()
74
+
75
+ def _rate_ok(self):
76
+ now = time.time()
77
+ self.edit_times = [t for t in self.edit_times if now - t < 3600]
78
+ return len(self.edit_times) < MAX_EDITS_PER_HOUR
79
+
80
+ def validate(self, section, content):
81
+ """Returns (ok, reason). Pure function — no side effects."""
82
+ section = str(section or "").strip().lower()
83
+ content = str(content or "").strip()
84
+ if section in LOCKED_SECTIONS:
85
+ return False, "locked section: %s is outside the editable surface" % section
86
+ if section not in EDITABLE_SECTIONS:
87
+ return False, "unknown section: %s" % section
88
+ if not content or len(content) < 20:
89
+ return False, "content too short"
90
+ if len(content) > MAX_SECTION_CHARS:
91
+ return False, "content exceeds %d chars" % MAX_SECTION_CHARS
92
+ return True, "ok"
93
+
94
+ def apply_edits(self, model_text):
95
+ """Parse [cortex_edit] blocks, validate, apply or reject.
96
+ Returns (clean_text, applied, rejected)."""
97
+ applied, rejected = [], []
98
+ if not self.config.get("self_edit_enabled", True):
99
+ return EDIT_BLOCK_RE.sub("", model_text), applied, rejected
100
+
101
+ def _grab(m):
102
+ try:
103
+ obj = json.loads(re.sub(r",\s*([}\]])", r"\1", m.group(1)))
104
+ except Exception:
105
+ self.audit.append("edit", "?", "model", "unparseable json", False)
106
+ rejected.append({"section": "?", "reason": "unparseable"})
107
+ return ""
108
+ sec = str(obj.get("section", "")).lower()
109
+ content = str(obj.get("content", ""))
110
+ ok, reason = self.validate(sec, content)
111
+ self.audit.append("edit", sec, "model", reason, ok)
112
+ if not ok:
113
+ rejected.append({"section": sec, "reason": reason})
114
+ return ""
115
+ with self.lock:
116
+ if len(applied) >= MAX_EDITS_PER_TURN:
117
+ rejected.append({"section": sec, "reason": "turn cap"})
118
+ return ""
119
+ if not self._rate_ok():
120
+ rejected.append({"section": sec, "reason": "rate limit"})
121
+ return ""
122
+ self.config[sec] = content
123
+ self.config["version"] = int(self.config.get("version", 1)) + 1
124
+ self.config["updated_utc"] = time.strftime(
125
+ "%Y-%m-%dT%H:%M:%SZ", time.gmtime())
126
+ log = self.config.get("_edit_log", []) or []
127
+ log.append({"ts": self.config["updated_utc"], "section": sec,
128
+ "chars": len(content), "preview": content[:120]})
129
+ self.config["_edit_log"] = log[-50:]
130
+ self.edit_times.append(time.time())
131
+ applied.append({"section": sec, "chars": len(content)})
132
+ return ""
133
+
134
+ clean = EDIT_BLOCK_RE.sub(_grab, model_text)
135
+ return clean, applied, rejected
136
+
137
+ def rollback(self, version):
138
+ """Admin-only: restore a pinned config version (caller supplies it)."""
139
+ self.audit.append("rollback", "-", "admin", "rollback to v%s" % version, True)
140
+ return True
141
+