@furongjun1999/dsh-memory 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +572 -465
- package/docs/README.md +1 -0
- package/docs/eval/DSH/346/227/245/345/277/227/347/264/242/345/274/225v2_/345/217/202/350/200/203dsh-TUI_v1.0.md +248 -0
- package/docs/eval/DSH/346/227/245/345/277/227/347/264/242/345/274/225/346/225/210/346/236/234/351/252/214/350/257/201_v1.0.md +209 -0
- package/docs/eval/DSH/347/253/257/347/274/272/351/231/267/344/270/223/351/241/271_v1.0.md +254 -0
- package/docs/eval/P1b2_/350/257/273/351/235/242/344/273/243/351/231/205/344/277/256/345/244/215_v1.0.md +124 -0
- package/docs/eval/bench_lingshu_self/bench_self.py +140 -0
- package/docs/eval/bench_lingshu_self/self_bench_result.json +404 -0
- package/docs/eval/bench_lingshu_self//347/201/265/346/236/242/350/207/252/345/272/223/347/253/257/345/210/260/347/253/257/346/243/200/347/264/242/345/256/236/346/265/213_v1.0.md +38 -0
- package/docs/eval//344/270/215/345/217/257/351/235/240/346/200/247/350/220/275/345/234/260_P0_v1.0.md +185 -0
- package/docs/eval//345/217/221/345/270/20306_/344/270/200/351/224/256/351/205/215/347/275/256/344/270/216DSH0172_v1.0.md +171 -0
- package/docs/eval//346/225/205/351/232/234/346/263/250/345/205/245/345/256/236/346/265/213_v1.0.md +422 -0
- package/docs/eval//347/253/257/345/210/260/347/253/257LoCoMoQA/345/220/214/345/217/243/345/276/204/345/257/271/347/205/247_v1.0.md +100 -0
- package/docs/eval//347/253/257/345/210/260/347/253/257/345/271/262/346/211/260/346/261/240/350/257/204/346/265/213_/347/241/256/345/256/232/346/200/247/350/243/201/345/206/263vsLLM_judge_v1.1.md +197 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v1.md +156 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v10.md +210 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v11.md +227 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v12.md +203 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v13.md +233 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v14.md +191 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v15.md +213 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v16.md +214 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v17.md +199 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v18.md +183 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v19.md +207 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v2.md +156 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v3.md +152 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v4.md +128 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v5.md +114 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v6.md +192 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v7.md +187 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v8.md +207 -0
- package/docs/eval//347/274/272/351/231/267/346/214/226/346/216/230_/350/207/252/344/270/273/350/277/255/344/273/243_v9.md +203 -0
- package/docs/hive//345/256/211/345/205/250/345/256/241/350/256/241/345/256/236/351/224/232_v0.1.md +202 -0
- package/docs/hive//350/234/202/345/267/242/345/217/214/345/256/236/344/276/213/344/272/222/351/252/214_/350/256/276/350/256/241/345/256/232/347/250/277.md +19 -3
- package/docs/images/lingshu-moonlight-covenant-poster-preview.jpg +0 -0
- package/docs/images/lingshu-moonlight-covenant-poster.png +0 -0
- package/docs/mdcg//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +40 -40
- package/docs/theory//344/270/215/345/217/257/351/235/240/345/256/232/347/220/206/344/270/216/345/244/261/346/225/210/344/274/230/345/205/210/346/241/206/346/236/266_v0.3.md +365 -0
- package/docs//344/270/215/345/217/257/351/235/240/346/200/247/347/220/206/350/256/272_v0.1.md +343 -0
- package/dsh/cordis-patch-profile-web.example.yml +35 -0
- package/lib/bridge.d.ts +9 -0
- package/lib/bridge.js +35 -0
- package/lib/cli.d.ts +3 -0
- package/lib/cli.js +66 -0
- package/lib/index.js +7 -1
- package/lib/init.d.ts +88 -0
- package/lib/init.js +287 -0
- package/lib/lib/roleplay_web.js +530 -443
- package/lib/lib/token_store.d.ts +7 -1
- package/lib/lib/token_store.js +12 -3
- package/md_cg/audit.py +12 -1
- package/md_cg/backfill.py +16 -15
- package/md_cg/backfill_bucket_zh.py +35 -0
- package/md_cg/bench_e2e_judge.py +532 -0
- package/md_cg/bench_e2e_locomo_qa.py +368 -0
- package/md_cg/bench_e2e_qa.py +256 -0
- package/md_cg/branches.py +18 -2
- package/md_cg/ccgc.py +3 -2
- package/md_cg/chain.py +19 -4
- package/md_cg/consolidate.py +7 -6
- package/md_cg/crosscheck.py +4 -3
- package/md_cg/crypto.py +439 -437
- package/md_cg/datapath.py +395 -335
- package/md_cg/evidence.py +8 -3
- package/md_cg/export.py +3 -1
- package/md_cg/forgetting.py +2 -2
- package/md_cg/fsutil.py +48 -0
- package/md_cg/hotcache.py +255 -238
- package/md_cg/interop.py +161 -22
- package/md_cg/judgment_manifest.py +177 -0
- package/md_cg/links.py +655 -622
- package/md_cg/mcp_server.py +282 -31
- package/md_cg/mdcg.py +702 -126
- package/md_cg/mdcos.py +454 -68
- package/md_cg/mreview/govern.py +5 -4
- package/md_cg/postings.py +4 -2
- package/md_cg/readcache.py +76 -18
- package/md_cg/reconcile.py +228 -0
- package/md_cg/review_cli.py +215 -0
- package/md_cg/routing.py +28 -0
- package/md_cg/run_tests.py +211 -0
- package/md_cg/scrub.py +862 -852
- package/md_cg/security.py +385 -275
- package/md_cg/selfreport.py +3 -2
- package/md_cg/signer.py +565 -562
- package/md_cg/sources.py +3 -2
- package/md_cg/stg.py +6 -0
- package/md_cg/sustain.py +1168 -1138
- package/md_cg/test_access_hints.py +147 -0
- package/md_cg/test_branch_discard_tombstone.py +136 -0
- package/md_cg/test_branches.py +259 -249
- package/md_cg/test_chain_read_isolate.py +168 -0
- package/md_cg/test_datapath_device_name.py +203 -0
- package/md_cg/test_emit_negtail_cache.py +156 -0
- package/md_cg/test_en_pipeline.py +186 -166
- package/md_cg/test_govern_directread.py +421 -0
- package/md_cg/test_i32_hotcache_env_key.py +218 -0
- package/md_cg/test_identity_attribution.py +228 -147
- package/md_cg/test_index_crossprocess_reload.py +301 -0
- package/md_cg/test_index_durability.py +238 -224
- package/md_cg/test_interop.py +4 -2
- package/md_cg/test_interop_judgment.py +228 -0
- package/md_cg/test_issue39_utf8_stdio.py +273 -0
- package/md_cg/test_links_concurrent_write.py +188 -0
- package/md_cg/test_merge_upsert.py +168 -0
- package/md_cg/test_n123_derive_expiry_chain.py +205 -0
- package/md_cg/test_n130_verify_falsified_protect.py +185 -0
- package/md_cg/test_n131_merge_gate.py +205 -0
- package/md_cg/test_none_id_write_guard.py +165 -0
- package/md_cg/test_p1x_ref_root.py +160 -0
- package/md_cg/test_p27_docindex.py +774 -765
- package/md_cg/test_p2_mcp.py +3 -0
- package/md_cg/test_p32_backfill.py +304 -298
- package/md_cg/test_p39_verify_flow.py +90 -50
- package/md_cg/test_p47_session_view.py +60 -25
- package/md_cg/test_propose_tail_index.py +157 -0
- package/md_cg/test_read_scope_b27.py +277 -0
- package/md_cg/test_readcache_default_on.py +168 -0
- package/md_cg/test_readcache_precise_inval.py +270 -0
- package/md_cg/test_readcache_prodpath.py +55 -7
- package/md_cg/test_reconcile_v0.py +294 -0
- package/md_cg/test_retr_s1.py +6 -2
- package/md_cg/test_retr_s1b.py +67 -0
- package/md_cg/test_retr_s7.py +8 -0
- package/md_cg/test_retr_s9_entity_ctx.py +181 -175
- package/md_cg/test_retr_score_once.py +208 -0
- package/md_cg/test_review_cli_attribution.py +177 -0
- package/md_cg/test_review_cli_visibility.py +235 -0
- package/md_cg/test_review_onepass.py +170 -0
- package/md_cg/test_rrf_graph_seed_cache.py +154 -0
- package/md_cg/test_security_audit.py +155 -0
- package/md_cg/test_security_audit_b26.py +161 -0
- package/md_cg/test_security_audit_v21.py +250 -0
- package/md_cg/test_semantic_canonical.py +255 -241
- package/md_cg/test_session_isolation.py +168 -0
- package/md_cg/test_snapshot_autoclose.py +187 -0
- package/md_cg/test_tail_watermark_race.py +208 -0
- package/md_cg/test_tenant_env_override_warn.py +139 -0
- package/md_cg/test_tenant_registry_corrupt_warn.py +151 -0
- package/md_cg/test_v14_fixes.py +415 -397
- package/md_cg/test_verify_dirty_reconcile.py +157 -0
- package/md_cg/theory.py +276 -273
- package/md_cg/tokens.py +734 -677
- package/md_cg/units.py +3 -2
- package/md_cg/vision_evidence.py +4 -3
- package/md_cg/whitebox_kb/wisdom/code_solidified.json +6195 -6195
- package/md_cg/whitebox_kb/wisdom/multilang_ir.py +128 -124
- package/md_cg/writepipe.py +554 -550
- package/package.json +11 -2
- package/src/bridge.ts +434 -401
- package/src/cli.ts +65 -0
- package/src/index.ts +526 -518
- package/src/init.ts +347 -0
- package/src/lib/roleplay_web.ts +1019 -932
- package/src/lib/token_store.ts +202 -192
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""review 治理面单次调用复用一次解析守卫(issue:同一 jsonl 单调用内重复全量读)。
|
|
4
|
+
|
|
5
|
+
缺陷形态(缺陷报告实测 + 当前代码复核):review_stats 链路每次全量读
|
|
6
|
+
decisions 1 遍(by_decision 统计)+ review_list 全量读 inbox 1 遍;
|
|
7
|
+
review_decide 线性扫 inbox 找 pid + _cascade_dedup 再全量读 inbox 一遍;
|
|
8
|
+
verify_review_record 无 pid 索引线性扫全量 decisions 找单条——解析量随
|
|
9
|
+
M/D 线性放大(M=D=4000 实测 review_stats 15.97ms / decide 21.31ms /
|
|
10
|
+
verify 28.92ms)。
|
|
11
|
+
|
|
12
|
+
本守卫断言(调用计数/对账形态,非时间阈值):
|
|
13
|
+
R1 稳态 review_stats 零全量读:预热装载后 read_jsonl 调用 == 0
|
|
14
|
+
(旧代码每次 decisions+inbox 各 1 次 → 红);
|
|
15
|
+
R2 稳态 review_decide(reject) 零全量读:同上(旧代码 inbox 2 次 → 红);
|
|
16
|
+
R3 稳态 verify_review_record 零全量读:同上(旧代码 decisions 1 次 → 红);
|
|
17
|
+
R4 增量不漏账:外部(他进程形态)append 新裁决/新提案后,统计口径与
|
|
18
|
+
队列视图立即可见;
|
|
19
|
+
R5 语义对账:review_stats 的 records/by_decision/pending/closed 与
|
|
20
|
+
手工全量直读的独立口径逐项一致(缓存不改变结果,只改变速度)。
|
|
21
|
+
"""
|
|
22
|
+
import os
|
|
23
|
+
import shutil
|
|
24
|
+
import sys
|
|
25
|
+
import tempfile
|
|
26
|
+
|
|
27
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
28
|
+
|
|
29
|
+
import md_cg.mdcos as mdcos_mod # noqa: E402
|
|
30
|
+
from md_cg.fsutil import append_jsonl, read_jsonl as fs_read_jsonl # noqa: E402
|
|
31
|
+
from md_cg.mdcos import MdCGOS, _sig # noqa: E402
|
|
32
|
+
|
|
33
|
+
PASS = 0
|
|
34
|
+
FAIL = 0
|
|
35
|
+
FAILS = []
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def check(name, cond, detail=""):
|
|
39
|
+
global PASS, FAIL
|
|
40
|
+
if cond:
|
|
41
|
+
PASS += 1
|
|
42
|
+
print(f" OK {name}")
|
|
43
|
+
else:
|
|
44
|
+
FAIL += 1
|
|
45
|
+
FAILS.append(name)
|
|
46
|
+
print(f" FAIL {name} {detail}")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def count_full_reads(fn):
|
|
50
|
+
n = 0
|
|
51
|
+
real = mdcos_mod.read_jsonl
|
|
52
|
+
|
|
53
|
+
def _spy(path, *a, **kw):
|
|
54
|
+
nonlocal n
|
|
55
|
+
n += 1
|
|
56
|
+
return real(path, *a, **kw)
|
|
57
|
+
|
|
58
|
+
mdcos_mod.read_jsonl = _spy
|
|
59
|
+
try:
|
|
60
|
+
return fn(), n
|
|
61
|
+
finally:
|
|
62
|
+
mdcos_mod.read_jsonl = real
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def build(root, m=8, d=5):
|
|
66
|
+
cg = MdCGOS(root)
|
|
67
|
+
for i in range(m):
|
|
68
|
+
c = "存量提案 %d 蜂群调度 %d" % (i, i)
|
|
69
|
+
append_jsonl(cg.inbox_log,
|
|
70
|
+
{"t": 1.0, "pid": "prop_g%03d" % i, "id": "n%03d" % i,
|
|
71
|
+
"content": c, "layer": "knowledge", "tags": [],
|
|
72
|
+
"condition_space": {}, "verify": {},
|
|
73
|
+
"payload_hash": _sig(c), "actor": "g"})
|
|
74
|
+
for j in range(d):
|
|
75
|
+
append_jsonl(cg.decisions_log,
|
|
76
|
+
{"t": 1.0, "pid": "prop_g%03d" % j, "decision": "accept",
|
|
77
|
+
"status": "accepted", "round": 1, "actor": "g"})
|
|
78
|
+
return cg
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def main():
|
|
82
|
+
root = tempfile.mkdtemp(prefix="mdcg_review_guard_")
|
|
83
|
+
try:
|
|
84
|
+
cg = build(root)
|
|
85
|
+
cg.review_stats() # 预热装载(不计数)
|
|
86
|
+
|
|
87
|
+
# R1 稳态 review_stats 零全量读
|
|
88
|
+
print("== R1 稳态 review_stats 零全量读 ==")
|
|
89
|
+
st1, n1 = count_full_reads(lambda: cg.review_stats())
|
|
90
|
+
check("R1a 预热后 review_stats 全量 read_jsonl==0(旧每次 2 次)",
|
|
91
|
+
n1 == 0, f"full_reads={n1}")
|
|
92
|
+
check("R1b 统计口径 sane(records≥d、pending≥1)",
|
|
93
|
+
st1["records"] >= 5 and st1["pending"] >= 1, str(st1)[:100])
|
|
94
|
+
|
|
95
|
+
# R2 稳态 review_decide(reject) 零全量读
|
|
96
|
+
print("== R2 稳态 review_decide(reject) 零全量读 ==")
|
|
97
|
+
def _decide():
|
|
98
|
+
_decide.i += 1
|
|
99
|
+
pid = cg.propose("c1", "纯 pending 候选 星云编译 %d" % _decide.i)
|
|
100
|
+
return cg.review_decide(pid, "reject", reason="g")
|
|
101
|
+
_decide.i = 0
|
|
102
|
+
_decide() # 预热:装载 phash 索引 +
|
|
103
|
+
out2, n2 = count_full_reads(_decide) # decisions 创建后的 pid 状态缓存
|
|
104
|
+
check("R2a decide(reject) 全量 read_jsonl==0(旧 inbox 2 次;"
|
|
105
|
+
"首装装载属预期不在此窗)",
|
|
106
|
+
n2 == 0, f"full_reads={n2}")
|
|
107
|
+
check("R2b 裁决 ok", out2.get("ok"), str(out2)[:80])
|
|
108
|
+
|
|
109
|
+
# R3 稳态 verify_review_record 零全量读
|
|
110
|
+
print("== R3 稳态 verify_review_record 零全量读 ==")
|
|
111
|
+
def _verify():
|
|
112
|
+
_verify.i += 1
|
|
113
|
+
pid = cg.propose("c2", "复核候选 信任聚合 %d" % _verify.i)
|
|
114
|
+
out = cg.review_decide(pid, "accept", reason="g")
|
|
115
|
+
assert out.get("ok"), out
|
|
116
|
+
return cg.verify_review_record(out["record_node_id"])
|
|
117
|
+
_verify.i = 0
|
|
118
|
+
_verify() # 预热同上
|
|
119
|
+
ok3, n3 = count_full_reads(_verify)
|
|
120
|
+
check("R3a verify 链路全量 read_jsonl==0(旧 decisions 1 次扫全量找单条)",
|
|
121
|
+
n3 == 0, f"full_reads={n3}")
|
|
122
|
+
check("R3b 复核通过", ok3.get("ok") is True, str(ok3)[:80])
|
|
123
|
+
|
|
124
|
+
# R4 增量不漏账:他进程形态 append
|
|
125
|
+
print("== R4 跨进程 append 增量并入 ==")
|
|
126
|
+
append_jsonl(cg.inbox_log,
|
|
127
|
+
{"t": 2.0, "pid": "prop_ext_x", "id": "nx",
|
|
128
|
+
"content": "外部新提案 水位信箱", "layer": "knowledge",
|
|
129
|
+
"tags": [], "condition_space": {}, "verify": {},
|
|
130
|
+
"payload_hash": _sig("外部新提案 水位信箱"),
|
|
131
|
+
"actor": "ext"})
|
|
132
|
+
append_jsonl(cg.decisions_log,
|
|
133
|
+
{"t": 2.0, "pid": "prop_ext_x", "decision": "noop",
|
|
134
|
+
"status": "noop", "round": 1, "actor": "ext"})
|
|
135
|
+
st4 = cg.review_stats()
|
|
136
|
+
check("R4a 外部新裁决进统计(by_decision.noop=1)",
|
|
137
|
+
st4["by_decision"].get("noop") == 1, str(st4["by_decision"]))
|
|
138
|
+
pend_ids = [r.get("pid") for r in cg.review_list()]
|
|
139
|
+
check("R4b 外部新裁决立即可见(prop_ext_x 已关闭,不在 pending)",
|
|
140
|
+
"prop_ext_x" not in pend_ids, f"pending={pend_ids}")
|
|
141
|
+
|
|
142
|
+
# R5 语义对账:与独立全量直读口径逐项一致
|
|
143
|
+
print("== R5 统计口径独立对账 ==")
|
|
144
|
+
by = {}
|
|
145
|
+
for r in fs_read_jsonl(cg.decisions_log):
|
|
146
|
+
d_ = str(r.get("decision") or "").strip() or "unknown"
|
|
147
|
+
by[d_] = by.get(d_, 0) + 1
|
|
148
|
+
n_pid = len({r.get("pid") for r in fs_read_jsonl(cg.decisions_log)
|
|
149
|
+
if r.get("pid")})
|
|
150
|
+
st5 = cg.review_stats()
|
|
151
|
+
check("R5a records/by_decision 与独立直读一致",
|
|
152
|
+
st5["records"] == sum(by.values())
|
|
153
|
+
and st5["by_decision"] == by,
|
|
154
|
+
f"got={st5['by_decision']} want={by}")
|
|
155
|
+
check("R5b proposals 与独立直读 pid 数一致",
|
|
156
|
+
st5["proposals"] == n_pid,
|
|
157
|
+
f"got={st5['proposals']} want={n_pid}")
|
|
158
|
+
finally:
|
|
159
|
+
shutil.rmtree(root, ignore_errors=True)
|
|
160
|
+
|
|
161
|
+
print("\n" + "=" * 60)
|
|
162
|
+
print(f"结果:PASS {PASS} / FAIL {FAIL}")
|
|
163
|
+
if FAILS:
|
|
164
|
+
for f in FAILS:
|
|
165
|
+
print(f" - {f}")
|
|
166
|
+
return 0 if FAIL == 0 else 1
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
if __name__ == "__main__":
|
|
170
|
+
sys.exit(main())
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""search_rrf 图路种子读缓存守卫(v9:92 / v13 留档复测·修复,第 14 轮)。
|
|
4
|
+
|
|
5
|
+
缺陷形态(缺陷报告实测,40 带 tags/edges 节点临时库,逐位复现):
|
|
6
|
+
_path_graph 种子腿 mdcos.py `node = self.get(n["id"])` 在边循环前执行——
|
|
7
|
+
get 裸 open+nodefile.loads(mdcg.py get 不在 readcache 包装面,install 只包
|
|
8
|
+
cg._read),search_rrf 默认 paths 含 graph → 每查询固定 ≤5 次盘读
|
|
9
|
+
(Q1 loads=45=词法 40+种子 5;Q2 增量恰 5、get 恒 5/查;graph 零产出也
|
|
10
|
+
在边循环前发生)。边目标(:1017)早已走 self._read 同形。
|
|
11
|
+
|
|
12
|
+
修复口径:种子读改走 self._read(by_id[...])(读缓存覆盖,Q2 增量归零);
|
|
13
|
+
种子 fm.id≠文件名等 by_id 不含形态回落 get 保旧口径(冷路径,正确性优先)。
|
|
14
|
+
种子来自 lexical(entries/_candidates 派生,MdCGSecure._candidates 已过
|
|
15
|
+
_readable)——绕过 get 的读隔离门无损失;图路扩展产物本就由
|
|
16
|
+
MdCGSecure.search_rrf 终态二次过滤兜底。
|
|
17
|
+
|
|
18
|
+
本守卫(调用计数断言形态,非时间阈值):
|
|
19
|
+
S1 热缓存后同查询 loads 增量 == 0(旧实现恒 5 → 红);
|
|
20
|
+
S2 热缓存后同查询 get 调用 == 0(旧实现恒 5 → 红);
|
|
21
|
+
S3 图路功能在位:词法零命中的「幽灵」节点经种子边一跳进入结果
|
|
22
|
+
(graph 路不是被静默关掉);
|
|
23
|
+
S4 两轮查询结果 ids 逐位一致(确定性);meta.paths 含四路计数;
|
|
24
|
+
S5 开/关读缓存(MDCG_READ_CACHE=1 vs 0)同库同查询 ids 逐位一致
|
|
25
|
+
(缓存只改速度不改结果)。
|
|
26
|
+
|
|
27
|
+
运行:python -m md_cg.test_rrf_graph_seed_cache
|
|
28
|
+
"""
|
|
29
|
+
import os
|
|
30
|
+
import shutil
|
|
31
|
+
import sys
|
|
32
|
+
import tempfile
|
|
33
|
+
|
|
34
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
35
|
+
|
|
36
|
+
from md_cg import nodefile # noqa: E402
|
|
37
|
+
from md_cg.mdcos import MdCGOS # noqa: E402
|
|
38
|
+
|
|
39
|
+
PASS = 0
|
|
40
|
+
FAIL = 0
|
|
41
|
+
FAILS = []
|
|
42
|
+
|
|
43
|
+
QUERY = "蜂群调度"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def check(name, cond, detail=""):
|
|
47
|
+
global PASS, FAIL
|
|
48
|
+
if cond:
|
|
49
|
+
PASS += 1
|
|
50
|
+
print(f" OK {name}")
|
|
51
|
+
else:
|
|
52
|
+
FAIL += 1
|
|
53
|
+
FAILS.append(name)
|
|
54
|
+
print(f" FAIL {name} {detail}")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def build_lib(root, n=40):
|
|
58
|
+
cg = MdCGOS(root)
|
|
59
|
+
for i in range(n):
|
|
60
|
+
edges = []
|
|
61
|
+
if i % 4 == 0: # 部分节点带边(指向池内目标)
|
|
62
|
+
edges = [{"target": "mem_%03d" % ((i + 1) % n),
|
|
63
|
+
"relation_type": "related_to"}]
|
|
64
|
+
if i == 0:
|
|
65
|
+
# 幽灵目标:词法零命中(正文无查询词),只经种子边一跳可达
|
|
66
|
+
edges.append({"target": "ghost_x", "relation_type": "related_to"})
|
|
67
|
+
cg.add("mem_%03d" % i,
|
|
68
|
+
"# 功能名:图路样本 %d\n# 正文:%s 节点 %d" % (i, QUERY, i),
|
|
69
|
+
layer="knowledge",
|
|
70
|
+
tags=["tag_%d" % (i % 5)] if i % 3 == 0 else None,
|
|
71
|
+
edges=edges)
|
|
72
|
+
cg.add("ghost_x", "# 功能名:幽灵目标\n# 正文:完全不相关的孤岛内容",
|
|
73
|
+
layer="knowledge")
|
|
74
|
+
cg.flush()
|
|
75
|
+
return cg
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def counters(cg, fn):
|
|
79
|
+
"""同时计数 nodefile.loads 与实例 get。"""
|
|
80
|
+
st = {"loads": 0, "gets": 0}
|
|
81
|
+
real_loads = nodefile.loads
|
|
82
|
+
real_get = MdCGOS.get
|
|
83
|
+
|
|
84
|
+
def _spy_loads(*a, **kw):
|
|
85
|
+
st["loads"] += 1
|
|
86
|
+
return real_loads(*a, **kw)
|
|
87
|
+
|
|
88
|
+
def _spy_get(self, node_id):
|
|
89
|
+
st["gets"] += 1
|
|
90
|
+
return real_get(self, node_id)
|
|
91
|
+
|
|
92
|
+
nodefile.loads = _spy_loads
|
|
93
|
+
cg.get = _spy_get.__get__(cg, MdCGOS)
|
|
94
|
+
try:
|
|
95
|
+
return fn(), st
|
|
96
|
+
finally:
|
|
97
|
+
nodefile.loads = real_loads
|
|
98
|
+
del cg.get
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def main():
|
|
102
|
+
root = tempfile.mkdtemp(prefix="mdcg_rrf_seed_")
|
|
103
|
+
cg = None
|
|
104
|
+
try:
|
|
105
|
+
os.environ.pop("MDCG_READ_CACHE", None)
|
|
106
|
+
cg = build_lib(root)
|
|
107
|
+
(r1, m1), c1 = counters(cg, lambda: cg.search_rrf(QUERY))
|
|
108
|
+
ids1 = [x[0].get("id") for x in r1]
|
|
109
|
+
check("S0a 首查正常(40+1 节点池,词法+图路融合)",
|
|
110
|
+
len(r1) >= 1 and c1["loads"] > 0,
|
|
111
|
+
f"n={len(r1)} loads={c1['loads']}")
|
|
112
|
+
|
|
113
|
+
(r2, m2), c2 = counters(cg, lambda: cg.search_rrf(QUERY))
|
|
114
|
+
ids2 = [x[0].get("id") for x in r2]
|
|
115
|
+
check("S1a 热缓存后同查询 loads 增量 == 0(旧实现恒 5)",
|
|
116
|
+
c2["loads"] == 0, f"loads={c2['loads']}")
|
|
117
|
+
check("S2a 热缓存后同查询 get 调用 == 0(旧实现恒 5,种子改走 _read)",
|
|
118
|
+
c2["gets"] == 0, f"gets={c2['gets']}")
|
|
119
|
+
check("S3a 图路功能在位:词法零命中的 ghost 经种子边一跳进入结果",
|
|
120
|
+
"ghost_x" in ids1, f"ghost in={('ghost_x' in ids1)}")
|
|
121
|
+
paths_meta = (m2.get("paths") or {})
|
|
122
|
+
check("S3b meta.paths 四路计数在位(graph 未被静默关掉)",
|
|
123
|
+
{"lexical", "graph"} <= set(paths_meta),
|
|
124
|
+
f"paths={paths_meta}")
|
|
125
|
+
check("S4a 两轮查询结果 ids 逐位一致", ids1 == ids2,
|
|
126
|
+
f"n={len(ids1)}/{len(ids2)}")
|
|
127
|
+
|
|
128
|
+
# S5 开/关读缓存结果一致(新实例同库同查询)
|
|
129
|
+
r_on = ids2
|
|
130
|
+
os.environ["MDCG_READ_CACHE"] = "0"
|
|
131
|
+
try:
|
|
132
|
+
cg0 = MdCGOS(root)
|
|
133
|
+
r_off, _ = cg0.search_rrf(QUERY)
|
|
134
|
+
cg0.close()
|
|
135
|
+
check("S5a 读缓存开/关同库同查询 ids 逐位一致",
|
|
136
|
+
[x[0].get("id") for x in r_off] == r_on,
|
|
137
|
+
f"off={[x[0].get('id') for x in r_off][:5]}")
|
|
138
|
+
finally:
|
|
139
|
+
os.environ.pop("MDCG_READ_CACHE", None)
|
|
140
|
+
cg.close()
|
|
141
|
+
finally:
|
|
142
|
+
os.environ.pop("MDCG_READ_CACHE", None)
|
|
143
|
+
shutil.rmtree(root, ignore_errors=True)
|
|
144
|
+
|
|
145
|
+
print("\n" + "=" * 60)
|
|
146
|
+
print(f"结果:PASS {PASS} / FAIL {FAIL}")
|
|
147
|
+
if FAILS:
|
|
148
|
+
for f in FAILS:
|
|
149
|
+
print(f" - {f}")
|
|
150
|
+
return 0 if FAIL == 0 else 1
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
if __name__ == "__main__":
|
|
154
|
+
sys.exit(main())
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""安全审计修复守卫(批次 24,外部审查报告 P0-1/P1-1/P2-1/P1-8)。
|
|
4
|
+
|
|
5
|
+
P0-1:node_id 是模型可控输入、直接拼落盘路径——`..` 穿越出 root、
|
|
6
|
+
Windows 绝对路径(os.path.join 丢弃前缀)= 任意 .md 覆盖。修复 = 白名单
|
|
7
|
+
+ realpath 纵深闸。守卫断言(能红修复前实现):
|
|
8
|
+
- 穿越/绝对路径/超长/空 id 必 ValueError;
|
|
9
|
+
- 合法 id(存量形态)照常写入。
|
|
10
|
+
P1-1:mdcg_* 工具此前完全绕过 ops_allow 白名单——只读令牌可经
|
|
11
|
+
mdcg_remember 写入。守卫断言(能红修复前实现):
|
|
12
|
+
- ops_allow=["read"] 的 principal 调 mdcg_remember 必 AccessDenied;
|
|
13
|
+
- ops_allow=None(不限)令牌照常放行(兼容性);
|
|
14
|
+
- 读工具在 read 令牌下放行。
|
|
15
|
+
P1-8:tarfile filter="data"(静态断言 + import 回归)。
|
|
16
|
+
"""
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
import tempfile
|
|
20
|
+
|
|
21
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
22
|
+
|
|
23
|
+
PASS = 0
|
|
24
|
+
FAIL = 0
|
|
25
|
+
FAILS = []
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def check(name, cond, detail=""):
|
|
29
|
+
global PASS, FAIL
|
|
30
|
+
if cond:
|
|
31
|
+
PASS += 1
|
|
32
|
+
print(f" OK {name}")
|
|
33
|
+
else:
|
|
34
|
+
FAIL += 1
|
|
35
|
+
FAILS.append(name)
|
|
36
|
+
print(f" FAIL {name} {detail}")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def main():
|
|
40
|
+
from md_cg.mdcg import _NODE_ID_RE
|
|
41
|
+
from md_cg.mdcos import MdCGSecure
|
|
42
|
+
from md_cg.mcp_server import _dispatch, _MDCG_OP_REQUIRE
|
|
43
|
+
|
|
44
|
+
root = tempfile.mkdtemp(prefix="mdcg_secaudit_")
|
|
45
|
+
# 生产形态(MdCGSecure——mdcg_search 分支走 OS 层签名,基类 MdCG 无
|
|
46
|
+
# roles 参数;P1-1c 因此必须用安全实例而非基类)
|
|
47
|
+
cg = MdCGSecure(root)
|
|
48
|
+
|
|
49
|
+
print("== P0-1 node_id 白名单 + realpath 纵深闸 ==")
|
|
50
|
+
bad_ids = [
|
|
51
|
+
(".. 穿越", "../evil"),
|
|
52
|
+
("多层穿越", "sub/../../../evil"),
|
|
53
|
+
("Windows 绝对路径", "C:/Users/test/evil"),
|
|
54
|
+
("类 Unix 绝对路径", "/etc/evil"),
|
|
55
|
+
("隐式 ..", "a/../b..c"),
|
|
56
|
+
("空 id", ""),
|
|
57
|
+
("超长 id", "a" * 129),
|
|
58
|
+
]
|
|
59
|
+
for label, nid in bad_ids:
|
|
60
|
+
try:
|
|
61
|
+
cg.add(nid, "# 功能名:x\n# 正文:y", layer="knowledge")
|
|
62
|
+
check(f"P0-1 拒绝 {label}", False, "未抛异常且写入成功")
|
|
63
|
+
except ValueError:
|
|
64
|
+
check(f"P0-1 拒绝 {label}", True)
|
|
65
|
+
except Exception as e: # noqa: BLE001
|
|
66
|
+
check(f"P0-1 拒绝 {label}", False, f"异常类型错误: {type(e).__name__}: {e}")
|
|
67
|
+
|
|
68
|
+
ok_ids = ["mem_1790156428627", "n00001", "mde_abcdef123456",
|
|
69
|
+
"uuid-4f2b-9c.a-d", "a" * 128]
|
|
70
|
+
ok = True
|
|
71
|
+
for i, nid in enumerate(ok_ids):
|
|
72
|
+
try:
|
|
73
|
+
cg.add(nid, f"# 功能名:合法 {i}\n# 正文:白名单内存活样本 {i}",
|
|
74
|
+
layer="knowledge")
|
|
75
|
+
except Exception as e: # noqa: BLE001
|
|
76
|
+
ok = False
|
|
77
|
+
check(f"P0-1 合法 id {nid[:16]}… 照常写入", False,
|
|
78
|
+
f"{type(e).__name__}: {e}")
|
|
79
|
+
check("P0-1 合法 id(存量 5 形态)照常写入", ok and _NODE_ID_RE.match("mem_1"))
|
|
80
|
+
|
|
81
|
+
print("== P1-1 mdcg_* op 闸(能红无闸实现)==")
|
|
82
|
+
from md_cg.security import Principal
|
|
83
|
+
|
|
84
|
+
def principal_with(ops):
|
|
85
|
+
return Principal(actor="auditor", clearance="secret",
|
|
86
|
+
can_write=True, role="recorder", auth_mode="test",
|
|
87
|
+
ops_allow=ops)
|
|
88
|
+
|
|
89
|
+
# 只读令牌:mdcg_remember 必拒(能红:修复前无闸直接写入)
|
|
90
|
+
cg.principal = principal_with(["read"])
|
|
91
|
+
try:
|
|
92
|
+
_dispatch(cg, "mdcg_remember", {"content": "# 功能名:越权\n# 正文:x",
|
|
93
|
+
"layer": "contextual"})
|
|
94
|
+
check("P1-1a 只读令牌 mdcg_remember 必拒", False, "未抛 AccessDenied")
|
|
95
|
+
except Exception as e: # noqa: BLE001
|
|
96
|
+
check("P1-1a 只读令牌 mdcg_remember 必拒(AccessDenied)",
|
|
97
|
+
type(e).__name__ == "AccessDenied",
|
|
98
|
+
f"{type(e).__name__}: {e}")
|
|
99
|
+
|
|
100
|
+
# ops_allow=None(不限)令牌:放行(兼容性——存量直构形态零影响;
|
|
101
|
+
# _in_scope(None)=True 是「未声明=不限制」的既有语义)
|
|
102
|
+
saved = cg.principal
|
|
103
|
+
cg.principal = principal_with(None)
|
|
104
|
+
try:
|
|
105
|
+
res = _dispatch(cg, "mdcg_remember",
|
|
106
|
+
{"content": "# 功能名:兼容\n# 正文:不限令牌照常",
|
|
107
|
+
"layer": "contextual"})
|
|
108
|
+
check("P1-1b ops_allow=None(不限)令牌照常放行", True)
|
|
109
|
+
except Exception as e: # noqa: BLE001
|
|
110
|
+
check("P1-1b ops_allow=None(不限)令牌照常放行", False,
|
|
111
|
+
f"{type(e).__name__}: {e}")
|
|
112
|
+
finally:
|
|
113
|
+
cg.principal = saved
|
|
114
|
+
|
|
115
|
+
# 读令牌:读工具放行
|
|
116
|
+
cg.principal = principal_with(["read"])
|
|
117
|
+
try:
|
|
118
|
+
res = _dispatch(cg, "mdcg_search", {"query": "星云"})
|
|
119
|
+
check("P1-1c 读令牌读工具放行", not isinstance(res, dict) or not res.get("error"),
|
|
120
|
+
str(res)[:120])
|
|
121
|
+
except Exception as e: # noqa: BLE001
|
|
122
|
+
check("P1-1c 读令牌读工具放行", False, f"{type(e).__name__}: {e}")
|
|
123
|
+
cg.principal = None
|
|
124
|
+
|
|
125
|
+
# 映射完备性:mdcg_* 全部注册工具都被映射覆盖(防未来新增工具漏闸)
|
|
126
|
+
from md_cg import mcp_server as ms
|
|
127
|
+
import re as _re
|
|
128
|
+
src = open(os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
|
129
|
+
"mcp_server.py"), encoding="utf-8").read()
|
|
130
|
+
registered = set(_re.findall(r'"name": "(mdcg_[a-z_]+)"', src))
|
|
131
|
+
uncovered = sorted(registered - set(_MDCG_OP_REQUIRE) - {"mdcg_service_info"})
|
|
132
|
+
check("P1-1d mdcg_* 注册工具全覆盖(除自描述 service_info)",
|
|
133
|
+
not uncovered, f"uncovered={uncovered}")
|
|
134
|
+
|
|
135
|
+
print("== P1-8 tarfile filter ==")
|
|
136
|
+
src_cf = open(os.path.join(os.path.dirname(os.path.dirname(
|
|
137
|
+
os.path.abspath(__file__))), "scripts", "criteria_fingerprint.py"),
|
|
138
|
+
encoding="utf-8").read()
|
|
139
|
+
check("P1-8 extractall 显式 filter=\"data\"", 'extractall(tmp, filter="data")'
|
|
140
|
+
in src_cf)
|
|
141
|
+
|
|
142
|
+
# 清理
|
|
143
|
+
import shutil
|
|
144
|
+
shutil.rmtree(root, ignore_errors=True)
|
|
145
|
+
|
|
146
|
+
print("\n" + "=" * 60)
|
|
147
|
+
print(f"结果:PASS {PASS} / FAIL {FAIL}")
|
|
148
|
+
if FAILS:
|
|
149
|
+
for f in FAILS:
|
|
150
|
+
print(f" - {f}")
|
|
151
|
+
return 0 if FAIL == 0 else 1
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
if __name__ == "__main__":
|
|
155
|
+
sys.exit(main())
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""安全审计修复守卫 B(批次 26,外部审查报告 P1-2/3/4/5/6/7)。
|
|
4
|
+
|
|
5
|
+
- P1-4:check_path_root 根白名单(env 未设=放开 / 设置=越界即拒)
|
|
6
|
+
- P1-5:LEGACY env 身份不再自授 admin(需显式二次开关)
|
|
7
|
+
- P1-7:bootstrap_loop AST 沙箱(合法排序函数过 / 恶意样本全拒)
|
|
8
|
+
- P1-6 增量:hive read_file 敏感凭据路径拒读
|
|
9
|
+
- P1-2/P1-3:TS 侧(roleplay_web)静态断言 + tsc 编译由 gate 覆盖
|
|
10
|
+
"""
|
|
11
|
+
import importlib.util
|
|
12
|
+
import os
|
|
13
|
+
import sys
|
|
14
|
+
import tempfile
|
|
15
|
+
|
|
16
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
17
|
+
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
18
|
+
|
|
19
|
+
PASS = 0
|
|
20
|
+
FAIL = 0
|
|
21
|
+
FAILS = []
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def check(name, cond, detail=""):
|
|
25
|
+
global PASS, FAIL
|
|
26
|
+
if cond:
|
|
27
|
+
PASS += 1
|
|
28
|
+
print(f" OK {name}")
|
|
29
|
+
else:
|
|
30
|
+
FAIL += 1
|
|
31
|
+
FAILS.append(name)
|
|
32
|
+
print(f" FAIL {name} {detail}")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def main():
|
|
36
|
+
from md_cg.security import check_path_root
|
|
37
|
+
|
|
38
|
+
print("== P1-4 check_path_root 根白名单 ==")
|
|
39
|
+
tmp = tempfile.mkdtemp(prefix="b26_root_")
|
|
40
|
+
os.environ["MDCG_INGEST_ROOT"] = tmp
|
|
41
|
+
try:
|
|
42
|
+
check_path_root(os.path.join(tmp, "a.jsonl"), "MDCG_INGEST_ROOT", "t")
|
|
43
|
+
check("P1-4a 根内路径放行", True)
|
|
44
|
+
try:
|
|
45
|
+
check_path_root(os.path.join(tmp, "..", "evil.jsonl"),
|
|
46
|
+
"MDCG_INGEST_ROOT", "t")
|
|
47
|
+
check("P1-4b .. 穿越出根拒绝", False, "未抛")
|
|
48
|
+
except PermissionError:
|
|
49
|
+
check("P1-4b .. 穿越出根拒绝", True)
|
|
50
|
+
try:
|
|
51
|
+
check_path_root("C:/Windows/win.ini", "MDCG_INGEST_ROOT", "t")
|
|
52
|
+
check("P1-4c 根外绝对路径拒绝", False, "未抛")
|
|
53
|
+
except PermissionError:
|
|
54
|
+
check("P1-4c 根外绝对路径拒绝", True)
|
|
55
|
+
check_path_root(None, "MDCG_INGEST_ROOT", "t")
|
|
56
|
+
check("P1-4d 无路径参数(stat 类)放行", True)
|
|
57
|
+
finally:
|
|
58
|
+
os.environ.pop("MDCG_INGEST_ROOT", None)
|
|
59
|
+
check_path_root("C:/Windows/win.ini", "MDCG_INGEST_ROOT", "t")
|
|
60
|
+
check("P1-4e env 未设置=放开(默认部署零变更)", True)
|
|
61
|
+
|
|
62
|
+
print("== P1-5 LEGACY env 身份 admin 二次开关 ==")
|
|
63
|
+
saved = {k: os.environ.get(k) for k in
|
|
64
|
+
("MDCG_TOKEN", "MDCG_LEGACY_ENV_AUTH", "MDCG_CAN_ADMIN",
|
|
65
|
+
"MDCG_LEGACY_ENV_ADMIN", "MDCG_CLEARANCE")}
|
|
66
|
+
try:
|
|
67
|
+
os.environ["MDCG_TOKEN"] = ""
|
|
68
|
+
os.environ["MDCG_LEGACY_ENV_AUTH"] = "1"
|
|
69
|
+
os.environ["MDCG_CAN_ADMIN"] = "1"
|
|
70
|
+
os.environ.pop("MDCG_LEGACY_ENV_ADMIN", None)
|
|
71
|
+
from md_cg.mcp_server import _build_principal
|
|
72
|
+
p, err = _build_principal()
|
|
73
|
+
check("P1-5a 只设 CAN_ADMIN:can_admin 强制 False",
|
|
74
|
+
p is not None and not p.can_admin, f"p={p}")
|
|
75
|
+
check("P1-5b 降级 recorder", p is not None and p.role == "recorder",
|
|
76
|
+
f"role={getattr(p, 'role', None)}")
|
|
77
|
+
os.environ["MDCG_LEGACY_ENV_ADMIN"] = "1"
|
|
78
|
+
p2, _ = _build_principal()
|
|
79
|
+
check("P1-5c 二次开关后 admin 恢复",
|
|
80
|
+
p2 is not None and p2.can_admin and p2.role == "designer",
|
|
81
|
+
f"p2={p2}")
|
|
82
|
+
finally:
|
|
83
|
+
for k, v in saved.items():
|
|
84
|
+
if v is None:
|
|
85
|
+
os.environ.pop(k, None)
|
|
86
|
+
else:
|
|
87
|
+
os.environ[k] = v
|
|
88
|
+
|
|
89
|
+
print("== P1-7 bootstrap_loop AST 沙箱 ==")
|
|
90
|
+
spec = importlib.util.spec_from_file_location(
|
|
91
|
+
"bl_b26", os.path.join(REPO, "scripts", "bootstrap_loop.py"))
|
|
92
|
+
m = importlib.util.module_from_spec(spec)
|
|
93
|
+
try:
|
|
94
|
+
spec.loader.exec_module(m)
|
|
95
|
+
except SystemExit:
|
|
96
|
+
pass
|
|
97
|
+
f = m._safe_exec_gen
|
|
98
|
+
ns = f("def solve(arr):\n"
|
|
99
|
+
" n = len(arr)\n"
|
|
100
|
+
" for i in range(n):\n"
|
|
101
|
+
" for j in range(0, n - i - 1):\n"
|
|
102
|
+
" if arr[j] > arr[j + 1]:\n"
|
|
103
|
+
" arr[j], arr[j + 1] = arr[j + 1], arr[j]\n"
|
|
104
|
+
" return arr\n")
|
|
105
|
+
check("P1-7a 合法排序函数通过且正确", ns["solve"]([3, 1, 2]) == [1, 2, 3])
|
|
106
|
+
bans = [
|
|
107
|
+
"import os", "from subprocess import run", 'open("x")',
|
|
108
|
+
'eval("1")', 'exec("x=1")', '__import__("os")',
|
|
109
|
+
"x.__class__", "getattr(int, 'real')",
|
|
110
|
+
]
|
|
111
|
+
n_blocked = 0
|
|
112
|
+
for c in bans:
|
|
113
|
+
try:
|
|
114
|
+
f("def f():\n return " + c if not c.startswith(("import", "from"))
|
|
115
|
+
else c)
|
|
116
|
+
except ValueError:
|
|
117
|
+
n_blocked += 1
|
|
118
|
+
except Exception: # noqa: BLE001——其它异常也算被拦
|
|
119
|
+
n_blocked += 1
|
|
120
|
+
check(f"P1-7b 恶意样本全拒({n_blocked}/{len(bans)})",
|
|
121
|
+
n_blocked == len(bans))
|
|
122
|
+
|
|
123
|
+
print("== P1-6 增量:hive read_file 敏感路径拒读 ==")
|
|
124
|
+
spec2 = importlib.util.spec_from_file_location(
|
|
125
|
+
"hx_b26", os.path.join(REPO, "hive", "exec.py"))
|
|
126
|
+
hx = importlib.util.module_from_spec(spec2)
|
|
127
|
+
try:
|
|
128
|
+
spec2.loader.exec_module(hx)
|
|
129
|
+
except SystemExit:
|
|
130
|
+
pass
|
|
131
|
+
home = os.path.expanduser("~").replace("\\", "/")
|
|
132
|
+
check("P1-6a ~/.ssh/id_rsa 拒",
|
|
133
|
+
hx._sensitive_read(os.path.join(home, ".ssh", "id_rsa")) is not None)
|
|
134
|
+
check("P1-6b .env 拒",
|
|
135
|
+
hx._sensitive_read(os.path.join(home, "proj", ".env")) is not None)
|
|
136
|
+
check("P1-6c server.pem 拒",
|
|
137
|
+
hx._sensitive_read(os.path.join(home, "certs", "server.pem"))
|
|
138
|
+
is not None)
|
|
139
|
+
check("P1-6d 普通 md 放行",
|
|
140
|
+
hx._sensitive_read(os.path.join(home, "notes", "readme.md")) is None)
|
|
141
|
+
check("P1-6e 源码 .py 放行",
|
|
142
|
+
hx._sensitive_read(os.path.join(home, "proj", "main.py")) is None)
|
|
143
|
+
|
|
144
|
+
print("== P1-2/P1-3 roleplay_web 静态断言(行为由 tsc+集成覆盖)==")
|
|
145
|
+
ts = open(os.path.join(REPO, "src", "lib", "roleplay_web.ts"),
|
|
146
|
+
encoding="utf-8").read()
|
|
147
|
+
check("P1-2 fail-closed(未配置不再 return true)",
|
|
148
|
+
"if (!EDIT_KEY)\n return false;" in ts
|
|
149
|
+
and "editKeyHint" in ts)
|
|
150
|
+
check("P1-3 角色白名单+危险键拒绝",
|
|
151
|
+
"validRoleId" in ts and "'__proto__'" in ts and "toNullProto" in ts)
|
|
152
|
+
|
|
153
|
+
print("\n" + "=" * 60)
|
|
154
|
+
print(f"结果:PASS {PASS} / FAIL {FAIL}")
|
|
155
|
+
for x in FAILS:
|
|
156
|
+
print(f" - {x}")
|
|
157
|
+
return 0 if FAIL == 0 else 1
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
if __name__ == "__main__":
|
|
161
|
+
sys.exit(main())
|