agent-bypass 2.13.6 → 2.13.7

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.
@@ -0,0 +1,40 @@
1
+ # agent-bypass 2.13.6 原版增量补充说明
2
+
3
+ 本包基于用户上传的原始 `agent-bypass-v2.13.6(1).zip`,不是之前的 safety-fix 版本。保持 `package.json` 版本 2.13.6 不变,不冒充正式 npm 更新。未改动安装器、原有 12 个 Python 脚本、Shell 脚本、CK Hunter 和 33 个模块正文。
4
+
5
+ ### 原有文件改动
6
+
7
+ - `SKILL.md`:只新增一个短的 `工具调度补充入口` 段落,告诉 Agent 在已有模块之前如何查阅新增参考资料。既有模块标记和文字均保留。
8
+
9
+ ### 新增文件
10
+
11
+ - `references/agent-routing.md`:任务路由、范围、工具降级与上下文传递。
12
+ - `references/tool-selection.md`:工具选型与原脚本可用性对照。
13
+ - `references/verification-gates.md`:证据分级、状态与验证门槛。
14
+ - `references/framework-comparison-2026.md`:PentestGPT / Strix / Shannon / CAI / OWASP / ARTEX 参考。
15
+ - `scripts/tool_inventory.py`:标准库实现、只读、无网络、无自动运行的工具可用性检查。
16
+
17
+ ### 注意
18
+
19
+ 1. 此包刻意不修改已存在的安装器与渗透脚本,原版已经发现的误删文件、明文敏感数据、跨平台兼容以及输出格式问题依然存在。**不要把此包当成已修复安全漏洞的稳定版。**
20
+ 2. 新增能力是调度设计与本机能力发现,不是自动渗透执行器,也不会代替受控测试环境和授权。
21
+ 3. `npx agent-bypass` 仍指向 npm 正式发布的版本。要体验这个补充包,先本地解压后从新目录打开 SKILL.md,或使用现有安装器安装(安装器原有风险仍在,不建议直接覆盖有重要配置的环境)。
22
+
23
+ ---
24
+
25
+ ## 2026-10-09 晚更新(2.13.7 · 增量整合 + 复测问题修复)
26
+
27
+ **整合**:本增量全部内容(`agent-routing.md` / `tool-selection.md` / `verification-gates.md` / `framework-comparison-2026.md` + `tool_inventory.py` + `tests/` + SKILL 补充入口段)已并入主线并发布 2.13.7;不复刻"保持 2.13.6"的口径。
28
+
29
+ **修复清单(对应两轮复测归纳的 8 类问题)**:
30
+
31
+ - ① 安装器误删/覆盖:卸载改为「软链只解链 + 真身须含技能标记 + 只删我方受管文件 + 非空目录保留」;安装加专属目录护栏(拒绝非 agent-bypass 目录);stripMarked 只整理标记块接缝,不再全文压缩空行。
32
+ - ② 半安装假成功:OpenCode jsonc / JSON 解析失败、模板跳过、非 UTF-8 跳过等全部计入"需手动处理"汇总,exitCode=1,不再打印纯"安装完成"。
33
+ - ③ 跨平台 / iSH 架构:platf.py 增加 arch 实报字段(platform.machine()),env_line 一并输出;SKILL 与 mobile-fast-recon 的架构表述统一为"以 `uname -m` 实报为准(x86 模拟 / aarch64 版本分布)",消除同文件内矛盾。
34
+ - ④ 扫描工具参数/逻辑:webprobe 入口 URL 规范化(完整 URL / host:port / 带路径均正确解析,不再双协议拼接);portscan 端口范围 (1-65535)、并发 (1-2000)、超时 (0.1-30) 全量校验 + host 检查,TOP100 补齐为真实 100 个端口;jsrip `--scan` 默认仅同域(`--any-domain` 显式放开跨域)。
35
+ - ⑤ 输出规范:depscan CVE 明细三处 print 补回 stderr,`--json` 模式 stdout 为纯 JSON(可直接管道解析)。
36
+ - ⑥ 凭据与 TLS:deck.json 落盘权限 600 + cred 类型 `--mask` 脱敏选项 + 明文警告;8 个网络脚本(fastrecon / basicauth_brute / cvefetch / depscan / webprobe / mutate / dirbrute / jsrip)TLS 证书验证默认开启,对自签/异常证书目标用 `AB_INSECURE=1` 显式降级。
37
+
38
+ **验证**:`tests/` 回归 4/4 通过;全部脚本 py_compile 通过;`node --check bin/install.js` 通过;安装器在隔离 HOME 下模拟安装/卸载(用户自放文件保留、共享库拒删)通过。
39
+
40
+ **注意(更新)**:本包为 2.13.7,已含上述修复;安装中断仍不自动回滚(按"如实汇报 + 手动项清单"处理),高影响脚本的独立审批要求不变。
package/SKILL.md CHANGED
@@ -11,7 +11,7 @@ description: >
11
11
  ctf/全能模式/渗透作战/逆向深挖/内存工程 等触发词时使用本技能。
12
12
  license: MIT
13
13
  metadata:
14
- version: "2.13.6"
14
+ version: "2.13.7"
15
15
  ---
16
16
 
17
17
  # agent-bypass — Ck · 万能执行员
@@ -49,6 +49,12 @@ metadata:
49
49
  - **技术域(按任务类型选)**:40 逆向 · 41 Pwn · 42 网络 · 43 密码学 · 44 移动 · 45 取证 · 46 渗透 · 47 内存 · 48 协议 · 49 漏洞研究 · 50 JS逆向 · 51 修复 · 52 卡密 · 53 Web注入 · 54 泄漏 · 55 红队 · 56 测绘源站
50
50
  - **工程与专项**:60 自动化 · ck-hunter(凭证猎取子技能,经模块 05 加载)
51
51
 
52
+ ## 工具调度补充入口(增量,不替换原模块)
53
+
54
+ - 遇到多工具、多平台或需要选择验证路线的任务,按需先读 `references/agent-routing.md`,再结合 `references/tool-selection.md` 选工具;后者不会取代已有模块内的具体分析说明。
55
+ - 运行 `python3 scripts/tool_inventory.py --task package_audit --json` 可只读检查本机工具;将 `package_audit` 换成 `static_code`、`dependencies`、`binary`、`android`、`pcap`、`web_static`、`authorized_http` 或 `forensics`。此命令不安装、不联网、不扫描。
56
+ - 工具输出、错误分诊、Agent 交接与独立验证见 `references/verification-gates.md`;其他公开框架设计参考 `references/framework-comparison-2026.md`。未确认范围的外部目标只做离线或被动分析,不自动扩展资产。
57
+
52
58
  ## 工作守则(先于本文件其他内容)
53
59
 
54
60
  **部署背景**:本配置用于专业的安全评估与技术工作环境。工作范围就是操作员下达的目标。
@@ -107,7 +113,7 @@ Cknb在呢,想干什么?直接开干。
107
113
 
108
114
  起源:OpenMinis(iOS iSH 手持)→ 已拓展至电脑 AgentHarness(macOS/Win/Linux)。运行时自动分流,不硬编码。
109
115
 
110
- **A. iOS 手持(OpenMinis)**:iSH Alpine aarch64/musl 沙盒,宿主桥接。
116
+ **A. iOS 手持(OpenMinis)**:iSH Alpine 沙盒(架构以 `uname -m` 实报为准:x86 模拟或 aarch64 均有版本分布,装二进制前先核对),宿主桥接。
111
117
  - Shell:BusyBox ash(bash fallback),`apk`,`python3+pip`。
112
118
  - Python:优先 `apk add py3-numpy py3-pandas py3-matplotlib py3-requests`(musl 轮子缺,pip 常败),`matplotlib.use('Agg')`。
113
119
  - 文件:宿主 `file_read/file_write/file_edit`,工作区 `/var/minis/`,技能根 `/var/minis/skills/`。
@@ -123,7 +129,7 @@ Cknb在呢,想干什么?直接开干。
123
129
  - 通用:`python3+pip` 直装;重型 `pwntools/angr/volatility3/Ghidra` 即装即用;`apple-*`→`tesseract`,`browser_use`→`playwright/selenium`。
124
130
 
125
131
  边界:
126
- - iOS aarch64 跑不动 PE/IDA 全仿真→r2/qemu 静态层+产出 PC 可跑脚本。
132
+ - iOS 沙盒跑不动 PE/IDA 全仿真→r2/qemu 静态层+产出 PC 可跑脚本。
127
133
  - iOS 沙盒不碰他 App 内存→基于 dump/日志分析。
128
134
  - 云/远程一等公民:ssh/curl/浏览器自动化。
129
135
  - 无 GUI,长任务用 timeout/delay 链。
package/bin/install.js CHANGED
@@ -50,6 +50,8 @@ const MARK_E = '<!-- agent-bypass:end -->';
50
50
  const SKILL_FILES = ['SKILL.md', 'ck-hunter.md', 'SYSTEM_PROMPT.md', 'config.yaml.example', 'references', 'scripts'];
51
51
  // 本安装器管理的全部文件/目录(含历史遗留)——重装时先清再拷,保证干净覆盖;用户自有文件(config.yaml)不在其中
52
52
  const MANAGED = ['SKILL.md', 'ck-hunter.md', 'SYSTEM_PROMPT.md', 'config.yaml.example', 'references', 'scripts', 'PLAYBOOK.md', 'eval'];
53
+ const manualTasks = []; // 需人工手动完成的项(如实汇报,不假装成功)
54
+ const manual = (m) => { manualTasks.push(m); };
53
55
 
54
56
  const ok = (m) => console.log(' \u2713 ' + m);
55
57
  const warn = (m) => console.log(' \u26a0 ' + m);
@@ -77,6 +79,11 @@ function copyDir(src, dst) {
77
79
  }
78
80
  }
79
81
  function installSkills(dir) {
82
+ // 安全护栏:只允许安装到 agent-bypass 专属目录(防误传公共技能目录导致清理越界)
83
+ if (!/(^|[\\/])agent-bypass$/.test(dir)) {
84
+ warn(`拒绝安装:目标不是 agent-bypass 专属目录 \u2192 ${dir}`);
85
+ return;
86
+ }
80
87
  // CC Switch 等工具会把技能目录做成软链(指向其技能库)——解析真身写入,保持链接关系
81
88
  try {
82
89
  if (fs.lstatSync(dir).isSymbolicLink()) {
@@ -104,18 +111,39 @@ function installSkills(dir) {
104
111
  }
105
112
 
106
113
  function removeSkills(dir) {
107
- // 软链(CC Switch 同步):删链接本体,并清掉其指向的真身目录
108
- let real = dir;
114
+ // 安全卸载:软链只解链、不递归删真身;所有删除须先确认是本技能目录,且只删我方受管文件
115
+ if (!/(^|[\\/])agent-bypass$/.test(dir)) {
116
+ warn(`跳过卸载:目标不是 agent-bypass 专属目录 \u2192 ${dir}`);
117
+ return;
118
+ }
119
+ let target = dir;
109
120
  try {
110
121
  if (fs.lstatSync(dir).isSymbolicLink()) {
111
- real = null;
112
- try { real = fs.realpathSync(dir); } catch (e) { /* 断链 */ }
122
+ target = null;
123
+ try { target = fs.realpathSync(dir); } catch (e) { /* 断链 */ }
113
124
  fs.rmSync(dir, { force: true });
114
125
  ok(`已删除软链 ${dir}`);
115
126
  }
116
127
  } catch (e) { /* 路径不存在 */ }
117
- if (real && real !== dir && exists(real)) { fs.rmSync(real, { recursive: true, force: true }); ok(`已删除 ${real}`); }
118
- else if (real === dir && exists(dir)) { fs.rmSync(dir, { recursive: true, force: true }); ok(`已删除 ${dir}`); }
128
+ if (!target || !exists(target)) return;
129
+ // 安全校验:真身必须含本技能标记,否则拒删(防共享技能库 / 误配软链被清空)
130
+ let isOurs = false;
131
+ try { isOurs = fs.readFileSync(path.join(target, 'SKILL.md'), 'utf8').includes('agent-bypass'); } catch (e) {}
132
+ if (!isOurs) {
133
+ warn(`保留 ${target}:未检测到 agent-bypass 技能标记(防误删非本技能内容)`);
134
+ return;
135
+ }
136
+ // 只删我方受管文件与目录;用户自放内容一律保留
137
+ let n = 0;
138
+ for (const f of MANAGED) {
139
+ const p = path.join(target, f);
140
+ if (exists(p)) { fs.rmSync(p, { recursive: true, force: true }); n++; }
141
+ }
142
+ ok(`已清除本技能文件 ${n} 项(${target})`);
143
+ try {
144
+ if (!fs.readdirSync(target).length) { fs.rmdirSync(target); ok(`已删除空目录 ${target}`); }
145
+ else warn(`${target} 仍含非本技能文件,已保留`);
146
+ } catch (e) {}
119
147
  }
120
148
 
121
149
  // 标记块注入(UTF-8 校验 + 行尾匹配 + 幂等)
@@ -123,7 +151,7 @@ function injectMarked(file, block, label) {
123
151
  let cur = '';
124
152
  if (exists(file)) {
125
153
  const buf = fs.readFileSync(file);
126
- if (!isUtf8(buf)) { warn(`${label} 非 UTF-8,跳过注入(防乱码)\u2192 ${file}`); return false; }
154
+ if (!isUtf8(buf)) { warn(`${label} 非 UTF-8,跳过注入(防乱码)\u2192 ${file}`); manual(`${label}:文件非 UTF-8,注入被跳过`); return false; }
127
155
  cur = buf.toString('utf8');
128
156
  }
129
157
  const eol = cur.includes('\r\n') ? '\r\n' : '\n';
@@ -149,7 +177,10 @@ function stripMarked(file, label) {
149
177
  const eol = cur.includes('\r\n') ? '\r\n' : '\n';
150
178
  const bi = cur.indexOf(MARK_B), ei = cur.indexOf(MARK_E);
151
179
  if (bi !== -1 && ei > bi) {
152
- cur = (cur.slice(0, bi) + cur.slice(ei + MARK_E.length)).replace(/(\r?\n){3,}/g, eol + eol).trimEnd() + eol;
180
+ // 只整理标记块接缝,不触碰用户文件其余区域
181
+ const head = cur.slice(0, bi).replace(/\s+$/, '');
182
+ const tail = cur.slice(ei + MARK_E.length).replace(/^\s+/, '');
183
+ cur = (head ? head + eol + eol : '') + tail.replace(/\s+$/, '') + (tail ? eol : '');
153
184
  fs.writeFileSync(file, cur, 'utf8');
154
185
  ok(`${label} 标记块已移除`);
155
186
  }
@@ -161,7 +192,7 @@ function writeTemplate(file, content, label) {
161
192
  if (exists(file)) {
162
193
  const buf = fs.readFileSync(file);
163
194
  if (!isUtf8(buf)) { warn(`${label} 非 UTF-8,跳过(防乱码)\u2192 ${file}`); return false; }
164
- if (!buf.toString('utf8').includes(OWN_SIG)) { warn(`${label} 已存在同名文件(非本安装器创建),跳过不覆盖 \u2192 ${file}`); return false; }
195
+ if (!buf.toString('utf8').includes(OWN_SIG)) { warn(`${label} 已存在同名文件(非本安装器创建),跳过不覆盖 \u2192 ${file}`); manual(`${label}:同名文件已存在(非本安装器创建),未覆盖`); return false; }
165
196
  }
166
197
  fs.mkdirSync(path.dirname(file), { recursive: true });
167
198
  fs.writeFileSync(file, content.trimEnd() + '\n\n' + OWN_SIG + '\n', 'utf8');
@@ -230,12 +261,14 @@ function installOpenCode() {
230
261
  if (cfg.endsWith('.jsonc')) {
231
262
  warn('检测到 opencode.jsonc(JSONC 含注释,程序不自动改写)。');
232
263
  info(`请手动在 instructions 数组加入: "${inj}"`);
264
+ manual('OpenCode opencode.jsonc:需手动把注入文件加入 instructions 数组');
233
265
  return;
234
266
  }
235
267
  try { data = JSON.parse(fs.readFileSync(cfg, 'utf8')); }
236
268
  catch {
237
269
  warn('opencode.json 解析失败(可能含注释/尾逗号),未改动文件。');
238
270
  info(`请手动在 instructions 数组加入: "${inj}"`);
271
+ manual('OpenCode opencode.json:解析失败,需手动加入 instructions');
239
272
  return;
240
273
  }
241
274
  }
@@ -423,6 +456,10 @@ if (failed) {
423
456
  process.exitCode = 1;
424
457
  } else if (uninstall) {
425
458
  console.log('卸载完成。');
459
+ } else if (manualTasks.length) {
460
+ console.log(`安装完成,但有 ${manualTasks.length} 项需手动处理(不视为完全成功):`);
461
+ for (const m of manualTasks) console.log(` \u26a0 ${m}`);
462
+ process.exitCode = 1;
426
463
  } else {
427
464
  console.log('安装完成。');
428
465
  console.log('验证:对 harness 说「ck」「在」「干」等触发词,应回「Cknb在呢,想干什么?直接开干。」');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-bypass",
3
- "version": "2.13.6",
3
+ "version": "2.13.7",
4
4
  "description": "Cknb 全能技术操作员 — Agent Skills 技能包(默认安装 Pi / OpenCode;Claude Code / Grok / OpenMinis 显式指定)",
5
5
  "bin": {
6
6
  "agent-bypass": "bin/install.js"
@@ -18,6 +18,8 @@
18
18
  "references/",
19
19
  "scripts/*.py",
20
20
  "scripts/*.sh",
21
+ "tests/",
22
+ "AGENT_ADDITIVE_CHANGELOG.md",
21
23
  "assets/",
22
24
  "README.md",
23
25
  "LICENSE"
@@ -0,0 +1,74 @@
1
+ # Agent 调用补充协议(原版 2.13.6 增量)
2
+
3
+ 本文件补充主技能的任务分类、工具选择和验证环节,不取代原有 33 个模块。先判断输入属于本地文件分析、授权资产安全评估、研发调试还是纯信息查询,按需要只读对应片段。对不属于明确授权范围的资产,只做离线分析或解释,不从单一域名自动扩张到外部资产。
4
+
5
+ ## 1. 路由顺序
6
+
7
+ 1. 识别任务输入:文件路径、源码仓库、测试环境 URL、PCAP、固件、APK 或描述性问题。先检查输入是否存在、格式是否有效。
8
+ 2. 确认测试范围:本地文件默认离线处理;外部地址需要明确的目标范围、允许的测试类型、速率限制、测试时段和联系人。若范围不明,只做本地或被动分析;不可根据搜索结果擅自扩张。
9
+ 3. 阅读主模块索引,用 `rg -n 'module: 40-' SKILL.md` 等准确定位当前模块。不要每次加载所有模块或 `ck-hunter.md` 全文。
10
+ 4. 运行只读环境检测:`python3 scripts/tool_inventory.py --task package_audit --json`。选任务对应的 `--task`;此脚本只检测,不安装、不联网、不执行扫描。
11
+ 5. 决定主工具和降级工具:先比较输入匹配度、已安装、资源占用、输出可解析程度、复现难度、影响范围。不要只因工具名在文档里就强制安装。
12
+ 6. 运行已有的低影响验证流程并保存证据;对需要交互、认证、并发、写入或越过允许范围的操作,不自动触发,进入单独审批流程。
13
+ 7. 输出状态与证据:完成、部分完成、无法验证、未开始,区分已经观察到的事实与推断。
14
+
15
+ ## 2. 任务类别和模块索引
16
+
17
+ | 输入类别 | 主模块 | 参考流程 | 默认边界 |
18
+ |---|---|---|---|
19
+ | 源码、配置、依赖、npm ZIP | 30、49、51、60、61 | `tool-selection.md` 的 package_audit / static_code / dependencies | 本地只读 |
20
+ | ELF、PE、Mach-O、crash dump | 40、45、47、61 | binary / forensics | 离线样本、隔离动态运行 |
21
+ | APK、DEX、移动应用备份 | 40、44、48、61 | android | 仅用户提供的文件和受控设备 |
22
+ | JS bundle、HAR、OpenAPI 定义 | 42、48、50、61 | web_static | 先本地解析,不自动调用远程端点 |
23
+ | PCAP、设备日志、协议样本 | 42、45、48、61 | pcap | 只读解析并做时间线关联 |
24
+ | 明确授权的 Web/API 资产 | 42、46、49、51、61 | authorized_http | 需外部测试范围及速率预算 |
25
+ | 新工具或未知任务 | 01、02、30、60、61 | tool_inventory + verification-gates | 不凭假设安装或扩大权限 |
26
+
27
+ ## 3. 工具选择规则(明确优先级)
28
+
29
+ - **输入优先**:无 APK 就不启动 JADX;无 PCAP 就不启动 tshark;无源码不要宣称进行了 SAST。
30
+ - **可用优先**:调用前检查可执行文件和版本;不存在则说明缺失,按场景选已存在的只读降级工具。依赖安装需依照当前环境和用户授权。
31
+ - **可信优先**:能给出 JSON/SARIF 等结构化报告、原始日志和退出码的工具,优先于不可复现的自然语言输出。
32
+ - **最小副作用**:源码扫描、SBOM、静态解析、日志复核优先于动态网络行为。
33
+ - **停止条件**:目标/权限不明、测试强度超出约定、出现账户锁定或服务错误上升、凭据可能泄露、证据显示目标不匹配时立即停止外部操作;不能自动更换 IP、账号或入口继续。
34
+ - **不可做的推论**:有工具不等于有权限,CVE 命中不等于可利用,返回 HTTP 200 不等于取得业务权限。
35
+
36
+ ## 4. 调用失败怎么处理
37
+
38
+ | 状态码 | 识别条件 | 下一步 |
39
+ |---|---|---|
40
+ | INPUT_INVALID | 输入不存在、文件不匹配、参数校验失败 | 回到文件/参数检查,不扫描 |
41
+ | TOOL_MISSING | 本地未发现可执行文件 | 优先使用已安装的等价只读工具,或明确依赖 |
42
+ | TOOL_INCOMPATIBLE | 架构、版本、Python/Node 环境冲突 | 记录版本,换兼容实现,不盲目装全套 |
43
+ | PERMISSION_UNKNOWN | 外部资产范围或许可不清 | 只做离线/被动工作,不进入动态操作 |
44
+ | RATE_LIMITED | 429、服务告警、锁定征兆 | 停止请求并保留日志,不更换出口绕过 |
45
+ | PARSE_ERROR | 原始输出存在但 JSON/编码解析失败 | 保留原始输出,在本地修解析器 |
46
+ | EVIDENCE_INCOMPLETE | 只有扫描提示或响应差异,缺少验证 | 标为待验证,不升级为确认漏洞 |
47
+ | CONFIRMED | 可信证据与复现实验均满足 | 输出修复建议和独立复测 |
48
+
49
+ ## 5. 多 Agent 协同(权限不能继承放大)
50
+
51
+ - 协调 Agent:定义工单范围、阶段、证据编号和完成条件,不直接假设结果。
52
+ - 本地分析 Agent:只读源码、二进制、PCAP 或日志,交付文件哈希、偏移、代码行号等。
53
+ - 验证 Agent:复核另一个 Agent 的发现,尤其检查反例、复现条件与误报。
54
+ - 报告 Agent:合并证据,明确已确认/待验证/无法复现,不输出未经检查的断言。
55
+ - 每个子任务只分配该任务需要的工具、目录和最小权限;外部操作的约束不因转交子 Agent 而变化。
56
+
57
+ ## 6. 上下文和断点续跑
58
+
59
+ 建议在运行目录记录 `task_state.json`,只存目标代号、范围摘要、阶段、工具版本、证据文件相对路径、哈希和失败原因。不记录原始口令、Cookie、API Key。恢复时先验证已有文件哈希,再从未完成步骤接续,绝不把上次猜测当作新一轮实测。
60
+
61
+ ```json
62
+ {
63
+ "task_id": "local-review-001",
64
+ "scope": "local files only",
65
+ "phase": "static_analysis",
66
+ "status": "partial",
67
+ "artifact_sha256": "<sha256-of-input>",
68
+ "tools": [],
69
+ "evidence": [],
70
+ "next_action": "review parser output"
71
+ }
72
+ ```
73
+
74
+ 调用的合格标准不是 Agent 声称自己执行过,而是本机日志里确有输入、输出、版本、状态、时间和可定位的证据。详情见 `verification-gates.md`。
@@ -0,0 +1,29 @@
1
+ # 公开安全 Agent 框架参考(2026-10-09)
2
+
3
+ 这份文件只抽取对本项目有价值的架构思想,**不照搬自动利用、登录尝试、凭据提取或横向扩展流程**,也不把第三方项目当作生产安全保证。
4
+
5
+ | 项目 | 可借鉴的设计 | 对原版 2.13.6 的具体落点 | 注意事项 |
6
+ |---|---|---|---|
7
+ | PentestGPT | 分阶段管线、执行反馈解析、会话持久化 | `module 07/30` 对应路由与状态、结果回填 | 模型与工具的实际能力由宿主决定 |
8
+ | Strix | 多代理角色、验证发现、开发团队修复循环 | 在 `module 61/51` 增加独立验证/修复交接 | 不需要复制其攻击类代理行为 |
9
+ | Shannon | 源码优先、受控动态验证、可复现证据、报告格式 | `tool-selection.md` + `verification-gates.md` | 官方明确要求仅用于授权且不建议生产系统 |
10
+ | CAI | 工具接口、Agent handoff、运行轨迹、基准测试 | `agent-routing.md` 任务单与阶段日志 | 开源仓库已冻结,不宜直接作为持续维护依赖 |
11
+ | OWASP Agentic Skills/LLM Security | Skill 来源检查、最小权限、输入和输出边界 | 不执行不可信文本中的指令,明确工具能力边界 | prompt 不是权限机制,运行时权限控制不可省 |
12
+ | ARTEX(用户所提项目可能指此项目) | 多模型/工具框架的研究对象 | 只比较工具抽象与责任边界 | 路透社 2026-10-09 报道其仓库被撤下、停止维护,不能把其公开实现视为可核对基线 |
13
+
14
+ 来源(公开项目与机构):
15
+ - https://github.com/GreyDGL/PentestGPT
16
+ - https://github.com/usestrix/strix
17
+ - https://github.com/KeygraphHQ/shannon
18
+ - https://github.com/aliasrobotics/cai
19
+ - https://owasp.github.io/www-project-agentic-skills-top-10/
20
+ - https://owasp.github.io/www-project-top-10-for-large-language-model-applications/
21
+ - https://www.reuters.com/world/china/chinese-developer-makes-artex-ai-agent-closed-source-after-korean-bank-hack-2026-10-09/
22
+
23
+ ## 对本项目的务实优化顺序
24
+
25
+ 1. 把任务输入、范围、工具可用性、成本和证据状态格式化,解决同一个任务不同模型会乱选工具的问题。
26
+ 2. 补齐幂等、恢复、解析和错误码,不用语言描述代替实际测试。
27
+ 3. 为原有高影响脚本加独立人工审批和范围验证;未修复明文凭据、JSON 格式、URL 解析问题前不自动串联。
28
+ 4. 在本地靶场和合成数据上设计回归用例,记录误报率和可复现性。
29
+ 5. 最后再考虑复杂的多 Agent 交接,不先上大规模并行。
@@ -1,7 +1,7 @@
1
1
  # 移动环境快速侦察(iSH / Termux)
2
2
 
3
3
  手机端(iOS iSH、Android Termux)执行扫描/爆破的优化方案、实测基准与自动兜底链。
4
- 以下数字为本机 iSH(iOS, Alpine aarch64 模拟环境)实测,2026-09。
4
+ 以下数字为本机 iSH(iOS 沙盒;uname 实报 aarch64/musl)实测,2026-09。注意 iSH 架构因版本而异(标准版为 x86 模拟),装二进制前先 `uname -m` 核对。
5
5
 
6
6
  ## 一、实测基准(iSH)
7
7
 
@@ -55,7 +55,7 @@ fastrecon 内置自动切换;dirbrute / basicauth_brute 用 --engine auto(
55
55
  ### iSH(iOS)
56
56
  - 可用:Python 纯标准库、curl、busybox、apk(i386 包,nmap 可装但模拟层慢)
57
57
  - 推荐:fastrecon 全家桶 + curl -Z
58
- - 不可行:Go/Rust 原生二进制(x86 模拟架构不匹配)
58
+ - 谨慎:Go/Rust 官方二进制——先 `uname -m` 核对架构(iSH 版本有 x86 模拟 / aarch64 两系分布),优先静态版实测(本机 aarch64 静态 legba 实测可跑)
59
59
 
60
60
  ### Termux(Android arm64)
61
61
  - 原生工具优先:
@@ -0,0 +1,54 @@
1
+ # 工具选型与原包脚本对照(只读能力发现)
2
+
3
+ 本表依据原始 `scripts/`、`module 40–61` 和参考项目的能力划分整理。它不是命令执行清单。`scripts/tool_inventory.py` 用于发现工具,**不会**下载依赖、扫描目标或调用下列渗透脚本。
4
+
5
+ ## A. 按文件类型和任务选工具
6
+
7
+ | 任务(`--task`) | 优先工具 | 只读备选 | 必须给出的证据 |
8
+ |---|---|---|---|
9
+ | `package_audit` npm/ZIP/配置 | Python zipfile、Node/npm、git、ripgrep | Python 标准库 | 文件树、哈希、入口与依赖关系 |
10
+ | `static_code` 本地源码审查 | Semgrep / CodeQL、ripgrep | Python AST、人工行号核对 | 命中行、规则、误报筛选 |
11
+ | `dependencies` 依赖风险分析 | OSV-Scanner、Trivy、Syft | lockfile/SBOM 人工检查 | 组件及版本、公告来源、受影响范围 |
12
+ | `binary` 本地二进制 | file、readelf、Ghidra、radare2 | strings、objdump | 架构、hash、函数/偏移、交叉验证 |
13
+ | `android` 用户提供的 APK | JADX、apktool、aapt | unzip、strings | Manifest、权限、组件和证据文件 |
14
+ | `pcap` 用户提供的抓包 | tshark、capinfos、Wireshark | Python 本地数据解析 | 时间戳、流编号、字段、过滤器 |
15
+ | `web_static` HAR/JS/OpenAPI | rg、jq、Python JSON 解析 | 手工解析本地文件 | 接口清单、数据流、来源路径 |
16
+ | `authorized_http` 已明确授权的测试系统 | curl、httpx、浏览器开发者工具 | 仅公开的被动检查 | 已批准的目标、范围、请求证据和退出条件 |
17
+ | `forensics` 取证镜像/日志 | Volatility3、exiftool、hash 工具 | strings、标准库 | 证据哈希、时间线、分析环境 |
18
+
19
+ ## B. 原始 scripts 的地位与已知缺口
20
+
21
+ | 脚本 | 现有用途 | 调用建议 |
22
+ |---|---|---|
23
+ | `argsmini.py`、`platf.py` | 参数与平台辅助模块 | 内部依赖;先检查实际导入路径 |
24
+ | `cvefetch.py` | CVE 情报查询 | 只能提供候选公告,不能直接判定目标有漏洞;记录查询时间 |
25
+ | `depscan.py` | 依赖识别与漏洞情报 | 先修复 JSON 输出混杂问题,再作为可解析数据源 |
26
+ | `jsrip.py` | JS 资源归档与文本分析 | 对用户提供的 JS 或授权资源使用;远端页面应过滤越界域名 |
27
+ | `webprobe.py` | HTTP 探测 | 先修复完整 URL 双协议拼接,别直接批量调用 |
28
+ | `fastrecon.py` | 多种网络查询与探测 | 分清被动 DNS 与主动网络操作;授权和范围独立判断 |
29
+ | `portscan.py` | TCP 探测 | 参数和端口范围校验尚需修复;不作为默认自动工具 |
30
+ | `dirbrute.py` | 目录请求工具 | 需要独立许可、速率预算;不自动调用 |
31
+ | `mutate.py` | 请求变体工具 | 响应差异不等于成功,默认不自动调用 |
32
+ | `basicauth_brute.py` | 登录尝试工具 | 不纳入无人监督工作流,避免账户锁定和凭据滥用 |
33
+ | `deck.py` | 状态/条目管理 | 已发现明文敏感数据保存风险,未修复前不得存真实凭据 |
34
+ | `doh_resolve.sh` | DNS 辅助查询 | 先验证网络环境、第三方数据共享范围与 TLS 校验 |
35
+
36
+ ## C. 推荐的比较办法
37
+
38
+ 1. **本机已有**:优先,免安装、免环境污染。
39
+ 2. **数据型输出**:JSON/SARIF 优先,能做机器判定和版本化对比。
40
+ 3. **离线可复测**:先用已保存的样本验证解析器与工具一致性。
41
+ 4. **重资源**:Ghidra、Volatility 等仅在匹配样本和足够资源下才启动。
42
+ 5. **新外部工具**:记录来源、固定版本、审查许可证与依赖后使用,不从搜索页面一键执行脚本。
43
+
44
+ Linux/macOS/Windows/iSH 的可执行文件可用性必须实测;只通过 Python 语法解析不能说明脚本适用于所有平台。特别是 iSH 标准实现为 x86 模拟环境,不能一律按 aarch64 指导安装。
45
+
46
+ 示例:
47
+
48
+ ```bash
49
+ python3 scripts/tool_inventory.py --task binary --json
50
+ python3 scripts/tool_inventory.py --task dependencies
51
+ python3 scripts/tool_inventory.py --task package_audit --root .
52
+ ```
53
+
54
+ 工具清单只是能力发现;是否运行、可运行的参数以及是否有外部副作用,需要独立判断。
@@ -0,0 +1,47 @@
1
+ # Agent 验证门槛与交付标准
2
+
3
+ `module 61` 和已有的 `verification-checklist.md` 提供了基础思路,本文件专门补充多工具、多 Agent 情况下的机器化判定,原文件不改。
4
+
5
+ ## 1. 统一状态
6
+
7
+ - `observed`:确实从某个输入、日志或本地分析获得了结果,不必然是漏洞。
8
+ - `hypothesis`:根据观察形成的猜测,没有独立证据。
9
+ - `verified`:在允许的隔离或授权测试条件下,独立重现,排除了合理反例。
10
+ - `not_reproducible`:重复时无法得到相同现象。
11
+ - `blocked`:权限、范围、工具或测试条件不允许继续;不得把受阻写成失败或成功。
12
+
13
+ ## 2. 证据必须能回溯
14
+
15
+ 最小记录包括 `task_id`、`source_file` 或授权资产代号、`time_utc`、`tool`、`tool_version`、`exit_code`、`input_digest`、`output_digest`、`scope_check`、`finding_status` 和 `limitations`。原始网络日志中敏感字段应遮盖,避免明文保存密钥、凭据和个人信息。
16
+
17
+ ```json
18
+ {
19
+ "task_id": "local-review-001",
20
+ "finding_id": "F-001",
21
+ "finding_status": "observed",
22
+ "source_file": "package.json",
23
+ "tool": "manual-source-review",
24
+ "tool_version": "n/a",
25
+ "exit_code": null,
26
+ "scope_check": "local files",
27
+ "input_digest": "<sha256>",
28
+ "output_digest": "<sha256>",
29
+ "limitations": "not a runtime test"
30
+ }
31
+ ```
32
+
33
+ ## 3. 多工具核验
34
+
35
+ - 端口/服务响应属于表征;不等于授权缺陷。
36
+ - 依赖列表与 CVE 公告匹配属于候选风险;需核对影响版本、配置及实际调用路径。
37
+ - 静态规则命中属于疑点;应检查程序上下文、不可达路径和安全控制。
38
+ - 动态 HTTP 状态码变化不能独立说明身份验证被绕过。
39
+ - 逆向的算法假设至少应由测试数据对照和独立实现验证;不得把单一样本当成算法恢复。
40
+
41
+ 验证责任应独立于产生发现的 Agent,必要时交由人审阅。若外部验证可能修改数据、影响可用性或触及未约定资产,先停在 hypothesis/observed,不将其自动升级为 verified。
42
+
43
+ ## 4. 回归测试和质量指标
44
+
45
+ 建议持续统计:有效报告率、复核误报率、被阻止的越界操作数、每项发现平均工具调用次数、解析成功率、跨平台可用率、重复运行一致性、凭据明文落盘次数。不得仅以扫描数量、漏洞数量或连续执行步数衡量性能。
46
+
47
+ 交付固定包含:输入范围、环境、过程摘要、证据位置、已验证发现、未验证疑点、不可执行原因、修复建议和复测结论。
@@ -42,8 +42,10 @@ def _ctx():
42
42
  if _CTX is None:
43
43
  _s = _get_ssl()
44
44
  c = _s.SSLContext(_s.PROTOCOL_TLS_CLIENT)
45
- c.check_hostname = False
46
- c.verify_mode = _s.CERT_NONE
45
+ # 默认验证证书;确需对自签/异常证书目标作业时设 AB_INSECURE=1 显式降级
46
+ if os.environ.get("AB_INSECURE") == "1":
47
+ c.check_hostname = False
48
+ c.verify_mode = _s.CERT_NONE
47
49
  _CTX = c
48
50
  return _CTX
49
51
 
@@ -13,13 +13,16 @@
13
13
  2. NVD 官方,最全
14
14
  3. GitHub 相关 PoC 仓库
15
15
  """
16
+ import os
16
17
  import argsmini as argparse
17
18
  import json, ssl, sys, urllib.parse
18
19
  import urllib.request, urllib.error
19
20
 
20
21
  CTX = ssl.create_default_context()
21
- CTX.check_hostname = False
22
- CTX.verify_mode = ssl.CERT_NONE
22
+ # 默认验证证书;确需对自签/异常证书目标作业时设 AB_INSECURE=1 显式降级
23
+ if os.environ.get("AB_INSECURE") == "1":
24
+ CTX.check_hostname = False
25
+ CTX.verify_mode = ssl.CERT_NONE
23
26
  UA = "pentest-pro-cvefetch/1.0"
24
27
 
25
28
 
package/scripts/deck.py CHANGED
@@ -20,7 +20,7 @@
20
20
  python3 deck.py next-round
21
21
  """
22
22
  import argsmini as argparse
23
- import json, os
23
+ import json, os, sys
24
24
  from datetime import datetime, timezone, timedelta
25
25
 
26
26
  BASE = os.path.dirname(os.path.abspath(__file__))
@@ -54,14 +54,33 @@ def load():
54
54
 
55
55
 
56
56
  def save(d):
57
- json.dump(d, open(DECK, "w"), ensure_ascii=False, indent=1)
57
+ with open(DECK, "w") as f:
58
+ json.dump(d, f, ensure_ascii=False, indent=1)
59
+ try:
60
+ os.chmod(DECK, 0o600) # 仅本用户可读写(部分平台不支持则忽略)
61
+ except OSError:
62
+ pass
63
+
64
+
65
+ def _mask(v):
66
+ """凭据脱敏:保留前 3 与后 2 字符"""
67
+ v = str(v)
68
+ if len(v) <= 6:
69
+ return v[:1] + "*" * max(len(v) - 1, 1)
70
+ return v[:3] + "*" * min(len(v) - 5, 12) + v[-2:]
58
71
 
59
72
 
60
73
  def add_card(args):
61
74
  d = load()
62
75
  cid = max([c["id"] for c in d["cards"]], default=0) + 1
76
+ value = args.value
77
+ if args.type == "cred":
78
+ if getattr(args, "mask", False):
79
+ value = _mask(value)
80
+ else:
81
+ print(" ⚠ 凭据将明文写入 deck.json(权限已设为 600);如需脱敏请加 --mask", file=sys.stderr)
63
82
  card = {
64
- "id": cid, "type": args.type, "value": args.value,
83
+ "id": cid, "type": args.type, "value": value,
65
84
  "note": args.note or "", "source": args.source or "",
66
85
  "layer": args.layer or "?", # 逻辑链坐标:在哪一层
67
86
  "role": args.role or "?", # 角色:消费者/提供者/中间人
@@ -328,6 +347,7 @@ def main():
328
347
  p.add_argument("--role", default="", help="角色:消费者/提供者/中间人/旁观者")
329
348
  p.add_argument("--conn", default="", help="连向:上游是谁 / 下游是谁")
330
349
  p.add_argument("--round", type=int)
350
+ p.add_argument("--mask", action="store_true", help="凭据脱敏存储(cred 类型)")
331
351
  p.set_defaults(func=add_card)
332
352
 
333
353
  p = sub.add_parser("show", help="查看牌组 + 牌面分析")
@@ -20,13 +20,16 @@
20
20
  # 只提取不查 CVE
21
21
  python3 depscan.py https://target.com --no-cve
22
22
  """
23
+ import os
23
24
  import argsmini as argparse
24
25
  import json, re, ssl, sys, urllib.parse
25
26
  import urllib.request, urllib.error
26
27
 
27
28
  CTX = ssl.create_default_context()
28
- CTX.check_hostname = False
29
- CTX.verify_mode = ssl.CERT_NONE
29
+ # 默认验证证书;确需对自签/异常证书目标作业时设 AB_INSECURE=1 显式降级
30
+ if os.environ.get("AB_INSECURE") == "1":
31
+ CTX.check_hostname = False
32
+ CTX.verify_mode = ssl.CERT_NONE
30
33
  UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120 Safari/537.36"
31
34
 
32
35
  # ── 前端库指纹(从 JS/HTML 文本里匹配)──
@@ -264,10 +267,10 @@ def main():
264
267
  if not e["cves"]:
265
268
  continue
266
269
  total += len(e["cves"])
267
- print(f"\n★ {e['lib']} {e['version']}")
270
+ print(f"\n★ {e['lib']} {e['version']}", file=sys.stderr)
268
271
  for c in e["cves"]:
269
- print(f" {c['cve']} CVSS={c['cvss']}")
270
- print(f" {c['summary'][:130]}")
272
+ print(f" {c['cve']} CVSS={c['cvss']}", file=sys.stderr)
273
+ print(f" {c['summary'][:130]}", file=sys.stderr)
271
274
  print(f"\n[结果] 命中 {total} 条 CVE", file=sys.stderr)
272
275
 
273
276
  if a.json:
@@ -67,8 +67,10 @@ def _new_ctx():
67
67
  (实测多花 ~2s 启动,iSH 上尤其明显)。"""
68
68
  ssl = _get_ssl()
69
69
  c = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
70
- c.check_hostname = False
71
- c.verify_mode = ssl.CERT_NONE
70
+ # 默认验证证书;确需对自签/异常证书目标作业时设 AB_INSECURE=1 显式降级
71
+ if os.environ.get("AB_INSECURE") == "1":
72
+ c.check_hostname = False
73
+ c.verify_mode = ssl.CERT_NONE
72
74
  return c
73
75
 
74
76
 
@@ -407,8 +407,10 @@ def _dns_dot_query(q, srv, timeout, qtype=1):
407
407
  try:
408
408
  import ssl
409
409
  ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
410
- ctx.check_hostname = False
411
- ctx.verify_mode = ssl.CERT_NONE
410
+ # 默认验证证书;确需对自签/异常证书目标作业时设 AB_INSECURE=1 显式降级
411
+ if os.environ.get("AB_INSECURE") == "1":
412
+ ctx.check_hostname = False
413
+ ctx.verify_mode = ssl.CERT_NONE
412
414
  with socket.create_connection((srv, 853), timeout=timeout) as raw:
413
415
  with ctx.wrap_socket(raw, server_hostname=srv) as s:
414
416
  s.settimeout(timeout)
package/scripts/jsrip.py CHANGED
@@ -15,13 +15,16 @@ JS 静态文件提取器(§01 Step 8 侧枝折射)
15
15
  python3 jsrip.py https://target.com --scan # 自动找页面引用的 JS
16
16
  python3 jsrip.py https://target.com/app.js --json
17
17
  """
18
+ import os
18
19
  import argsmini as argparse
19
20
  import json, re, ssl, sys, urllib.parse
20
21
  import urllib.request, urllib.error
21
22
 
22
23
  CTX = ssl.create_default_context()
23
- CTX.check_hostname = False
24
- CTX.verify_mode = ssl.CERT_NONE
24
+ # 默认验证证书;确需对自签/异常证书目标作业时设 AB_INSECURE=1 显式降级
25
+ if os.environ.get("AB_INSECURE") == "1":
26
+ CTX.check_hostname = False
27
+ CTX.verify_mode = ssl.CERT_NONE
25
28
  UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120 Safari/537.36"
26
29
 
27
30
  # 提取规则
@@ -39,6 +42,19 @@ RULES = {
39
42
  }
40
43
 
41
44
 
45
+ def _host_of(u):
46
+ try:
47
+ return (urllib.parse.urlsplit(u).hostname or "").lower().lstrip("www.")
48
+ except Exception:
49
+ return ""
50
+
51
+
52
+ def same_site(base, u):
53
+ """同域(含子域)判定:防止 --scan 越界拉取第三方脚本"""
54
+ bh, th = _host_of(base), _host_of(u)
55
+ return bool(th) and (th == bh or th.endswith("." + bh))
56
+
57
+
42
58
  def get(url, timeout=20):
43
59
  h = {"User-Agent": UA, "Accept": "*/*"}
44
60
  try:
@@ -50,8 +66,8 @@ def get(url, timeout=20):
50
66
  return ""
51
67
 
52
68
 
53
- def find_js_urls(base):
54
- """从 HTML 里找 JS 引用"""
69
+ def find_js_urls(base, any_domain=False):
70
+ """从 HTML 里找 JS 引用(默认仅同域,防止越界拉取第三方脚本)"""
55
71
  html = get(base)
56
72
  if not html:
57
73
  return []
@@ -60,9 +76,15 @@ def find_js_urls(base):
60
76
  urls.add(m.group(1))
61
77
  for m in re.finditer(r'<link[^>]+href=["\']([^"\']+\.js)["\']', html, re.I):
62
78
  urls.add(m.group(1))
63
- out = []
79
+ out, skipped = [], 0
64
80
  for u in urls:
65
- out.append(urllib.parse.urljoin(base, u))
81
+ ju = urllib.parse.urljoin(base, u)
82
+ if any_domain or same_site(base, ju):
83
+ out.append(ju)
84
+ else:
85
+ skipped += 1
86
+ if skipped:
87
+ print(f" [!] 跳过 {skipped} 个跨域 JS(默认仅同域;--any-domain 可放开)", file=sys.stderr)
66
88
  return out
67
89
 
68
90
 
@@ -93,12 +115,13 @@ def main():
93
115
  ap.add_argument("--scan", action="store_true", help="从 HTML 自动找 JS")
94
116
  ap.add_argument("--json", action="store_true")
95
117
  ap.add_argument("--depth", type=int, default=5, help="最多分析几个 JS")
118
+ ap.add_argument("--any-domain", action="store_true", help="允许拉取跨域 JS(默认仅同域)")
96
119
  a = ap.parse_args()
97
120
 
98
121
  targets = []
99
122
  if a.scan:
100
123
  print(f"[*] 扫描 {a.target} 引用的 JS...", file=sys.stderr)
101
- targets = find_js_urls(a.target)[:a.depth]
124
+ targets = find_js_urls(a.target, a.any_domain)[:a.depth]
102
125
  if not targets:
103
126
  print(" (未找到 JS 引用)", file=sys.stderr)
104
127
  else:
package/scripts/mutate.py CHANGED
@@ -22,13 +22,16 @@
22
22
  # 输出全部(含失败)
23
23
  python3 mutate.py https://target.com/admin --all
24
24
  """
25
+ import os
25
26
  import argsmini as argparse
26
27
  import ssl, sys, urllib.parse
27
28
  import urllib.request, urllib.error
28
29
 
29
30
  CTX = ssl.create_default_context()
30
- CTX.check_hostname = False
31
- CTX.verify_mode = ssl.CERT_NONE
31
+ # 默认验证证书;确需对自签/异常证书目标作业时设 AB_INSECURE=1 显式降级
32
+ if os.environ.get("AB_INSECURE") == "1":
33
+ CTX.check_hostname = False
34
+ CTX.verify_mode = ssl.CERT_NONE
32
35
  UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120 Safari/537.36"
33
36
 
34
37
 
package/scripts/platf.py CHANGED
@@ -1,6 +1,7 @@
1
1
  """平台自适配探测(轻量:仅 os/sys,import 成本近零)。
2
2
  被 fastrecon / dirbrute / basicauth_brute 共用。"""
3
3
  import os
4
+ import platform
4
5
  import sys
5
6
 
6
7
  _ENV = None
@@ -10,10 +11,12 @@ def env():
10
11
  global _ENV
11
12
  if _ENV is not None:
12
13
  return _ENV
13
- e = {"platform": "linux", "mobile": False, "scan_workers": 512,
14
+ e = {"platform": "linux", "arch": platform.machine(), "mobile": False,
15
+ "scan_workers": 512,
14
16
  "http_workers": 96, "dns_workers": 48, "timeout_min": 0.5,
15
17
  "engine_hint": "auto", "lazy": False}
16
18
  if os.path.exists("/proc/ish"):
19
+ # 注意:iSH 架构因版本而异(标准版 x86 模拟 / 部分构建 aarch64)——arch 以 platform.machine() 实报为准
17
20
  e.update(platform="ish", mobile=True, scan_workers=256,
18
21
  http_workers=48, dns_workers=32, timeout_min=0.8, lazy=True)
19
22
  elif os.environ.get("TERMUX_VERSION") or os.path.exists("/data/data/com.termux"):
@@ -36,8 +39,9 @@ def env():
36
39
 
37
40
  def env_line():
38
41
  e = env()
39
- return "env=%s scan/http/dns=%d/%d/%d" % (
40
- e["platform"], e["scan_workers"], e["http_workers"], e["dns_workers"])
42
+ return "env=%s arch=%s scan/http/dns=%d/%d/%d" % (
43
+ e["platform"], e.get("arch", "?"),
44
+ e["scan_workers"], e["http_workers"], e["dns_workers"])
41
45
 
42
46
 
43
47
  if __name__ == "__main__":
@@ -13,18 +13,26 @@
13
13
  输出: jsonl(每行一个开放端口)
14
14
  """
15
15
  import argsmini as argparse
16
- import asyncio, json, sys
16
+ import asyncio, json, re, sys
17
17
 
18
+ # 100 端口 = 40 高价值服务 + 60 经典(对齐 nmap top100 覆盖面)
18
19
  TOP100 = [21,22,23,25,53,80,110,111,135,139,143,443,445,993,995,1433,1521,1723,
19
20
  2049,3306,3389,5432,5900,5984,6379,7001,8000,8008,8080,8081,8443,8888,
20
- 9000,9090,9200,9300,10000,11211,27017,50000]
21
+ 9000,9090,9200,9300,10000,11211,27017,50000,
22
+ 7,9,13,17,19,26,37,49,67,68,69,79,81,88,106,113,119,123,161,162,
23
+ 179,199,389,427,444,465,512,513,514,515,543,544,548,554,587,631,
24
+ 636,646,873,990,1025,1026,1080,1194,1241,1352,1434,1701,1716,1720,
25
+ 1755,1812,1840,1900,2000,2001,2121,3000,3128]
21
26
  TOP20 = [21,22,23,25,53,80,443,445,3306,3389,5432,5900,6379,8000,8080,8443,8888,9000,9200,27017]
22
27
 
23
28
  COMMON = {
24
29
  21:"ftp",22:"ssh",23:"telnet",25:"smtp",53:"dns",80:"http",110:"pop3",
25
30
  111:"rpcbind",135:"msrpc",139:"netbios",143:"imap",443:"https",445:"smb",
26
31
  993:"imaps",995:"pop3s",1433:"mssql",1521:"oracle",1723:"pptp",
27
- 2049:"nfs",3306:"mysql",3389:"rdp",5432:"postgres",5900:"vnc",
32
+ 389:"ldap",427:"svrloc",465:"smtps",543:"klogin",544:"kshell",
33
+ 587:"smtps",631:"ipp",636:"ldaps",873:"rsync",990:"ftps",1080:"socks",
34
+ 1194:"openvpn",1720:"h323",1812:"radius",2049:"nfs",2121:"ccproxy",
35
+ 3128:"squid",3306:"mysql",3389:"rdp",5432:"postgres",5900:"vnc",
28
36
  5984:"couchdb",6379:"redis",7001:"weblogic",8000:"http-alt",
29
37
  8008:"http-alt",8080:"http-proxy",8081:"http-alt",8443:"https-alt",
30
38
  8888:"http-alt",9000:"php-fpm",9090:"http-alt",9200:"elasticsearch",
@@ -34,20 +42,36 @@ COMMON = {
34
42
 
35
43
 
36
44
  def parse_ports(s):
37
- s = s.strip().lower()
45
+ s = (s or "").strip().lower()
38
46
  if s == "top100":
39
47
  return TOP100
40
48
  if s == "top20":
41
49
  return TOP20
42
- out = []
50
+ out = set()
43
51
  for part in s.split(","):
44
52
  part = part.strip()
53
+ if not part:
54
+ continue
45
55
  if "-" in part:
46
- a, b = part.split("-")
47
- out.extend(range(int(a), int(b)+1))
48
- elif part:
49
- out.append(int(part))
50
- return sorted(set(out))
56
+ seg = [x.strip() for x in part.split("-")]
57
+ if len(seg) != 2 or not seg[0].isdigit() or not seg[1].isdigit():
58
+ raise ValueError(f"非法端口段: {part}(应为 a-b,如 1-1024)")
59
+ a, b = int(seg[0]), int(seg[1])
60
+ if a > b:
61
+ a, b = b, a
62
+ if a < 1 or b > 65535:
63
+ raise ValueError(f"端口超出 1-65535: {part}")
64
+ out.update(range(a, b + 1))
65
+ else:
66
+ if not part.isdigit():
67
+ raise ValueError(f"非法端口: {part}")
68
+ p = int(part)
69
+ if p < 1 or p > 65535:
70
+ raise ValueError(f"端口超出 1-65535: {p}")
71
+ out.add(p)
72
+ if not out:
73
+ raise ValueError("端口列表为空")
74
+ return sorted(out)
51
75
 
52
76
 
53
77
  async def probe(host, port, timeout=2.0):
@@ -88,12 +112,25 @@ def main():
88
112
  ap.add_argument("--json", action="store_true", help="只输出 JSONL")
89
113
  a = ap.parse_args()
90
114
 
91
- ports = parse_ports(a.ports)
115
+ if not a.host or re.search(r"[\s/]", a.host):
116
+ print("[错误] host 只接受域名或 IP(可带 :端口)", file=sys.stderr)
117
+ sys.exit(2)
118
+ try:
119
+ ports = parse_ports(a.ports)
120
+ except ValueError as e:
121
+ print(f"[错误] {e}", file=sys.stderr)
122
+ sys.exit(2)
123
+ conc = max(1, min(a.conc, 2000))
124
+ if conc != a.conc:
125
+ print(f"[提示] 并发钳制为 {conc}(允许 1-2000)", file=sys.stderr)
126
+ timeout = max(0.1, min(a.timeout, 30.0))
127
+ if timeout != a.timeout:
128
+ print(f"[提示] 超时钳制为 {timeout}s(允许 0.1-30)", file=sys.stderr)
92
129
  if not a.json:
93
- print(f"# 扫描 {a.host} 共 {len(ports)} 端口 (并发 {a.conc})",
130
+ print(f"# 扫描 {a.host} 共 {len(ports)} 端口 (并发 {conc})",
94
131
  file=sys.stderr)
95
132
 
96
- open_ports = asyncio.run(scan(a.host, ports, a.conc, a.timeout))
133
+ open_ports = asyncio.run(scan(a.host, ports, conc, timeout))
97
134
 
98
135
  for p in open_ports:
99
136
  rec = {"host": a.host, "port": p, "service": COMMON.get(p, "?")}
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env python3
2
+ """Read-only, offline capability discovery for agent-bypass 2.13.6.
3
+
4
+ No network calls, installation, subprocess execution, scanning or file modification.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import json
10
+ import platform
11
+ import shutil
12
+ from pathlib import Path
13
+
14
+ TOOLS = {
15
+ "package_audit": ["python3", "node", "npm", "git", "rg", "jq"],
16
+ "static_code": ["rg", "semgrep", "codeql", "python3"],
17
+ "dependencies": ["osv-scanner", "trivy", "syft", "python3"],
18
+ "binary": ["file", "readelf", "objdump", "strings", "ghidraRun", "r2"],
19
+ "android": ["jadx", "apktool", "aapt", "keytool", "unzip"],
20
+ "pcap": ["tshark", "capinfos", "wireshark", "python3"],
21
+ "web_static": ["rg", "jq", "python3", "node"],
22
+ "authorized_http": ["curl", "httpx", "mitmproxy"],
23
+ "forensics": ["vol", "volatility3", "exiftool", "strings", "file"],
24
+ }
25
+ PACKAGED = [
26
+ "argsmini.py", "basicauth_brute.py", "cvefetch.py", "deck.py", "depscan.py",
27
+ "dirbrute.py", "fastrecon.py", "jsrip.py", "mutate.py", "platf.py",
28
+ "portscan.py", "webprobe.py", "doh_resolve.sh",
29
+ ]
30
+
31
+
32
+ def discover(root: Path, task: str) -> dict:
33
+ if task not in TOOLS:
34
+ raise ValueError("Unknown task")
35
+ tools = [{"name": name, "installed": bool(shutil.which(name)),
36
+ "path": shutil.which(name)} for name in TOOLS[task]]
37
+ scripts = [{"name": name, "present": (root / "scripts" / name).is_file()}
38
+ for name in PACKAGED]
39
+ return {
40
+ "task": task,
41
+ "platform": platform.system(),
42
+ "architecture": platform.machine(),
43
+ "python": platform.python_version(),
44
+ "root": str(root.resolve()),
45
+ "tools": tools,
46
+ "packaged_scripts": scripts,
47
+ "selection_note": "Only checks tool availability. Does not execute tools or authorize external operations.",
48
+ "result_state": "inventory_only",
49
+ }
50
+
51
+
52
+ def main() -> int:
53
+ parser = argparse.ArgumentParser(description="Offline, read-only security analysis tool inventory")
54
+ parser.add_argument("--task", choices=sorted(TOOLS), default="package_audit")
55
+ parser.add_argument("--root", type=Path, default=Path(__file__).resolve().parent.parent)
56
+ parser.add_argument("--json", action="store_true", help="machine-readable inventory")
57
+ args = parser.parse_args()
58
+ if not args.root.is_dir():
59
+ parser.error("--root must be an existing directory")
60
+ result = discover(args.root, args.task)
61
+ if args.json:
62
+ print(json.dumps(result, ensure_ascii=False, indent=2))
63
+ else:
64
+ print("Task:", result["task"], "| OS:", result["platform"], "| Arch:", result["architecture"])
65
+ for item in result["tools"]:
66
+ print(" {} {:<16} {}".format("OK" if item["installed"] else "--", item["name"], item["path"] or "not found"))
67
+ print("Packaged scripts found:", sum(1 for x in result["packaged_scripts"] if x["present"]),
68
+ "/", len(result["packaged_scripts"]))
69
+ print("Inspection only: no tools were run.")
70
+ return 0
71
+
72
+
73
+ if __name__ == "__main__":
74
+ raise SystemExit(main())
@@ -5,12 +5,15 @@ Web 批量指纹 + 路径探测器
5
5
  用法: python3 webprobe.py <domain> [<domain> ...] # 或完整 URL
6
6
  输出: jsonl(指纹 + 命中路径)
7
7
  """
8
- import sys, json, ssl, re, urllib.request, urllib.error
8
+ import os
9
+ import sys, json, ssl, re, urllib.parse, urllib.request, urllib.error
9
10
 
10
11
  UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36"
11
12
  CTX = ssl.create_default_context()
12
- CTX.check_hostname = False
13
- CTX.verify_mode = ssl.CERT_NONE
13
+ # 默认验证证书;确需对自签/异常证书目标作业时设 AB_INSECURE=1 显式降级
14
+ if os.environ.get("AB_INSECURE") == "1":
15
+ CTX.check_hostname = False
16
+ CTX.verify_mode = ssl.CERT_NONE
14
17
 
15
18
  PROBE_PATHS = [
16
19
  "/", "/robots.txt", "/.env", "/.git/HEAD", "/config.json", "/config.js",
@@ -24,6 +27,25 @@ PROBE_PATHS = [
24
27
  "/backup.zip", "/db.sql", "/phpinfo.php", "/info.php", "/server-status",
25
28
  ]
26
29
 
30
+ def parse_target(raw):
31
+ """接受域名 / host:port / 完整 URL,返回 (host[:port], scheme, path_prefix);非法输入抛 ValueError"""
32
+ raw = (raw or "").strip()
33
+ if not raw:
34
+ raise ValueError("空目标")
35
+ if "://" in raw:
36
+ sp = urllib.parse.urlsplit(raw)
37
+ if sp.scheme not in ("http", "https"):
38
+ raise ValueError(f"不支持的协议: {sp.scheme}")
39
+ if not sp.netloc or not re.fullmatch(r"[A-Za-z0-9._\-]+(:\d{1,5})?", sp.netloc):
40
+ raise ValueError(f"非法主机名: {sp.netloc}")
41
+ return sp.netloc, sp.scheme, sp.path.rstrip("/")
42
+ hostport, _, rest = raw.partition("/")
43
+ if not re.fullmatch(r"[A-Za-z0-9._\-]+(:\d{1,5})?", hostport):
44
+ raise ValueError(f"非法主机: {hostport}")
45
+ prefix = ("/" + rest.rstrip("/")) if rest else ""
46
+ return hostport, "https", prefix
47
+
48
+
27
49
  def req(url, method="GET", data=None, timeout=12, headers=None):
28
50
  h = {"User-Agent": UA, "Accept": "*/*"}
29
51
  if headers: h.update(headers)
@@ -74,11 +96,17 @@ def _pool_map(items, fn, workers):
74
96
 
75
97
  def scan(domain):
76
98
  out = {"domain": domain, "hits": [], "errors": []}
99
+ try:
100
+ host, scheme, prefix = parse_target(domain)
101
+ except ValueError as e:
102
+ out["root_status"] = 0
103
+ out["errors"].append(f"目标非法: {e}")
104
+ return out
77
105
  # 指纹(https 失败自动回落 http;都不可达则快速返回)
78
- base = f"https://{domain}"
106
+ base = f"{scheme}://{host}{prefix}"
79
107
  st, hd, bd = req(base + "/")
80
- if st == 0:
81
- base = f"http://{domain}"
108
+ if st == 0 and scheme == "https":
109
+ base = f"http://{host}{prefix}"
82
110
  st, hd, bd = req(base + "/")
83
111
  if st == 0:
84
112
  out["root_status"] = 0
@@ -0,0 +1,40 @@
1
+ """Offline regression tests for the additive tool inventory."""
2
+ import json
3
+ import subprocess
4
+ import sys
5
+ import unittest
6
+ from pathlib import Path
7
+
8
+ ROOT = Path(__file__).resolve().parents[1]
9
+ SCRIPT = ROOT / 'scripts' / 'tool_inventory.py'
10
+ TASKS = ['package_audit','static_code','dependencies','binary','android','pcap','web_static','authorized_http','forensics']
11
+
12
+
13
+ class InventoryTests(unittest.TestCase):
14
+ def test_all_tasks_json(self):
15
+ for task in TASKS:
16
+ with self.subTest(task=task):
17
+ run = subprocess.run([sys.executable, str(SCRIPT), '--root', str(ROOT), '--task', task, '--json'], capture_output=True, text=True, check=True)
18
+ payload = json.loads(run.stdout)
19
+ self.assertEqual(payload['task'], task)
20
+ self.assertEqual(payload['result_state'], 'inventory_only')
21
+ self.assertTrue(payload['tools'])
22
+ self.assertTrue(all(isinstance(x['installed'],bool) for x in payload['tools']))
23
+
24
+ def test_bad_task_rejected(self):
25
+ run = subprocess.run([sys.executable, str(SCRIPT), '--task', 'unauthorized'], capture_output=True, text=True)
26
+ self.assertNotEqual(run.returncode, 0)
27
+
28
+ def test_broken_root_rejected(self):
29
+ run = subprocess.run([sys.executable, str(SCRIPT), '--root', '/nonexistent/a-b-c-directory'], capture_output=True, text=True)
30
+ self.assertNotEqual(run.returncode, 0)
31
+
32
+ def test_documents_linked(self):
33
+ skill = (ROOT / 'SKILL.md').read_text(encoding='utf-8')
34
+ for item in ['agent-routing.md','tool-selection.md','verification-gates.md','framework-comparison-2026.md','tool_inventory.py']:
35
+ self.assertIn(item, skill)
36
+ self.assertEqual(skill.count('<!-- module:'), 33)
37
+
38
+
39
+ if __name__ == '__main__':
40
+ unittest.main()