@furongjun1999/dsh-memory 0.5.0 → 0.5.1
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 +552 -465
- package/docs/README.md +1 -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//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_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/lib/bridge.d.ts +9 -0
- package/lib/bridge.js +35 -0
- package/lib/index.js +7 -1
- 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 +582 -580
- 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 +280 -29
- package/md_cg/mdcg.py +616 -125
- package/md_cg/mdcos.py +430 -66
- 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 +170 -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_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_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_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 +6 -2
- package/src/bridge.ts +434 -401
- package/src/index.ts +526 -518
- package/src/lib/roleplay_web.ts +1019 -932
- package/src/lib/token_store.ts +202 -192
|
@@ -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())
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""批次 34 守卫:V21 报告八项缺陷的能红断言。
|
|
3
|
+
|
|
4
|
+
V21-1 editKeyHint 无限自递归(TS 侧静态断言)
|
|
5
|
+
V21-2 _readable 密级回填 fail-open(行为断言:_read 失败 → secret 档拒读)
|
|
6
|
+
V21-3 _NODE_ID_RE 中文/逗号(5 官方红同源——直断言 + 防线不回退)
|
|
7
|
+
V21-4 run_channel_b 主体归位(冒烟:队列空场景返回 dict 非 None + 沙箱模块级)
|
|
8
|
+
V21-5 P2-3 就地剥离自愈(门控残留两次查询后消失)
|
|
9
|
+
V21-6 P2-4 doc_key[0] 可哈希(reach 分支不再 TypeError)
|
|
10
|
+
V21-7 lingshu_cg 返回体递归脱敏(_redact_deep 行为 + 接线静态断言)
|
|
11
|
+
V21-8 敏感名族匹配(报告 8 类实测样本全拒 + 正常文件不误伤)
|
|
12
|
+
"""
|
|
13
|
+
import ast
|
|
14
|
+
import os
|
|
15
|
+
import sys
|
|
16
|
+
import tempfile
|
|
17
|
+
|
|
18
|
+
PASS = 0
|
|
19
|
+
FAIL = 0
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def check(name, cond, detail=""):
|
|
23
|
+
global PASS, FAIL
|
|
24
|
+
if cond:
|
|
25
|
+
PASS += 1
|
|
26
|
+
print(f" [PASS] {name}")
|
|
27
|
+
else:
|
|
28
|
+
FAIL += 1
|
|
29
|
+
print(f" [FAIL] {name} {detail}")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def v21_1_editkeyhint():
|
|
33
|
+
print("== V21-1 editKeyHint 真分支非自调用 ==")
|
|
34
|
+
src = open("src/lib/roleplay_web.ts", encoding="utf-8").read()
|
|
35
|
+
check("已配置分支返回终止文案(非自调用)",
|
|
36
|
+
"? '缺少编辑密钥(x-edit-key 头)'" in src)
|
|
37
|
+
check("无限递归形态已消除", "? editKeyHint()" not in src)
|
|
38
|
+
check("未配置分支引导文案保留",
|
|
39
|
+
"编辑未开放:服务端需配置环境变量 ROLEPLAY_EDIT_KEY" in src)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def v21_2_readable_failclosed():
|
|
43
|
+
print("== V21-2 _readable 密级未知 fail-closed ==")
|
|
44
|
+
root = tempfile.mkdtemp(prefix="v21_2_")
|
|
45
|
+
from md_cg.mdcos import MdCGSecure
|
|
46
|
+
from md_cg.security import Principal
|
|
47
|
+
cg = MdCGSecure(root, principal=Principal(
|
|
48
|
+
actor="peer", clearance="internal", can_write=False, can_admin=False,
|
|
49
|
+
auth_mode="test"))
|
|
50
|
+
saved = cg._read
|
|
51
|
+
cg._read = lambda e: (None, None) # 模拟盘读失败(V21-2 触发形态)
|
|
52
|
+
e = {"path": "knowledge/prot.md", "layer": "knowledge",
|
|
53
|
+
"sensitivity": None}
|
|
54
|
+
try:
|
|
55
|
+
ok = cg._readable(e)
|
|
56
|
+
finally:
|
|
57
|
+
cg._read = saved
|
|
58
|
+
check("密级未知节点对平级 internal 身份不可见", ok is False,
|
|
59
|
+
f"_readable={ok!r}(fail-open 则为 True)")
|
|
60
|
+
check("回填落为最高档 secret", e.get("sensitivity") == "secret",
|
|
61
|
+
str(e.get("sensitivity")))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def v21_3_nodeid_charset():
|
|
65
|
+
print("== V21-3 node_id 中文/逗号放行 + 防线不回退 ==")
|
|
66
|
+
from md_cg.mdcg import _NODE_ID_RE, MdCG
|
|
67
|
+
root = tempfile.mkdtemp(prefix="v21_3_")
|
|
68
|
+
cg = MdCG(root)
|
|
69
|
+
for nid in ("task_任务-名-一", "metal_铜", "perturb_0a_电路",
|
|
70
|
+
"comma,node", "票据-research-审计"):
|
|
71
|
+
check(f"中文/逗号 id 放行:{nid}", bool(_NODE_ID_RE.match(nid)))
|
|
72
|
+
got = cg.add(nid, f"# 功能名:占位{nid}\n# 生效条件:占位\n"
|
|
73
|
+
f"# 子功能:占位\n# 执行:占位\n# 验证方式:占位\n"
|
|
74
|
+
f"# 不适用条件:占位\n# 正文四要素:占位", "contextual")
|
|
75
|
+
check(f" 落盘成功:{nid}", got == nid, repr(got))
|
|
76
|
+
for bad in ("../evil", "a:b", "/abs/path", "x" * 129, ""):
|
|
77
|
+
check(f" 恶意 id 仍拒:{bad[:20]!r}", not _NODE_ID_RE.match(bad)
|
|
78
|
+
if bad else True)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def v21_4_run_channel_b():
|
|
82
|
+
print("== V21-4 run_channel_b 主体归位 ==")
|
|
83
|
+
src = open("scripts/bootstrap_loop.py", encoding="utf-8").read()
|
|
84
|
+
tree = ast.parse(src)
|
|
85
|
+
lines = src.splitlines()
|
|
86
|
+
# 沙箱函数必须在模块级(不在任何函数体内)
|
|
87
|
+
sandbox_at_top = any(
|
|
88
|
+
isinstance(n, ast.FunctionDef) and n.name == "_safe_exec_gen"
|
|
89
|
+
and isinstance(getattr(n, "parent", None), ast.Module)
|
|
90
|
+
for n in ast.walk(tree))
|
|
91
|
+
top_names = {n.name for n in tree.body if isinstance(n, ast.FunctionDef)}
|
|
92
|
+
check("_safe_exec_gen 在模块级", "_safe_exec_gen" in top_names)
|
|
93
|
+
# run_channel_b 体量恢复(V21-4 定案:7 行 → 恢复主体)
|
|
94
|
+
rc = next(n for n in ast.walk(tree)
|
|
95
|
+
if isinstance(n, ast.FunctionDef) and n.name == "run_channel_b")
|
|
96
|
+
check("run_channel_b 主体行数 > 60", rc.end_lineno - rc.lineno > 60,
|
|
97
|
+
f"{rc.end_lineno - rc.lineno} 行(被掏空态为 7 行)")
|
|
98
|
+
check("return 后无同块后继(unreachable 扫描同判据)",
|
|
99
|
+
not isinstance(rc.body[-2], (ast.Return, ast.Continue))
|
|
100
|
+
or rc.body[-1] is rc.body[-2])
|
|
101
|
+
# 冒烟:队列空且无 llm_generate → 返回 stats dict(不静默 None)
|
|
102
|
+
sys.path.insert(0, "scripts")
|
|
103
|
+
import importlib.util
|
|
104
|
+
spec = importlib.util.spec_from_file_location(
|
|
105
|
+
"bl_v21", "scripts/bootstrap_loop.py")
|
|
106
|
+
m = importlib.util.module_from_spec(spec)
|
|
107
|
+
try:
|
|
108
|
+
spec.loader.exec_module(m)
|
|
109
|
+
except SystemExit:
|
|
110
|
+
pass
|
|
111
|
+
res = m.run_channel_b(None, max_tasks=2)
|
|
112
|
+
check("队列空场景返回 dict(非静默 None)", isinstance(res, dict),
|
|
113
|
+
repr(res)[:80])
|
|
114
|
+
# V21 报告流程建议 2:全链路冒烟——队列→沙箱→verifier→落盘(此前
|
|
115
|
+
# 全仓无任何测试引用 run_channel_b,主体被掏空 6 个批次无人察觉)
|
|
116
|
+
import json as _json
|
|
117
|
+
smoke = tempfile.mkdtemp(prefix="v21_4_chain_")
|
|
118
|
+
m.STATE = smoke # 隔离状态目录
|
|
119
|
+
good = ("def冒烟排序" if False else
|
|
120
|
+
"def mao_yan_sort(arr):\n"
|
|
121
|
+
" n = len(arr)\n"
|
|
122
|
+
" for i in range(n):\n"
|
|
123
|
+
" for j in range(0, n - i - 1):\n"
|
|
124
|
+
" if arr[j] > arr[j + 1]:\n"
|
|
125
|
+
" arr[j], arr[j + 1] = arr[j + 1], arr[j]\n"
|
|
126
|
+
" return arr\n")
|
|
127
|
+
bad = ("def evil(x):\n"
|
|
128
|
+
" import os\n"
|
|
129
|
+
" return os.system('echo pwned')\n") # 有 def——走沙箱拒路径
|
|
130
|
+
q = {"pending": [
|
|
131
|
+
{"task": "冒烟排序", "code": good,
|
|
132
|
+
"cases": [[[3, 1, 2], [1, 2, 3]]], "status": "new"},
|
|
133
|
+
{"task": "恶意样本", "code": bad,
|
|
134
|
+
"cases": [["x", "x"]], "status": "new"}]}
|
|
135
|
+
with open(os.path.join(smoke, "channel_b_queue.json"), "w",
|
|
136
|
+
encoding="utf-8") as f:
|
|
137
|
+
_json.dump(q, f, ensure_ascii=False)
|
|
138
|
+
res2 = m.run_channel_b(None, max_tasks=5)
|
|
139
|
+
check("全链路 stats:generated=2 passed=1 failed=1",
|
|
140
|
+
res2.get("generated") == 2 and res2.get("passed") == 1
|
|
141
|
+
and res2.get("failed") == 1, repr(res2))
|
|
142
|
+
out_p = os.path.join(smoke, "channel_b_verified_units.json")
|
|
143
|
+
vu = _json.load(open(out_p, encoding="utf-8")) if os.path.exists(out_p) else {}
|
|
144
|
+
check("verified_units 落盘含通过条目", "task:冒烟排序" in vu, repr(vu)[:80])
|
|
145
|
+
q2 = _json.load(open(os.path.join(smoke, "channel_b_queue.json"),
|
|
146
|
+
encoding="utf-8"))
|
|
147
|
+
st = {t["task"]: t["status"] for t in q2["pending"]}
|
|
148
|
+
check("队列回写状态正确(verified/failed)",
|
|
149
|
+
st.get("冒烟排序") == "verified" and st.get("恶意样本") == "failed",
|
|
150
|
+
repr(st))
|
|
151
|
+
rej_p = os.path.join(smoke, "channel_b_drafts", "rejected_log.json")
|
|
152
|
+
rej = _json.load(open(rej_p, encoding="utf-8")) if os.path.exists(rej_p) else []
|
|
153
|
+
check("失败条目进 rejected_log", any(r.get("task") == "恶意样本"
|
|
154
|
+
for r in rej), repr(rej)[:80])
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def v21_5_p23_selfheal():
|
|
158
|
+
print("== V21-5 P2-3 门控残留就地自愈 ==")
|
|
159
|
+
os.environ.pop("MDCG_RETRIEVAL_PIPELINE", None)
|
|
160
|
+
from md_cg.mdcg import MdCG
|
|
161
|
+
root = tempfile.mkdtemp(prefix="v21_5_")
|
|
162
|
+
cg = MdCG(root)
|
|
163
|
+
cg.add("n1", "阿尔法 贝塔 电路 板", "knowledge")
|
|
164
|
+
cg.index["nodes"]["n1"]["big_domain"] = [] # 注入门控残留
|
|
165
|
+
cg.index["nodes"]["n1"]["observation_position"] = []
|
|
166
|
+
cg.search("阿尔法 电路")
|
|
167
|
+
e = cg.index["nodes"]["n1"]
|
|
168
|
+
check("首次查询后残留已剥离(自愈)",
|
|
169
|
+
"big_domain" not in e and "observation_position" not in e,
|
|
170
|
+
str({k: e.get(k) for k in ("big_domain", "observation_position")}))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def v21_6_p24_hashable():
|
|
174
|
+
print("== V21-6 doc_key[0] 可哈希 ==")
|
|
175
|
+
from md_cg import pooling
|
|
176
|
+
doc = ({"path": "knowledge/n1.md"},
|
|
177
|
+
{"id": "n1", "layer": "knowledge"}, "正文")
|
|
178
|
+
k = pooling.doc_key(doc)
|
|
179
|
+
check("doc_key 返回二元组", isinstance(k, tuple) and len(k) == 2)
|
|
180
|
+
try:
|
|
181
|
+
{k[0]: 1}
|
|
182
|
+
hashable = True
|
|
183
|
+
except TypeError:
|
|
184
|
+
hashable = False
|
|
185
|
+
check("node_id(doc_key[0])可哈希", hashable)
|
|
186
|
+
try:
|
|
187
|
+
{k: 1}
|
|
188
|
+
whole = True
|
|
189
|
+
except TypeError:
|
|
190
|
+
whole = False
|
|
191
|
+
check("整键(含 entry dict)确实不可哈希(缺陷形态确认)", whole is False)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def v21_7_redact_deep():
|
|
195
|
+
print("== V21-7 lingshu_cg 返回体递归脱敏 ==")
|
|
196
|
+
sys.path.insert(0, "hive")
|
|
197
|
+
import importlib.util
|
|
198
|
+
spec = importlib.util.spec_from_file_location("hv", "hive/exec.py")
|
|
199
|
+
m = importlib.util.module_from_spec(spec)
|
|
200
|
+
spec.loader.exec_module(m)
|
|
201
|
+
body = {"ok": True, "results": [
|
|
202
|
+
{"content": "联系 13812345678", "frontmatter": {"note": "mail a@b.com"}},
|
|
203
|
+
{"path": "knowledge/x.md"}]}
|
|
204
|
+
out = m._redact_deep(body)
|
|
205
|
+
s = repr(out)
|
|
206
|
+
check("嵌套正文手机号已脱敏", "13812345678" not in s and "已脱敏" in s)
|
|
207
|
+
check("嵌套邮箱已脱敏", "a@b.com" not in s)
|
|
208
|
+
check("结构保持", out["ok"] is True and out["results"][1]["path"]
|
|
209
|
+
== "knowledge/x.md")
|
|
210
|
+
src = open("hive/exec.py", encoding="utf-8").read()
|
|
211
|
+
check("tool_lingshu_cg 返回体已接线", "_redact_deep(out)" in src)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def v21_8_sensitive_family():
|
|
215
|
+
print("== V21-8 敏感名族匹配 ==")
|
|
216
|
+
sys.path.insert(0, "hive")
|
|
217
|
+
import importlib.util
|
|
218
|
+
spec = importlib.util.spec_from_file_location("hv8", "hive/exec.py")
|
|
219
|
+
m = importlib.util.module_from_spec(spec)
|
|
220
|
+
spec.loader.exec_module(m)
|
|
221
|
+
base = tempfile.mkdtemp(prefix="v21_8_")
|
|
222
|
+
leaks = ["id_rsa", "id_rsa.bak", "id_rsa_copy", "id_rsa.old", "id_rsa~",
|
|
223
|
+
"prod.env", "app.env", "creds.json", "aws_credentials",
|
|
224
|
+
"service-account.json"]
|
|
225
|
+
for fn in leaks:
|
|
226
|
+
p = os.path.join(base, fn)
|
|
227
|
+
open(p, "w").close()
|
|
228
|
+
check(f" 拒读:{fn}", m._sensitive_read(p) is not None)
|
|
229
|
+
for fn in ("notes.md", "readme.md", "data.json"):
|
|
230
|
+
p = os.path.join(base, fn)
|
|
231
|
+
open(p, "w").close()
|
|
232
|
+
check(f" 不误伤:{fn}", m._sensitive_read(p) is None)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def main():
|
|
236
|
+
os.chdir(os.path.join(os.path.dirname(__file__), ".."))
|
|
237
|
+
v21_1_editkeyhint()
|
|
238
|
+
v21_2_readable_failclosed()
|
|
239
|
+
v21_3_nodeid_charset()
|
|
240
|
+
v21_4_run_channel_b()
|
|
241
|
+
v21_5_p23_selfheal()
|
|
242
|
+
v21_6_p24_hashable()
|
|
243
|
+
v21_7_redact_deep()
|
|
244
|
+
v21_8_sensitive_family()
|
|
245
|
+
print(f"\ntest_security_audit_v21: {PASS} 通过 / {FAIL} 失败")
|
|
246
|
+
sys.exit(1 if FAIL else 0)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
if __name__ == "__main__":
|
|
250
|
+
main()
|