RFTSystems commited on
Commit
876f891
·
verified ·
1 Parent(s): adb9c3b

Update brutal_test.py

Browse files
Files changed (1) hide show
  1. brutal_test.py +160 -112
brutal_test.py CHANGED
@@ -1,125 +1,173 @@
1
- import os, json, tempfile, shutil, zipfile
 
 
 
 
 
 
2
  import rft_flightrecorder as fr
3
 
4
- def read_lines(p):
5
- with open(p, "r", encoding="utf-8") as f:
6
- return [ln.rstrip("\n") for ln in f if ln.strip()]
7
 
8
- def write_lines(p, lines):
9
- with open(p, "w", encoding="utf-8") as f:
10
- for ln in lines:
11
- f.write(ln + "\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
- def assert_true(cond, msg):
14
- if not cond:
15
- raise AssertionError(msg)
16
 
17
- def assert_false(cond, msg):
18
- if cond:
19
- raise AssertionError(msg)
20
 
21
  def main():
22
- td = tempfile.mkdtemp(prefix="rft_brutal_")
23
- log = os.path.join(td, "flightlog.jsonl")
24
- print("TEMP:", td)
25
 
26
- sk, pk = fr.gen_keys()
 
27
 
28
- # --- PASS flow ---
29
- sid, msg = fr.start_session(log, "audit-demo", "deterministic", "brutal test", True, sk)
30
- print("start:", msg)
31
- assert_true(sid, "No session_id returned")
32
 
33
- # append signed events
34
- for i in range(1, 6):
35
- ev, m = fr.append_event(
36
- log_path=log,
37
- session_id=sid,
38
- event_type="note",
39
- payload_text=json.dumps({"i": i, "msg": "ok"}, ensure_ascii=False),
40
- parent_event_hash="",
41
- sign_event=True,
42
- sk_hex=sk,
43
- model_id="audit-demo",
44
- run_mode="deterministic",
45
- )
46
- assert_true(ev is not None, f"append failed at {i}: {m}")
47
-
48
- status, ok, report = fr.verify_session(log, sid, pk, require_signatures=True)
49
- print("verify signed:", status)
50
- assert_true(ok, "Signed session should verify PASS")
51
-
52
- anchor, msg = fr.finalise_session(log, sid, True, sk, "audit-demo", "deterministic")
53
- print("finalise:", msg)
54
- assert_true(anchor is not None, "Finalise failed")
55
-
56
- status, ok, report = fr.verify_session(log, sid, pk, require_signatures=True)
57
- print("verify after finalise:", status)
58
- assert_true(ok, "Session should still verify after finalise")
59
-
60
- bundle, msg = fr.export_session_bundle(log, sid)
61
- print("export:", msg)
62
- assert_true(bundle and os.path.exists(bundle), "Bundle not created")
63
-
64
- # import verify
65
- status, ok, report, stored = fr.import_bundle_verify(bundle, pk_hex=pk, require_signatures=False, store_into_log=False, log_path=log)
66
- print("import verify:", status)
67
- assert_true(ok, "Imported bundle should verify PASS")
68
-
69
- # --- FAIL: tamper payload ---
70
- lines = read_lines(log)
71
- assert_true(len(lines) > 3, "Not enough log lines")
72
-
73
- tampered = lines[:]
74
- obj = json.loads(tampered[2])
75
- obj["payload"]["msg"] = "hacked"
76
- tampered[2] = json.dumps(obj, ensure_ascii=False)
77
- tamper_log = os.path.join(td, "tamper_payload.jsonl")
78
- write_lines(tamper_log, tampered)
79
-
80
- status, ok, report = fr.verify_session(tamper_log, sid, pk, require_signatures=True)
81
- print("tamper payload verify:", status)
82
- assert_false(ok, "Tampered payload must FAIL")
83
-
84
- # --- FAIL: reorder lines ---
85
- reordered = lines[:]
86
- if len(reordered) >= 4:
87
- reordered[2], reordered[3] = reordered[3], reordered[2]
88
- reorder_log = os.path.join(td, "tamper_reorder.jsonl")
89
- write_lines(reorder_log, reordered)
90
-
91
- status, ok, report = fr.verify_session(reorder_log, sid, pk, require_signatures=True)
92
- print("reorder verify:", status)
93
- assert_false(ok, "Reordered events must FAIL")
94
-
95
- # --- FAIL: delete a line ---
96
- deleted = lines[:]
97
- deleted.pop(2)
98
- del_log = os.path.join(td, "tamper_delete.jsonl")
99
- write_lines(del_log, deleted)
100
-
101
- status, ok, report = fr.verify_session(del_log, sid, pk, require_signatures=True)
102
- print("delete verify:", status)
103
- assert_false(ok, "Deleted event must FAIL")
104
-
105
- # --- FAIL: wrong public key ---
106
- sk2, pk2 = fr.gen_keys()
107
- status, ok, report = fr.verify_session(log, sid, pk2, require_signatures=True)
108
- print("wrong pk verify:", status)
109
- assert_false(ok, "Wrong public key must FAIL")
110
-
111
- # --- FAIL: require signatures but unsigned event exists ---
112
- # create new session with mixed signed/unsigned
113
- sid2, _ = fr.start_session(log, "audit-demo", "deterministic", "mixed sigs", False, sk)
114
- ev, _ = fr.append_event(log, sid2, "note", '{"x":1}', "", False, sk, "audit-demo", "deterministic") # unsigned
115
- status, ok, report = fr.verify_session(log, sid2, pk, require_signatures=True)
116
- print("require sigs w/ unsigned:", status)
117
- assert_false(ok, "Require signatures must fail if any event unsigned")
118
-
119
- print("\nALL BRUTAL TESTS PASSED (meaning PASS/FAIL behaved correctly).")
120
- shutil.rmtree(td, ignore_errors=True)
121
- # leave bundle in cwd for inspection
122
- print("Bundle left in cwd:", bundle)
123
 
124
  if __name__ == "__main__":
125
  main()
 
1
+ import os
2
+ import json
3
+ import threading
4
+ import tempfile
5
+ import zipfile
6
+ import time
7
+
8
  import rft_flightrecorder as fr
9
 
 
 
 
10
 
11
+ def _append_with_retry(*, tries: int = 40, sleep_s: float = 0.01, **kwargs):
12
+ last = ""
13
+ for _ in range(tries):
14
+ ev, msg = fr.append_event(**kwargs)
15
+ if ev is not None:
16
+ return ev, msg
17
+ last = msg or ""
18
+ if "busy" in last.lower() or "lock" in last.lower():
19
+ time.sleep(sleep_s)
20
+ continue
21
+ break
22
+ return None, last
23
+
24
+
25
+ def two_tab_spam_test(log_path: str, threads: int = 6, events_per_thread: int = 80):
26
+ sk_hex, _pk_hex = fr.gen_keys()
27
+ sid, msg = fr.start_session(log_path, "brutal-test", "deterministic", "spam test", False, sk_hex)
28
+ assert sid, f"Failed to start session: {msg}"
29
+
30
+ errors = []
31
+ errors_lock = threading.Lock()
32
+
33
+ def worker(tid: int):
34
+ for i in range(events_per_thread):
35
+ payload = json.dumps({"thread": tid, "i": i}, ensure_ascii=False)
36
+ ev, m = _append_with_retry(
37
+ log_path=log_path,
38
+ session_id=sid,
39
+ event_type="note",
40
+ payload_text=payload,
41
+ parent_event_hash="",
42
+ sign_event=False,
43
+ sk_hex=sk_hex,
44
+ model_id="brutal-test",
45
+ run_mode="deterministic",
46
+ )
47
+ if not ev:
48
+ with errors_lock:
49
+ errors.append(m)
50
+
51
+ ts = [threading.Thread(target=worker, args=(t,)) for t in range(threads)]
52
+ for t in ts:
53
+ t.start()
54
+ for t in ts:
55
+ t.join()
56
+
57
+ assert not errors, f"Append errors occurred (first 5): {errors[:5]}"
58
+
59
+ status, ok, report = fr.verify_session(log_path, sid, pk_hex="", require_signatures=False)
60
+ assert ok, f"Session failed verification after spam.\n{status}\n{report}"
61
+
62
+ all_events, _corrupt = fr.read_jsonl(log_path)
63
+ evs = fr.events_for_session(all_events, sid)
64
+ expected_before = 1 + (threads * events_per_thread)
65
+ assert len(evs) == expected_before, f"Expected {expected_before} events before finalise, got {len(evs)}"
66
+
67
+ anchor, m = fr.finalise_session(log_path, sid, False, sk_hex, "brutal-test", "deterministic")
68
+ assert anchor, f"Finalise failed: {m}"
69
+
70
+ ev, m = fr.append_event(
71
+ log_path=log_path,
72
+ session_id=sid,
73
+ event_type="note",
74
+ payload_text='{"post_end": true}',
75
+ parent_event_hash="",
76
+ sign_event=False,
77
+ sk_hex=sk_hex,
78
+ model_id="brutal-test",
79
+ run_mode="deterministic",
80
+ )
81
+ assert ev is None and "end" in (m or "").lower(), f"Expected refused append after end, got: {m}"
82
+
83
+ status, ok, report = fr.verify_session(log_path, sid, pk_hex="", require_signatures=False)
84
+ assert ok, f"Session failed verification after finalise.\n{status}\n{report}"
85
+
86
+ all_events, _corrupt = fr.read_jsonl(log_path)
87
+ evs = fr.events_for_session(all_events, sid)
88
+ expected_after = expected_before + 1 # session_end
89
+ assert len(evs) == expected_after, f"Expected {expected_after} events after finalise, got {len(evs)}"
90
+
91
+ return sid
92
+
93
+
94
+ def tamper_zip_test(log_path: str):
95
+ sk_hex, _pk_hex = fr.gen_keys()
96
+ sid, msg = fr.start_session(log_path, "brutal-test", "deterministic", "tamper test", False, sk_hex)
97
+ assert sid, f"Failed to start session: {msg}"
98
+
99
+ for i in range(25):
100
+ ev, m = _append_with_retry(
101
+ log_path=log_path,
102
+ session_id=sid,
103
+ event_type="note",
104
+ payload_text=json.dumps({"i": i}),
105
+ parent_event_hash="",
106
+ sign_event=False,
107
+ sk_hex=sk_hex,
108
+ model_id="brutal-test",
109
+ run_mode="deterministic",
110
+ )
111
+ assert ev, f"Append failed during tamper test: {m}"
112
+
113
+ anchor, m = fr.finalise_session(log_path, sid, False, sk_hex, "brutal-test", "deterministic")
114
+ assert anchor, f"Finalise failed during tamper test: {m}"
115
+
116
+ zip_name, msg = fr.export_session_bundle(log_path, sid)
117
+ assert zip_name and os.path.exists(zip_name), f"Export failed: {msg}"
118
+
119
+ with tempfile.TemporaryDirectory() as td:
120
+ with zipfile.ZipFile(zip_name, "r") as z:
121
+ z.extractall(td)
122
+
123
+ events_file = None
124
+ for fn in os.listdir(td):
125
+ if fn.endswith("_events.jsonl"):
126
+ events_file = os.path.join(td, fn)
127
+ break
128
+ assert events_file, "No events jsonl found inside exported zip"
129
+
130
+ with open(events_file, "r", encoding="utf-8") as f:
131
+ lines = f.read().splitlines()
132
+ assert len(lines) > 5, "Not enough events in bundle to tamper"
133
+
134
+ obj = json.loads(lines[4])
135
+ obj.setdefault("payload", {})
136
+ if isinstance(obj["payload"], dict):
137
+ obj["payload"]["tampered"] = True
138
+ lines[4] = json.dumps(obj, ensure_ascii=False)
139
+
140
+ with open(events_file, "w", encoding="utf-8") as f:
141
+ f.write("\n".join(lines) + "\n")
142
+
143
+ tampered_zip = os.path.join(td, "tampered.zip")
144
+ with zipfile.ZipFile(tampered_zip, "w", compression=zipfile.ZIP_DEFLATED) as z:
145
+ z.write(events_file, arcname=os.path.basename(events_file))
146
+
147
+ status, ok, report, _ = fr.import_bundle_verify(
148
+ bundle_path=tampered_zip,
149
+ pk_hex="",
150
+ require_signatures=False,
151
+ store_into_log=False,
152
+ log_path=log_path,
153
+ )
154
+ assert not ok, f"Tampered bundle incorrectly verified PASS.\n{status}\n{report}"
155
 
156
+ return True
 
 
157
 
 
 
 
158
 
159
  def main():
160
+ with tempfile.TemporaryDirectory() as td:
161
+ log_path = os.path.join(td, "flightlog.jsonl")
 
162
 
163
+ sid1 = two_tab_spam_test(log_path, threads=8, events_per_thread=60)
164
+ print(f"[PASS] two_tab_spam_test: session_id={sid1}")
165
 
166
+ ok = tamper_zip_test(log_path)
167
+ print(f"[PASS] tamper_zip_test: {ok}")
168
+
169
+ print("[ALL PASS]")
170
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
  if __name__ == "__main__":
173
  main()