agent-bypass 2.20.1 → 2.22.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/AGENT_ADDITIVE_CHANGELOG.md +127 -10
- package/README.md +45 -10
- package/SKILL.md +3 -3
- package/SYSTEM_PROMPT.md +17 -0
- package/bin/install.js +336 -5
- package/dsh/AGENTS.md +28 -0
- package/dsh/README.md +83 -0
- package/dsh/build-template.js +310 -0
- package/dsh/persona.md +136 -0
- package/dsh/preset.template.yml +132 -0
- package/package.json +6 -3
- package/tests/test_regressions.py +404 -0
- package/tests/test_tool_inventory.py +1 -1
|
@@ -153,5 +153,409 @@ class JsripSameSiteTests(unittest.TestCase):
|
|
|
153
153
|
self.assertFalse(self.js.same_site("https://example.com", "https://wwwexample.com/a.js"))
|
|
154
154
|
|
|
155
155
|
|
|
156
|
+
class DshInstallerTests(unittest.TestCase):
|
|
157
|
+
"""DeepSeek Harness:技能安装 + Ck 模式预设注入(临时 HOME,离线)。"""
|
|
158
|
+
|
|
159
|
+
DSH_ENV = ("DSH_HOME", "DSH_PROFILE", "DSH_AGENTS_HOME", "DSH_APP_PATH", "DSH_ASAR_PATH")
|
|
160
|
+
|
|
161
|
+
def _node(self, *args, home):
|
|
162
|
+
env = dict(os.environ)
|
|
163
|
+
env["HOME"] = home
|
|
164
|
+
for k in self.DSH_ENV:
|
|
165
|
+
env.pop(k, None)
|
|
166
|
+
return _tmp_run(["node", str(ROOT / "bin" / "install.js"), *args], env=env)
|
|
167
|
+
|
|
168
|
+
def _seed(self, home, patch_text="[]\n"):
|
|
169
|
+
prof = Path(home) / ".dsh" / "profiles" / "desktop"
|
|
170
|
+
prof.mkdir(parents=True, exist_ok=True)
|
|
171
|
+
f = prof / "cordis.patch.yml"
|
|
172
|
+
f.write_text(patch_text, encoding="utf-8")
|
|
173
|
+
return f
|
|
174
|
+
|
|
175
|
+
def test_install_idempotent_and_persona_dollar_preserved(self):
|
|
176
|
+
with tempfile.TemporaryDirectory() as home:
|
|
177
|
+
patch = self._seed(home)
|
|
178
|
+
r1 = self._node("--dsh", home=home)
|
|
179
|
+
self.assertEqual(r1.returncode, 0, r1.stdout + r1.stderr)
|
|
180
|
+
skill = Path(home) / ".agents" / "skills" / "agent-bypass" / "SKILL.md"
|
|
181
|
+
self.assertTrue(skill.exists(), "DSH 技能应安装到 ~/.agents/skills")
|
|
182
|
+
text = patch.read_text(encoding="utf-8")
|
|
183
|
+
self.assertEqual(text.count("agent-bypass:dsh-preset-begin"), 1)
|
|
184
|
+
self.assertEqual(text.count("id: preset-ck"), 1)
|
|
185
|
+
self.assertIn("Ck 万能执行员", text)
|
|
186
|
+
# $$ 不能被 String.replace 的替换串语义吃掉(PHP 变量变量)
|
|
187
|
+
self.assertIn("parse_str / $$", text)
|
|
188
|
+
# 幂等:重复安装不重复追加
|
|
189
|
+
r2 = self._node("--dsh", home=home)
|
|
190
|
+
self.assertEqual(r2.returncode, 0, r2.stdout + r2.stderr)
|
|
191
|
+
text2 = patch.read_text(encoding="utf-8")
|
|
192
|
+
self.assertEqual(text2.count("agent-bypass:dsh-preset-begin"), 1)
|
|
193
|
+
self.assertEqual(text2.count("id: preset-ck"), 1)
|
|
194
|
+
|
|
195
|
+
def test_crlf_preserved_and_uninstall_restores(self):
|
|
196
|
+
with tempfile.TemporaryDirectory() as home:
|
|
197
|
+
patch = self._seed(home, '# note\r\n- id: web\r\n name: "@deepseek-ai/dsh-web"\r\n')
|
|
198
|
+
self.assertEqual(self._node("--dsh", home=home).returncode, 0)
|
|
199
|
+
data = patch.read_bytes()
|
|
200
|
+
self.assertIn(b"agent-bypass:dsh-preset-begin", data)
|
|
201
|
+
self.assertGreater(data.count(b"\r\n"), 100, "CRLF 文件应保持 CRLF")
|
|
202
|
+
r = self._node("--dsh", "--uninstall", home=home)
|
|
203
|
+
self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
|
|
204
|
+
data2 = patch.read_bytes()
|
|
205
|
+
self.assertNotIn(b"agent-bypass:dsh-preset-begin", data2)
|
|
206
|
+
self.assertIn(b"# note", data2)
|
|
207
|
+
self.assertIn(b"id: web", data2)
|
|
208
|
+
self.assertFalse((Path(home) / ".agents" / "skills" / "agent-bypass").exists())
|
|
209
|
+
|
|
210
|
+
def test_comment_only_patch_stays_valid_yaml(self):
|
|
211
|
+
with tempfile.TemporaryDirectory() as home:
|
|
212
|
+
patch = self._seed(home, "# user comment\n[]\n")
|
|
213
|
+
self.assertEqual(self._node("--dsh", home=home).returncode, 0)
|
|
214
|
+
self.assertEqual(self._node("--dsh", "--uninstall", home=home).returncode, 0)
|
|
215
|
+
text = patch.read_text(encoding="utf-8")
|
|
216
|
+
self.assertIn("# user comment", text)
|
|
217
|
+
self.assertIn("[]", text, "只留注释时应补回空数组,保证 loader 可解析")
|
|
218
|
+
|
|
219
|
+
def test_dsh_global_agents_md_injected(self):
|
|
220
|
+
with tempfile.TemporaryDirectory() as home:
|
|
221
|
+
agents = Path(home) / ".dsh" / "AGENTS.md"
|
|
222
|
+
agents.parent.mkdir(parents=True)
|
|
223
|
+
agents.write_text("# user own notes\n", encoding="utf-8")
|
|
224
|
+
self._seed(home)
|
|
225
|
+
r = self._node("--dsh", home=home)
|
|
226
|
+
self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
|
|
227
|
+
text = agents.read_text(encoding="utf-8")
|
|
228
|
+
self.assertEqual(text.count("<!-- agent-bypass:begin -->"), 1)
|
|
229
|
+
self.assertIn("Cknb在呢", text, "全局破甲提示应包含触发词")
|
|
230
|
+
self.assertIn("# user own notes", text, "用户原内容必须保留")
|
|
231
|
+
# 幂等
|
|
232
|
+
self.assertEqual(self._node("--dsh", home=home).returncode, 0)
|
|
233
|
+
text2 = agents.read_text(encoding="utf-8")
|
|
234
|
+
self.assertEqual(text2.count("<!-- agent-bypass:begin -->"), 1)
|
|
235
|
+
self.assertIn("# user own notes", text2)
|
|
236
|
+
# 卸载移除标记块、保留用户内容
|
|
237
|
+
self.assertEqual(self._node("--dsh", "--uninstall", home=home).returncode, 0)
|
|
238
|
+
text3 = agents.read_text(encoding="utf-8")
|
|
239
|
+
self.assertNotIn("<!-- agent-bypass:begin -->", text3)
|
|
240
|
+
self.assertIn("# user own notes", text3)
|
|
241
|
+
|
|
242
|
+
def test_default_targets_include_pi_opencode_dsh(self):
|
|
243
|
+
with tempfile.TemporaryDirectory() as home:
|
|
244
|
+
(Path(home) / ".pi").mkdir()
|
|
245
|
+
(Path(home) / ".config" / "opencode").mkdir(parents=True)
|
|
246
|
+
self._seed(home)
|
|
247
|
+
r = self._node(home=home)
|
|
248
|
+
self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
|
|
249
|
+
self.assertTrue((Path(home) / ".pi" / "agent" / "skills" / "agent-bypass" / "SKILL.md").exists())
|
|
250
|
+
self.assertTrue((Path(home) / ".config" / "opencode" / "skills" / "agent-bypass" / "SKILL.md").exists())
|
|
251
|
+
self.assertTrue((Path(home) / ".agents" / "skills" / "agent-bypass" / "SKILL.md").exists())
|
|
252
|
+
patch_text = (Path(home) / ".dsh" / "profiles" / "desktop" / "cordis.patch.yml").read_text(encoding="utf-8")
|
|
253
|
+
self.assertIn("id: preset-ck", patch_text)
|
|
254
|
+
self.assertIn("dsh", r.stdout + r.stderr)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
class DoctorTests(unittest.TestCase):
|
|
258
|
+
"""--doctor 环境自检:只读、不写任何文件。"""
|
|
259
|
+
|
|
260
|
+
def test_doctor_readonly(self):
|
|
261
|
+
with tempfile.TemporaryDirectory() as home:
|
|
262
|
+
env = dict(os.environ)
|
|
263
|
+
env["HOME"] = home
|
|
264
|
+
for k in ("DSH_HOME", "DSH_PROFILE", "DSH_AGENTS_HOME", "DSH_APP_PATH", "DSH_ASAR_PATH"):
|
|
265
|
+
env.pop(k, None)
|
|
266
|
+
r = _tmp_run(["node", str(ROOT / "bin" / "install.js"), "--doctor"], env=env)
|
|
267
|
+
self.assertEqual(r.returncode, 0, r.stdout + r.stderr)
|
|
268
|
+
out = r.stdout + r.stderr
|
|
269
|
+
self.assertIn("环境自检", out)
|
|
270
|
+
self.assertIn("平台", out)
|
|
271
|
+
self.assertIn("包管理器", out)
|
|
272
|
+
# 只读:临时 HOME 不应产生任何文件
|
|
273
|
+
self.assertEqual(list(Path(home).rglob("*")), [], "doctor 不应写文件")
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class NetDnsScanOriginTests(unittest.TestCase):
|
|
277
|
+
"""fake-IP/TUN:netdns 判定 + portscan 解析守卫 + origin_trace 解析(全部离线)。"""
|
|
278
|
+
|
|
279
|
+
@classmethod
|
|
280
|
+
def setUpClass(cls):
|
|
281
|
+
sys.path.insert(0, str(SCRIPTS))
|
|
282
|
+
import netdns
|
|
283
|
+
import portscan
|
|
284
|
+
import origin_trace
|
|
285
|
+
cls.nd = netdns
|
|
286
|
+
cls.ps = portscan
|
|
287
|
+
cls.ot = origin_trace
|
|
288
|
+
|
|
289
|
+
def test_fake_ip_ranges(self):
|
|
290
|
+
for ip in ("198.18.0.5", "198.19.255.1", "240.1.2.3", "198.51.100.7", "203.0.113.9"):
|
|
291
|
+
self.assertTrue(self.nd.is_fake_ip(ip), ip)
|
|
292
|
+
for ip in ("1.1.1.1", "8.8.8.8", "192.168.1.1", "10.0.0.1"):
|
|
293
|
+
self.assertFalse(self.nd.is_fake_ip(ip), ip)
|
|
294
|
+
|
|
295
|
+
def test_parse_dns_json(self):
|
|
296
|
+
sample = ('{"Answer":[{"type":1,"data":"93.184.216.34"},'
|
|
297
|
+
'{"type":28,"data":"2606:2800::1"},{"type":1,"data":"198.18.0.1"}]}')
|
|
298
|
+
self.assertEqual(self.nd._parse_dns_json(sample, "A"), ["93.184.216.34", "198.18.0.1"])
|
|
299
|
+
self.assertEqual(self.nd._parse_dns_json(sample, "AAAA"), ["2606:2800::1"])
|
|
300
|
+
self.assertEqual(self.nd._parse_dns_json("not-json", "A"), [])
|
|
301
|
+
|
|
302
|
+
def test_portscan_split_host_fake_guard(self):
|
|
303
|
+
self.assertEqual(self.ps.split_host("https://example.com:8443/x"), ("example.com", 8443))
|
|
304
|
+
self.assertEqual(self.ps.split_host("1.2.3.4"), ("1.2.3.4", None))
|
|
305
|
+
self.assertIsNone(self.ps.split_host("bad host")[0])
|
|
306
|
+
self.assertTrue(self.ps.is_fake_ip("198.18.1.2"))
|
|
307
|
+
ip, src, fake = self.ps.resolve_target("198.18.1.2", mode="auto")
|
|
308
|
+
self.assertEqual((ip, src), ("198.18.1.2", "ip"))
|
|
309
|
+
self.assertEqual(fake, [])
|
|
310
|
+
|
|
311
|
+
def test_origin_trace_parsers(self):
|
|
312
|
+
ct = '[{"name_value":"a.example.com\\n*.example.com\\nother.test"}]'
|
|
313
|
+
self.assertEqual(self.ot.parse_crtsh(ct, "example.com"), ["a.example.com", "example.com"])
|
|
314
|
+
self.assertEqual(self.ot.extract_title("<title> Hi There </title>"), "Hi There")
|
|
315
|
+
self.assertEqual(self.ot.cdn_markers({"cf-ray": "x", "server": "nginx"}), ["cf-ray"])
|
|
316
|
+
base = {"title": "X", "server": "nginx", "status": "HTTP/1.1 200 OK", "cdn": ["cf-ray"]}
|
|
317
|
+
cand = {"title": "X", "server": "nginx", "status": "HTTP/1.1 200 OK", "cdn": [], "cert_domain_ok": True}
|
|
318
|
+
score, reasons = self.ot.score_candidate(base, cand)
|
|
319
|
+
self.assertGreaterEqual(score, 5)
|
|
320
|
+
self.assertIn("title", reasons)
|
|
321
|
+
self.assertLess(self.ot.score_candidate(base, {"cdn": ["cf-ray"]})[0], score)
|
|
322
|
+
|
|
323
|
+
def test_doh_shell_syntax(self):
|
|
324
|
+
sh = shutil.which("sh")
|
|
325
|
+
if not sh:
|
|
326
|
+
self.skipTest("no sh")
|
|
327
|
+
r = _tmp_run([sh, "-n", str(SCRIPTS / "doh_resolve.sh")])
|
|
328
|
+
self.assertEqual(r.returncode, 0, r.stderr)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class SkillCompactTests(unittest.TestCase):
|
|
332
|
+
"""SKILL.md 体积/去重回归:防止再次膨胀。"""
|
|
333
|
+
|
|
334
|
+
def test_skill_structure_and_size(self):
|
|
335
|
+
skill = (ROOT / "SKILL.md").read_text(encoding="utf-8")
|
|
336
|
+
self.assertEqual(skill.count("<!-- module:"), 35)
|
|
337
|
+
self.assertNotIn("## 修正对照", skill, "重复的修正对照表已移除,勿再加回")
|
|
338
|
+
self.assertLess(len(skill.encode("utf-8")), 132000, "SKILL.md 体积超限(保持精简,长内容放 references/)")
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class HangProtectionTests(unittest.TestCase):
|
|
342
|
+
"""防卡死:DNS 硬超时、扫描总时限、溯源预算/同名 CDN 扣分(全部离线)。"""
|
|
343
|
+
|
|
344
|
+
@classmethod
|
|
345
|
+
def setUpClass(cls):
|
|
346
|
+
sys.path.insert(0, str(SCRIPTS))
|
|
347
|
+
import netdns, portscan, origin_trace
|
|
348
|
+
cls.nd = netdns
|
|
349
|
+
cls.ps = portscan
|
|
350
|
+
cls.ot = origin_trace
|
|
351
|
+
|
|
352
|
+
def test_system_resolve_hard_timeout(self):
|
|
353
|
+
import time as _t
|
|
354
|
+
orig = self.nd._system_resolve_impl
|
|
355
|
+
self.nd._system_resolve_impl = lambda name: (_t.sleep(3), ["1.2.3.4"])[1]
|
|
356
|
+
t0 = _t.monotonic()
|
|
357
|
+
try:
|
|
358
|
+
ips = self.nd.system_resolve("example.com", timeout=0.2)
|
|
359
|
+
finally:
|
|
360
|
+
self.nd._system_resolve_impl = orig
|
|
361
|
+
self.assertEqual(ips, [])
|
|
362
|
+
self.assertLess(_t.monotonic() - t0, 1.5, "system_resolve 必须有硬超时,不能挂住")
|
|
363
|
+
|
|
364
|
+
def test_portscan_total_deadline(self):
|
|
365
|
+
import asyncio, time as _t
|
|
366
|
+
found, timed_out = asyncio.run(
|
|
367
|
+
self.ps.scan("127.0.0.1", [9, 10], conc=1, timeout=0.05,
|
|
368
|
+
deadline=_t.monotonic() - 1))
|
|
369
|
+
self.assertEqual(found, [])
|
|
370
|
+
self.assertTrue(timed_out)
|
|
371
|
+
|
|
372
|
+
def test_origin_same_cdn_penalty(self):
|
|
373
|
+
base = {"title": "X", "server": "nginx", "status": "HTTP/1.1 200 OK", "cdn": ["cf-ray"]}
|
|
374
|
+
cand_cdn = {"title": "X", "server": "nginx", "status": "HTTP/1.1 200 OK", "cdn": ["cf-ray"]}
|
|
375
|
+
cand_origin = {"title": "X", "server": "nginx", "status": "HTTP/1.1 200 OK", "cdn": []}
|
|
376
|
+
s_cdn, r_cdn = self.ot.score_candidate(base, cand_cdn)
|
|
377
|
+
s_org, r_org = self.ot.score_candidate(base, cand_origin)
|
|
378
|
+
self.assertIn("same-cdn", r_cdn)
|
|
379
|
+
self.assertLess(s_cdn, s_org)
|
|
380
|
+
|
|
381
|
+
def test_passive_ct_parsers(self):
|
|
382
|
+
cs = '[{"dns_names":["www.example.com","*.example.com","x.test"]}]'
|
|
383
|
+
self.assertEqual(self.ot.parse_certspotter(cs, "example.com"), ["www.example.com", "example.com"])
|
|
384
|
+
otx = ('{"passive_dns":[{"hostname":"mail.example.com","address":"1.2.3.4"},'
|
|
385
|
+
'{"hostname":"x.test","address":"9.9.9.9"},'
|
|
386
|
+
'{"hostname":"cdn.example.com","address":"198.18.0.9"}]}')
|
|
387
|
+
hosts, ips = self.ot.parse_otx(otx, "example.com")
|
|
388
|
+
self.assertIn("mail.example.com", hosts)
|
|
389
|
+
self.assertEqual(ips, ["1.2.3.4"], "被动 DNS 的 fake-IP 应被过滤")
|
|
390
|
+
hosts, ips = self.ot.parse_hackertarget("vpn.example.com,5.6.7.8\nAPI count exceeded", "example.com")
|
|
391
|
+
self.assertEqual(hosts, ["vpn.example.com"])
|
|
392
|
+
self.assertEqual(ips, ["5.6.7.8"])
|
|
393
|
+
self.assertEqual(self.ot.parse_anubis('["a.example.com","b.test"]', "example.com"), ["a.example.com"])
|
|
394
|
+
self.assertEqual(self.ot.parse_rapiddns("<td>oa.example.com</td><td>x.test</td>", "example.com"),
|
|
395
|
+
["oa.example.com"])
|
|
396
|
+
for src in ("crt.sh", "certspotter", "otx", "hackertarget", "anubis", "rapiddns"):
|
|
397
|
+
self.assertIn(src, self.ot.CT_SOURCES)
|
|
398
|
+
|
|
399
|
+
def test_ct_multi_source_fallback(self):
|
|
400
|
+
def fake(name, domain, timeout):
|
|
401
|
+
if name == "crt.sh":
|
|
402
|
+
raise RuntimeError("502 Bad Gateway")
|
|
403
|
+
return (["s1.example.com", "s2.example.com"], ["1.2.3.4"])
|
|
404
|
+
orig = self.ot._ct_fetch
|
|
405
|
+
self.ot._ct_fetch = fake
|
|
406
|
+
try:
|
|
407
|
+
hosts, ips, status = self.ot.fetch_passive_names(
|
|
408
|
+
"example.com", sources=["crt.sh", "otx"], budget=5)
|
|
409
|
+
finally:
|
|
410
|
+
self.ot._ct_fetch = orig
|
|
411
|
+
self.assertEqual(hosts, ["s1.example.com", "s2.example.com"])
|
|
412
|
+
self.assertEqual(ips, ["1.2.3.4"])
|
|
413
|
+
self.assertIn("失败", status["crt.sh"], "单源 502 应被记录并跳过")
|
|
414
|
+
|
|
415
|
+
def test_origin_bad_flag_rejected(self):
|
|
416
|
+
r = _tmp_run([sys.executable, str(SCRIPTS / "origin_trace.py"), "--bogus"])
|
|
417
|
+
self.assertEqual(r.returncode, 2)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
class PivotQueryTests(unittest.TestCase):
|
|
421
|
+
"""搜索引警扩面:murmur3/favicon hash、7 引擎查询生成、CLI JSON(离线)。"""
|
|
422
|
+
|
|
423
|
+
@classmethod
|
|
424
|
+
def setUpClass(cls):
|
|
425
|
+
sys.path.insert(0, str(SCRIPTS))
|
|
426
|
+
import pivot_queries
|
|
427
|
+
cls.pq = pivot_queries
|
|
428
|
+
|
|
429
|
+
def test_murmur3_known_vectors(self):
|
|
430
|
+
self.assertEqual(self.pq.murmur3_32(b""), 0)
|
|
431
|
+
self.assertEqual(self.pq.murmur3_32(b"foo"), -156908512)
|
|
432
|
+
self.assertEqual(self.pq.murmur3_32(b"hello"), 613153351)
|
|
433
|
+
|
|
434
|
+
def test_favicon_hash_wiring(self):
|
|
435
|
+
import base64
|
|
436
|
+
data = b"\x89PNG\r\n\x1a\nfake-favicon-bytes"
|
|
437
|
+
hb, hr = self.pq.favicon_hashes(data)
|
|
438
|
+
self.assertEqual(hb, self.pq.murmur3_32(base64.b64encode(data)))
|
|
439
|
+
self.assertEqual(hr, self.pq.murmur3_32(data))
|
|
440
|
+
|
|
441
|
+
def test_build_queries_across_engines(self):
|
|
442
|
+
info = {"domain": "t.com", "ip": "1.2.3.4", "cidr": "1.2.3.0/24", "cert": "t.com",
|
|
443
|
+
"title": "Admin", "body": "uniq", "asn": "AS13335", "port": 8443, "icon_hash": -123}
|
|
444
|
+
q = self.pq.build_queries(info)
|
|
445
|
+
for eng in ("fofa", "shodan", "quake", "zoomeye", "hunter", "censys", "netlas"):
|
|
446
|
+
self.assertIn(eng, q)
|
|
447
|
+
self.assertIn('ip="1.2.3.4"', q["fofa"])
|
|
448
|
+
self.assertIn('icon_hash="-123"', q["fofa"])
|
|
449
|
+
self.assertIn("http.favicon.hash:-123", q["shodan"])
|
|
450
|
+
self.assertIn("net:1.2.3.0/24", q["shodan"])
|
|
451
|
+
self.assertIn('cert:"t.com"', q["quake"])
|
|
452
|
+
self.assertIn("cidr:1.2.3.0/24", q["zoomeye"])
|
|
453
|
+
self.assertIn('web.title="Admin"', q["hunter"])
|
|
454
|
+
|
|
455
|
+
def test_cli_json_and_favicon_file(self):
|
|
456
|
+
with tempfile.TemporaryDirectory() as d:
|
|
457
|
+
fav = Path(d) / "favicon.ico"
|
|
458
|
+
data = b"\x00\x01\x02fake"
|
|
459
|
+
fav.write_bytes(data)
|
|
460
|
+
r = _tmp_run([sys.executable, str(SCRIPTS / "pivot_queries.py"),
|
|
461
|
+
"--domain", "t.com", "--ip", "1.2.3.4",
|
|
462
|
+
"--favicon", str(fav), "--json"])
|
|
463
|
+
self.assertEqual(r.returncode, 0, r.stderr)
|
|
464
|
+
payload = json.loads(r.stdout)
|
|
465
|
+
self.assertEqual(payload["input"]["icon_hash"], self.pq.favicon_hashes(data)[0])
|
|
466
|
+
self.assertTrue(payload["queries"]["fofa"])
|
|
467
|
+
self.assertTrue(payload["scan_templates"])
|
|
468
|
+
|
|
469
|
+
def test_cli_rejects_empty_input(self):
|
|
470
|
+
r = _tmp_run([sys.executable, str(SCRIPTS / "pivot_queries.py")])
|
|
471
|
+
self.assertEqual(r.returncode, 2)
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class EnvChainTests(unittest.TestCase):
|
|
475
|
+
"""多路径攻击:.env 解析/分类/脱敏/链生成(离线)。"""
|
|
476
|
+
|
|
477
|
+
SAMPLE = "\n".join([
|
|
478
|
+
"APP_ENV=production",
|
|
479
|
+
"DB_HOST=10.0.0.5",
|
|
480
|
+
"DB_PORT=3306",
|
|
481
|
+
"DB_USER=appuser",
|
|
482
|
+
"DB_PASSWORD=SuperSecret1",
|
|
483
|
+
"REDIS_HOST=10.0.0.6",
|
|
484
|
+
"REDIS_PASSWORD=redispass",
|
|
485
|
+
"AWS_ACCESS_KEY_ID=AKIAFAKE123",
|
|
486
|
+
"AWS_SECRET_ACCESS_KEY=fakesecret",
|
|
487
|
+
"JWT_SECRET=jwtsecret123",
|
|
488
|
+
"SMTP_HOST=smtp.example.com",
|
|
489
|
+
"SSH_USER=deploy",
|
|
490
|
+
]) + "\n"
|
|
491
|
+
|
|
492
|
+
@classmethod
|
|
493
|
+
def setUpClass(cls):
|
|
494
|
+
sys.path.insert(0, str(SCRIPTS))
|
|
495
|
+
import env_chain
|
|
496
|
+
cls.ec = env_chain
|
|
497
|
+
|
|
498
|
+
def test_parse_env_and_classify(self):
|
|
499
|
+
env = self.ec.parse_env('export DB_HOST=1.2.3.4\nDB_USER="u"\n# comment\nBADLINE\n')
|
|
500
|
+
self.assertEqual(env["DB_HOST"], "1.2.3.4")
|
|
501
|
+
self.assertEqual(env["DB_USER"], "u")
|
|
502
|
+
self.assertEqual(self.ec.classify("DB_PASSWORD"), "mysql")
|
|
503
|
+
self.assertEqual(self.ec.classify("REDIS_URL"), "db_url")
|
|
504
|
+
self.assertEqual(self.ec.classify("AWS_SECRET_ACCESS_KEY"), "aws")
|
|
505
|
+
self.assertEqual(self.ec.classify("JWT_SECRET"), "secret")
|
|
506
|
+
self.assertEqual(self.ec.classify("SMTP_PASSWORD"), "smtp")
|
|
507
|
+
self.assertEqual(self.ec.classify("SSH_PRIVATE_KEY"), "ssh")
|
|
508
|
+
|
|
509
|
+
def test_extract_and_chains_masking(self):
|
|
510
|
+
env = self.ec.parse_env(self.SAMPLE)
|
|
511
|
+
data = self.ec.extract(env)
|
|
512
|
+
services = {s["service"] for s in data["services"]}
|
|
513
|
+
self.assertIn("mysql", services)
|
|
514
|
+
self.assertIn("redis", services)
|
|
515
|
+
chains = self.ec.build_chains(data)
|
|
516
|
+
text = json.dumps(chains, ensure_ascii=False)
|
|
517
|
+
self.assertNotIn("SuperSecret1", text, "默认必须脱敏")
|
|
518
|
+
self.assertNotIn("redispass", text)
|
|
519
|
+
revealed = json.dumps(self.ec.build_chains(data, reveal=True), ensure_ascii=False)
|
|
520
|
+
self.assertIn("SuperSecret1", revealed)
|
|
521
|
+
self.assertIn(".env → MySQL", revealed)
|
|
522
|
+
|
|
523
|
+
def test_cli_json_roundtrip(self):
|
|
524
|
+
with tempfile.TemporaryDirectory() as d:
|
|
525
|
+
f = Path(d) / ".env"
|
|
526
|
+
f.write_text(self.SAMPLE, encoding="utf-8")
|
|
527
|
+
r = _tmp_run([sys.executable, str(SCRIPTS / "env_chain.py"), str(f), "--json"])
|
|
528
|
+
self.assertEqual(r.returncode, 0, r.stderr)
|
|
529
|
+
payload = json.loads(r.stdout)
|
|
530
|
+
self.assertIn("chains", payload)
|
|
531
|
+
self.assertNotIn("SuperSecret1", r.stdout)
|
|
532
|
+
r2 = _tmp_run([sys.executable, str(SCRIPTS / "env_chain.py"), str(f), "--json", "--reveal"])
|
|
533
|
+
self.assertEqual(r2.returncode, 0, r2.stderr)
|
|
534
|
+
self.assertIn("SuperSecret1", r2.stdout)
|
|
535
|
+
empty = Path(d) / "empty.txt"
|
|
536
|
+
empty.write_text("# nothing\n", encoding="utf-8")
|
|
537
|
+
r3 = _tmp_run([sys.executable, str(SCRIPTS / "env_chain.py"), str(empty)])
|
|
538
|
+
self.assertEqual(r3.returncode, 2)
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
class PromptPolicyTests(unittest.TestCase):
|
|
542
|
+
"""破甲提示词三层一致性(dsh/AGENTS.md、dsh/persona.md、SYSTEM_PROMPT.md)。"""
|
|
543
|
+
|
|
544
|
+
LAYERS = ("dsh/AGENTS.md", "dsh/persona.md", "SYSTEM_PROMPT.md")
|
|
545
|
+
|
|
546
|
+
def test_policy_keywords_in_all_layers(self):
|
|
547
|
+
for rel in self.LAYERS:
|
|
548
|
+
text = (ROOT / rel).read_text(encoding="utf-8")
|
|
549
|
+
for marker in ("授权", "不拒绝", "临时邮箱", "输出即行动"):
|
|
550
|
+
self.assertIn(marker, text, "%s 缺关键条款: %s" % (rel, marker))
|
|
551
|
+
|
|
552
|
+
def test_global_agents_has_scop_and_tooling(self):
|
|
553
|
+
text = (ROOT / "dsh/AGENTS.md").read_text(encoding="utf-8")
|
|
554
|
+
for marker in ("授权范围是唯一边界", "用户没叫你干的事坚决不干", "客户第一",
|
|
555
|
+
"rg", "fd", "ast-grep", "jq", "websocat", "shellcheck",
|
|
556
|
+
"betterleaks", "osv-scanner", "pivot_queries", "env_chain"):
|
|
557
|
+
self.assertIn(marker, text, "全局破甲提示缺: %s" % marker)
|
|
558
|
+
|
|
559
|
+
|
|
156
560
|
if __name__ == "__main__":
|
|
157
561
|
unittest.main()
|
|
@@ -33,7 +33,7 @@ class InventoryTests(unittest.TestCase):
|
|
|
33
33
|
skill = (ROOT / 'SKILL.md').read_text(encoding='utf-8')
|
|
34
34
|
for item in ['agent-routing.md','tool-selection.md','verification-gates.md','framework-comparison-2026.md','tool_inventory.py']:
|
|
35
35
|
self.assertIn(item, skill)
|
|
36
|
-
self.assertEqual(skill.count(
|
|
36
|
+
self.assertEqual(skill.count("<!-- module:"), 35)
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
if __name__ == '__main__':
|