dsh-capdiff 0.0.0-stage → 0.1.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/DESIGN.md +201 -0
- package/LICENSE +202 -0
- package/README.md +209 -2
- package/package.json +45 -4
- package/src/cli.js +257 -0
- package/src/core.js +914 -0
package/DESIGN.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# DESIGN — dsh-capdiff
|
|
2
|
+
|
|
3
|
+
> 状态:MVP `0.1.0`,未发布。实现只用 Node 内建模块,无第三方依赖。
|
|
4
|
+
> 本文说明「为什么这样实现」,以及它**故意不做**什么。
|
|
5
|
+
|
|
6
|
+
## 1. 目标
|
|
7
|
+
|
|
8
|
+
给定一个 DSH 插件目录和一份 manifest,回答:
|
|
9
|
+
|
|
10
|
+
1. 代码里静态可见的能力,manifest 声明了吗?(`undeclared`)
|
|
11
|
+
2. manifest 声明的能力,在扫描文件里看得到吗?(`unused`)
|
|
12
|
+
3. 两边都有的能力是哪些?(`matched`)
|
|
13
|
+
|
|
14
|
+
输出三种形态:人类摘要、JSON、SARIF 2.1.0。作为 CI/PR 的**第一道提示**,而不是安全判定。
|
|
15
|
+
|
|
16
|
+
## 2. 非目标
|
|
17
|
+
|
|
18
|
+
- 不做运行时沙箱、不执行插件代码、不发起网络请求。
|
|
19
|
+
- 不做完整 JavaScript 解析器 / AST 类型检查。
|
|
20
|
+
- 不验证 manifest 的真实性、签名或来源;manifest 在本工具眼里只是不可信输入。
|
|
21
|
+
- 不证明插件安全,不输出「checked / safe」结论。
|
|
22
|
+
- 不做依赖树分析(不递归 `node_modules`,也不分析传递依赖)。
|
|
23
|
+
|
|
24
|
+
## 3. 架构与流水线
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
CLI (src/cli.js)
|
|
28
|
+
parseArgs / read manifest / print
|
|
29
|
+
│
|
|
30
|
+
▼
|
|
31
|
+
core (src/core.js)
|
|
32
|
+
scanTarget(target)
|
|
33
|
+
├─ collectSourceFiles() 递归收集 .js/.mjs/.cjs
|
|
34
|
+
├─ analyzeSource(code)
|
|
35
|
+
│ ├─ extractImports() 正则 + 注释/字符串遮蔽
|
|
36
|
+
│ ├─ collectCapabilityEvidence()
|
|
37
|
+
│ └─ mapCapabilities() 去重、保持首次出现顺序
|
|
38
|
+
└─ 聚合跨文件的 capabilities + firstEvidence
|
|
39
|
+
parseDeclared(manifest)
|
|
40
|
+
diffCapabilities(declared, actual)
|
|
41
|
+
buildFindings(diff, firstEvidence)
|
|
42
|
+
toSarif(findings)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`src/core.js` 只 import `node:fs/promises` 与 `node:path`,无第三方包;`src/cli.js` 只做参数解析、文件读取与输出,业务逻辑都在 core。因此 `node --test` 可离线运行。
|
|
46
|
+
|
|
47
|
+
## 4. 数据模型
|
|
48
|
+
|
|
49
|
+
```js
|
|
50
|
+
// extractImports() 的一项
|
|
51
|
+
{ specifier: 'node:fs', kind: 'static', index: 42 }
|
|
52
|
+
// kind: 'static' | 'dynamic' | 'require' | 're-export'
|
|
53
|
+
|
|
54
|
+
// collectCapabilityEvidence() 的一项(内部/导出)
|
|
55
|
+
{
|
|
56
|
+
capability: 'env',
|
|
57
|
+
origin: 'code', // 'import' | 'code'
|
|
58
|
+
kind: 'usage', // import 时保留静态种类
|
|
59
|
+
specifier: null, // code 证据没有 specifier
|
|
60
|
+
evidence: 'process.env',
|
|
61
|
+
index: 120,
|
|
62
|
+
line: 7
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// diffCapabilities()
|
|
66
|
+
{
|
|
67
|
+
matched: ['filesystem'],
|
|
68
|
+
undeclared: ['env'],
|
|
69
|
+
unused: ['network']
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// buildFindings() 的一项
|
|
73
|
+
{ kind: 'undeclared', capability: 'env', file: 'src/a.js', line: 7,
|
|
74
|
+
evidence: 'process.env', message: '...' }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
所有数组都保持确定性顺序:import/能力证据按源码偏移,`matched`/`unused` 按声明顺序,`undeclared` 按实际检测顺序。重复能力折叠为一项。
|
|
78
|
+
|
|
79
|
+
## 5. 静态提取(extractImports)
|
|
80
|
+
|
|
81
|
+
| kind | 正则覆盖 |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `static` | `import x from 'm'`、`import {a} from 'm'`、`import * as n from 'm'`、`import 'm'` |
|
|
84
|
+
| `dynamic` | `import('m')`(字符串字面量) |
|
|
85
|
+
| `require` | `require('m')`(字符串字面量) |
|
|
86
|
+
| `re-export` | `export {a} from 'm'`、`export * from 'm'`、`export * as n from 'm'` |
|
|
87
|
+
|
|
88
|
+
实现细节:
|
|
89
|
+
|
|
90
|
+
- 关键字前用 `(?<![\w$.])`,避免把 `foo.import(...)` / `foo.require(...)` 当作模块加载。
|
|
91
|
+
- `import` 子句字符集限制为标识符、空白、`{}`、`,`、`*`,避免在畸形源码里 `[\s\S]*?` 跨行吞掉无关文本。
|
|
92
|
+
- 副作用 import 的 `(?!\()` 防止把 `import ('m')` 重复记成 static 与 dynamic。
|
|
93
|
+
- `maskNonCode()` 把注释、字符串、模板字面量内容替换成空格,**长度与换行不变**。所有 match 再检查起始字符在原文与遮蔽文本中是否一致,从而跳过注释/字符串里的假 import 与假 `eval`。
|
|
94
|
+
- `index` 是语句关键字在源码中的 UTF-16 偏移;行号由 `lineOfIndex()` 换算(1-based)。
|
|
95
|
+
|
|
96
|
+
已知盲区:计算出的 specifier、`createRequire`、正则字面量、模板 `${...}` 中的代码(整段模板被遮蔽)、TypeScript/JSX。详见 §10。
|
|
97
|
+
|
|
98
|
+
## 6. 能力映射
|
|
99
|
+
|
|
100
|
+
`CAPABILITY_MAP` 是显式表:
|
|
101
|
+
|
|
102
|
+
- `node:fs` / `fs` / `fs/promises` → `filesystem`
|
|
103
|
+
- `node:net` / `net` / `node:http(s)` / `http(s)` / `http2` / `tls` / `dgram` / `fetch` → `network`
|
|
104
|
+
- `node:child_process` / `child_process` → `process`
|
|
105
|
+
- `node:crypto` / `crypto` → `crypto`
|
|
106
|
+
- 语言级:`process.env` → `env`;`eval`、`new Function` → `eval`
|
|
107
|
+
|
|
108
|
+
查找顺序:精确匹配 → 去掉 `node:` 再匹配 → 取 specifier 第一段匹配(因此 `node:fs/promises` 继承 `fs`)→ 否则 `unknown`。`unknown` 是有意保留的兜底:它既覆盖第三方包,也覆盖相对路径 import,提醒复核而不是假装不存在。
|
|
109
|
+
|
|
110
|
+
语言级检测对 `process.env`、`process['env']`、`eval(...)`、`new Function(...)` 生效;`eval` 用 lookbehind 排除 `obj.eval(...)`。这仍是正则,不处理 `(0, eval)`、`globalThis.eval`、`const { env } = process` 等变体。
|
|
111
|
+
|
|
112
|
+
## 7. 声明解析与规范化
|
|
113
|
+
|
|
114
|
+
`parseDeclared()` 支持:
|
|
115
|
+
|
|
116
|
+
- `{ dsh: { capabilities: [...] } }`
|
|
117
|
+
- `{ dsh: { capabilities: { network: true, filesystem: false } } }`(truthy 才声明)
|
|
118
|
+
- `{ capabilities: [...] }`(脚本便利)
|
|
119
|
+
- 直接数组
|
|
120
|
+
- 数组元素 `{ capability | name | id | type }`,或 `{ network: true }`
|
|
121
|
+
|
|
122
|
+
规范化规则:
|
|
123
|
+
|
|
124
|
+
1. 去空白、转小写;
|
|
125
|
+
2. 精确命中 `CAPABILITY_MAP` 的 specifier 先映射(避免 `node:child_process` 中的 `_` 被折叠破坏);
|
|
126
|
+
3. 空白/下划线折叠为 `-` 后查规范名与别名表(`fs`→`filesystem`、`http`→`network`、`environment_variables`→`env` 等);
|
|
127
|
+
4. 未知自定义名保留为小写 token,不擅自归入 `unknown`。
|
|
128
|
+
|
|
129
|
+
声明顺序保留,重复项折叠。`diffCapabilities()` 对 declared/actual 再做一次同样的规范化,所以手写 `'fs'` 与 `'filesystem'` 能正确匹配。
|
|
130
|
+
|
|
131
|
+
## 8. 差异与 SARIF
|
|
132
|
+
|
|
133
|
+
`diffCapabilities()` 是纯集合运算:
|
|
134
|
+
|
|
135
|
+
- `matched = declared ∩ actual`
|
|
136
|
+
- `undeclared = actual − declared`
|
|
137
|
+
- `unused = declared − actual`
|
|
138
|
+
|
|
139
|
+
`buildFindings()` 把 `undeclared` 关联到该能力的首个证据(文件 + 行 + 触发文本),把 `unused` 关联到 manifest(若 CLI 已知)。`toSarif()` 输出:
|
|
140
|
+
|
|
141
|
+
- `version: "2.1.0"`、`$schema`;
|
|
142
|
+
- `runs[0].tool.driver.name = "dsh-capdiff"`;
|
|
143
|
+
- 规则 `dsh-capdiff/undeclared`(level `warning`)与 `dsh-capdiff/unused`(level `note`);
|
|
144
|
+
- 每结果带 `ruleId`、`ruleIndex`、`message.text`、`properties.capability/kind`;
|
|
145
|
+
- 有文件时带 `physicalLocation.artifactLocation.uri`,有行号时带 `region.startLine`。
|
|
146
|
+
|
|
147
|
+
为什么 `undeclared` 不是 `error`:静态启发式有误报,且 manifest 可能只是尚未更新。工具应提示复核,不应把启发式当 blocker;CI 可用 JSON 的 `diff.undeclared` 自定义退出策略。
|
|
148
|
+
|
|
149
|
+
## 9. CLI contract
|
|
150
|
+
|
|
151
|
+
- `--json` 与 `--sarif` 互斥(同时给出 → exit 1),避免输出格式歧义。
|
|
152
|
+
- 未给出 `--manifest`:声明视为空集合,实际检测全部进入 `undeclared`;不自动猜 manifest 路径,避免错误归属。
|
|
153
|
+
- 目标不存在/不是可读路径、manifest 读不到或非法 JSON → stderr + exit 1。
|
|
154
|
+
- 扫描成功但存在 findings → 仍 exit 0;是否失败交给 CI 判断。
|
|
155
|
+
- 人类输出固定包含 heuristic 免责声明;JSON 的 `findings` 保留机器可读的 file/line/evidence。
|
|
156
|
+
|
|
157
|
+
## 10. 误报 / 漏报模型
|
|
158
|
+
|
|
159
|
+
| 类型 | 例子 | 结果 |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| 误报 | 对象/类方法 `eval() {}` 被当作 `eval` 调用 | 多报 `undeclared eval` |
|
|
162
|
+
| 误报 | 类型/死代码中的 import、仅测试用的 `node:fs` | 多报 |
|
|
163
|
+
| 误报 | manifest 用未收录的自定义名声明已知能力 | 可能出现 `undeclared` + `unused` 并存 |
|
|
164
|
+
| 漏报 | `import(name)`、`require(name)`、`createRequire()` | 看不到 |
|
|
165
|
+
| 漏报 | 模板中的 `${process.env.X}`(整段模板被遮蔽) | 看不到 `env` |
|
|
166
|
+
| 漏报 | `globalThis.process.env`、`const { env } = process`、`process['e'+'nv']` | 看不到 |
|
|
167
|
+
| 漏报 | `eval` 别名、`Function(...)`(不带 `new`)、`node:vm`、native addon | 看不到 |
|
|
168
|
+
| 漏报 | 运行时从配置/网络加载的模块 | 看不到 |
|
|
169
|
+
|
|
170
|
+
因此 `matched` 只表示「声明与静态可见集合一致」;0 findings 只表示「当前启发式没发现差异」。两者都**不是**安全结论,也不覆盖「声明了但实际滥用」的情形。
|
|
171
|
+
|
|
172
|
+
## 11. 安全与信任边界
|
|
173
|
+
|
|
174
|
+
- 输入源码与 manifest 都按不可信文本处理;CLI 只读文件,不执行、不 eval、不联网。
|
|
175
|
+
- 本工具不验证 manifest 签名;信任链是另一个问题。若需要内容哈希/签名/撤销,应配合 `testatum-attest` 一类工具(本仓库另一包,未在此依赖)。
|
|
176
|
+
- SARIF 中的路径可能暴露本地目录结构;共享报告前请自行裁剪(CLI 不自动做绝对/相对路径策略之外的脱敏)。
|
|
177
|
+
|
|
178
|
+
## 12. 测试
|
|
179
|
+
|
|
180
|
+
`test/` 共 26 个 `node:test` 用例:
|
|
181
|
+
|
|
182
|
+
- `core.test.js`:静态/动态/require/re-export 提取、注释与字符串过滤、映射表、别名、`env`/`eval`、声明解析、diff、行号、SARIF、findings。
|
|
183
|
+
- `scan.test.js`:临时目录递归、扩展名过滤、`node_modules`/`.git` 跳过、跨文件证据聚合、空目录。
|
|
184
|
+
- `cli.test.js`:`--help`/`--version`、目录 + `--json`、`--sarif`、非法 manifest、互斥参数。
|
|
185
|
+
|
|
186
|
+
临时目录使用 `os.tmpdir()` + `mkdtemp`,测试结束在 `t.after` 中先断言路径位于 tmpdir 下再 `rm -rf`。验收命令:
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
cd packages/dsh-capdiff
|
|
190
|
+
node --check src/*.js
|
|
191
|
+
node --test test/*.test.js
|
|
192
|
+
node src/cli.js --help
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## 13. 未来工作(不阻塞 0.1.0)
|
|
196
|
+
|
|
197
|
+
- 可选 AST 解析器(仍保持默认零依赖)以降低误报/漏报。
|
|
198
|
+
- 可配置 ignore glob、测试目录排除、baseline 与 SARIF fingerprints。
|
|
199
|
+
- 扩展语言级模式:`globalThis.process.env`、解构、`node:vm`、全局 `fetch`。
|
|
200
|
+
- 校验 manifest schema / 版本,报告未知声明名。
|
|
201
|
+
- 与 attestation 工具联动:把 manifest 签名状态带进报告。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/README.md
CHANGED
|
@@ -1,3 +1,210 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-capdiff
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> 社区项目,**与 DeepSeek(深度求索)及 DeepSeek Harness 官方无隶属关系、无赞助、无背书**。
|
|
4
|
+
> Apache-2.0,版本 `0.1.0`,**尚未发布到 npm**。
|
|
5
|
+
|
|
6
|
+
## 是什么
|
|
7
|
+
|
|
8
|
+
`dsh-capdiff` 是一个**静态能力差异扫描器**:读取 DSH 插件 manifest 里的 `dsh.capabilities`,扫描源码中**静态可见**的模块引用与语言级能力使用,然后报告:
|
|
9
|
+
|
|
10
|
+
| 结果 | 含义 |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `matched` | manifest 声明了,扫描也看到了 |
|
|
13
|
+
| `undeclared` | 代码里静态可见,但 manifest 没声明 |
|
|
14
|
+
| `unused` | manifest 声明了,但在被扫描文件里没看到 |
|
|
15
|
+
|
|
16
|
+
它**不执行**插件代码。它输出的是供人复核的候选差异,**不是安全审计,也不声称 `checked=safe`**。
|
|
17
|
+
|
|
18
|
+
## 为什么
|
|
19
|
+
|
|
20
|
+
DSH 插件 manifest 可以声明 `dsh.capabilities`,但声明是手写的,代码里实际 import 了什么往往没人对照。`dsh-capdiff` 用零依赖的正则启发式做第一道低成本检查:
|
|
21
|
+
|
|
22
|
+
- PR / 发版前看一眼「声明和 import 是否对得上」;
|
|
23
|
+
- 把差异转成 SARIF,接进代码扫描面板;
|
|
24
|
+
- 把「声明了但没看到」的项当成清理提示,而不是结论。
|
|
25
|
+
|
|
26
|
+
## 安装
|
|
27
|
+
|
|
28
|
+
本版本未发布,直接从源码运行(需要 Node `^22.19.0 || >=24.9.0`):
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
node packages/dsh-capdiff/src/cli.js <file-or-dir> [--manifest path] [--json|--sarif]
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
作为 npm `bin` 使用(本地 `npm link` 或在该包目录 `npm install -g .` 之后):
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
dsh-capdiff ./my-plugin --manifest ./my-plugin/plugin.json
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
离线测试:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
cd packages/dsh-capdiff && npm test
|
|
44
|
+
# 或仓库根目录:
|
|
45
|
+
node --test packages/dsh-capdiff/test/*.test.js
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## 用法
|
|
49
|
+
|
|
50
|
+
```text
|
|
51
|
+
dsh-capdiff <file-or-dir> [options]
|
|
52
|
+
|
|
53
|
+
--manifest <path> manifest JSON(含 dsh.capabilities)
|
|
54
|
+
--json 输出机器可读 JSON
|
|
55
|
+
--sarif 输出 SARIF 2.1.0
|
|
56
|
+
-h, --help 帮助
|
|
57
|
+
-V, --version 版本(输出 `dsh-capdiff 0.1.0` 并 exit 0)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
- 目标是目录时递归扫描 `.js` / `.mjs` / `.cjs`,跳过任意层级的 `node_modules` 与 `.git`,不跟随 symlink。
|
|
61
|
+
- 未传 `--manifest` 时声明集合为空,所有检测到的能力都会成为 `undeclared`;人类可读输出会提示这一点。
|
|
62
|
+
- 是否在 CI 中失败由调用方根据 `diff.undeclared` 或 SARIF 决定,本工具不把 findings 当作失败。
|
|
63
|
+
|
|
64
|
+
退出码:
|
|
65
|
+
|
|
66
|
+
| 码 | 含义 |
|
|
67
|
+
|---|---|
|
|
68
|
+
| `0` | 扫描完成(**即使有 findings**) |
|
|
69
|
+
| `1` | 保留:本工具没有“业务否定”状态 |
|
|
70
|
+
| `2` | 用法、输入、manifest 或 IO 错误(无参数、未知选项、缺文件) |
|
|
71
|
+
|
|
72
|
+
### manifest 格式
|
|
73
|
+
|
|
74
|
+
数组:
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"name": "example-plugin",
|
|
79
|
+
"dsh": {
|
|
80
|
+
"capabilities": ["filesystem", "network"]
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
对象(truthy 表示声明,`false`/`0`/`null` 表示未声明):
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"dsh": {
|
|
90
|
+
"capabilities": {
|
|
91
|
+
"filesystem": true,
|
|
92
|
+
"network": {
|
|
93
|
+
"outbound": ["api.example.com"]
|
|
94
|
+
},
|
|
95
|
+
"eval": false
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
为了方便脚本调用,`manifest.capabilities` 和直接传入的声明数组也会被接受;数组元素除了字符串,也可以是 `{ "capability": "network" }` / `{ "name": "network" }` 形式的对象。
|
|
102
|
+
|
|
103
|
+
### 能力映射
|
|
104
|
+
|
|
105
|
+
| capability | 静态触发示例 |
|
|
106
|
+
|---|---|
|
|
107
|
+
| `filesystem` | `import fs from 'node:fs'`、`require('fs/promises')` |
|
|
108
|
+
| `network` | `node:net`、`node:http`、`node:https`、`node:http2`、`node:tls`、`node:dgram`;`fetch` 仅作为 import specifier 映射,全局 `fetch(...)` 调用目前不扫描 |
|
|
109
|
+
| `process` | `node:child_process` |
|
|
110
|
+
| `env` | `process.env`、`process['env']` |
|
|
111
|
+
| `eval` | `eval(...)`、`new Function(...)` |
|
|
112
|
+
| `crypto` | `node:crypto` |
|
|
113
|
+
| `unknown` | 映射表之外的 import(含相对路径、第三方包、其他内建模块) |
|
|
114
|
+
|
|
115
|
+
声明侧会做有限规范化(大小写、`_`/空格、常见别名),例如 `fs` → `filesystem`、`http` → `network`、`child_process` → `process`、`environment_variables` → `env`。`unknown` 是兜底类别,不是真实能力;相对路径 import 也会落到 `unknown`,通常应结合上下文判断,或显式声明 `unknown`。
|
|
116
|
+
|
|
117
|
+
### 输出
|
|
118
|
+
|
|
119
|
+
人类可读摘要(默认):
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
dsh-capdiff 0.1.0 - static capability diff (heuristic)
|
|
123
|
+
Target: examples/my-plugin
|
|
124
|
+
Manifest: examples/my-plugin/plugin.json
|
|
125
|
+
Files: 2 source file(s) scanned
|
|
126
|
+
Declared: filesystem, network
|
|
127
|
+
Detected: env, filesystem, network
|
|
128
|
+
|
|
129
|
+
Undeclared (1) - used in code, absent from manifest:
|
|
130
|
+
- env (src/index.js:12, process.env)
|
|
131
|
+
|
|
132
|
+
Matched (2): filesystem, network
|
|
133
|
+
|
|
134
|
+
Note: static heuristic only - "matched" and zero findings are not proof of safety.
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`--json` 输出目标、文件数、`declared`、`actual`、`diff` 与 `findings`:
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
141
|
+
"command": "scan",
|
|
142
|
+
"ok": true,
|
|
143
|
+
"tool": "dsh-capdiff",
|
|
144
|
+
"version": "0.1.0",
|
|
145
|
+
"target": "/abs/path/my-plugin",
|
|
146
|
+
"manifest": "/abs/path/my-plugin/plugin.json",
|
|
147
|
+
"filesScanned": 2,
|
|
148
|
+
"declared": ["filesystem", "network"],
|
|
149
|
+
"actual": ["env", "filesystem", "network"],
|
|
150
|
+
"diff": {
|
|
151
|
+
"matched": ["filesystem", "network"],
|
|
152
|
+
"undeclared": ["env"],
|
|
153
|
+
"unused": []
|
|
154
|
+
},
|
|
155
|
+
"findings": [
|
|
156
|
+
{
|
|
157
|
+
"kind": "undeclared",
|
|
158
|
+
"capability": "env",
|
|
159
|
+
"file": "src/index.js",
|
|
160
|
+
"line": 12,
|
|
161
|
+
"evidence": "process.env",
|
|
162
|
+
"message": "..."
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
`--sarif` 输出最小 SARIF 2.1.0:`runs[0].tool.driver.rules` 只有 `dsh-capdiff/undeclared`(`warning`)与 `dsh-capdiff/unused`(`note`)两条规则,按需出现;`locations` 指向首个证据文件与行号(`unused` 指向 manifest,若已知)。
|
|
169
|
+
|
|
170
|
+
## 限制
|
|
171
|
+
|
|
172
|
+
**这是静态启发式,不是解析器、不是沙箱、不是审计。**
|
|
173
|
+
|
|
174
|
+
- **只报「静态可见」**。以下都可能完全看不到:
|
|
175
|
+
- 计算出的 specifier:`import(name)`、`require(name)`;
|
|
176
|
+
- `createRequire()`、`node:vm`、native addon、子进程里再 require;
|
|
177
|
+
- `globalThis['process']['env']`、`const { env } = process`、`process['e' + 'nv']`;
|
|
178
|
+
- 配置驱动 / 插件加载器在运行时加载的模块;
|
|
179
|
+
- 传递依赖里的能力(本工具只扫描目标目录内的源码)。
|
|
180
|
+
- **`unused` 不是死代码证明**:能力可能通过动态方式使用,或只在未扫描的分支/文件中使用。
|
|
181
|
+
- **`matched` / 0 findings 不是安全证明**:manifest 本身可能漏声明、错声明或不可信;本工具不验证 manifest 的签名/来源,也不验证能力使用是否安全。
|
|
182
|
+
- **误报**:正则不解析 AST;对象方法 `eval() {}`、类方法、注释/字符串边界、正则字面量等场景可能产生误报。代码中的注释与字符串内容会被遮蔽,但模板字面量整体被遮蔽,`${process.env.X}` 这类用法会漏报;正则字面量中的引号也可能干扰遮蔽。
|
|
183
|
+
- **范围**:只扫描 `.js` / `.mjs` / `.cjs`;不扫描 `.ts` / `.jsx` / `.json` / native 文件;跳过 `node_modules`/`.git`,不跟随 symlink;不读取 `package.json` 的 `imports`/`exports` 语义。
|
|
184
|
+
- **声明规范化是有限的**:未知的自定义能力名会保留(小写、空白/下划线折叠),不会自动变成某个已知类别。
|
|
185
|
+
|
|
186
|
+
因此:**「没有差异」只能说明「这两个集合在当前启发式下一致」,不能说明插件安全。**
|
|
187
|
+
|
|
188
|
+
## 命名核验
|
|
189
|
+
|
|
190
|
+
核验日期:**2026-11-14**(Asia/Shanghai)。
|
|
191
|
+
|
|
192
|
+
- npm Registry:`GET https://registry.npmjs.org/dsh-capdiff` → **HTTP 404**(`{"error":"Not found"}`),当前 npm 上无同名包。
|
|
193
|
+
- GitHub:`GET https://api.github.com/search/repositories?q=dsh-capdiff+in:name` → **`total_count: 0`**,当前仓库名搜索无同名仓库。
|
|
194
|
+
|
|
195
|
+
核验命令可复现:
|
|
196
|
+
|
|
197
|
+
```sh
|
|
198
|
+
curl -sS -o /dev/null -w '%{http_code}\n' https://registry.npmjs.org/dsh-capdiff
|
|
199
|
+
curl -sS 'https://api.github.com/search/repositories?q=dsh-capdiff+in:name'
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
以上只表示该日期这两个索引中没有同名项,**不构成商标或名称专有性判断**;发布前应重新核验。
|
|
203
|
+
|
|
204
|
+
## 非官方声明
|
|
205
|
+
|
|
206
|
+
这是一个社区工具,不隶属于 DeepSeek(深度求索)或 DeepSeek Harness 官方,也未获得其赞助或背书。“DeepSeek Harness”“DSH”仅用于说明兼容对象。
|
|
207
|
+
|
|
208
|
+
## License
|
|
209
|
+
|
|
210
|
+
Apache-2.0,见 [LICENSE](./LICENSE)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-capdiff",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Static capability diff scanner for DSH plugin manifests: declared dsh.capabilities vs imports and eval/env use found in code, with JSON and SARIF output.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"bin": {
|
|
8
|
+
"dsh-capdiff": "./src/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/core.js",
|
|
12
|
+
"./cli": "./src/cli.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"src",
|
|
16
|
+
"README.md",
|
|
17
|
+
"DESIGN.md",
|
|
18
|
+
"LICENSE"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": "^22.19.0 || >=24.9.0"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"test": "node --test test/*.test.js"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"dsh",
|
|
28
|
+
"deepseek-harness",
|
|
29
|
+
"capabilities",
|
|
30
|
+
"plugin-security",
|
|
31
|
+
"sarif",
|
|
32
|
+
"static-analysis"
|
|
33
|
+
],
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "git+https://github.com/PerryLink/dsh-next-suite.git",
|
|
37
|
+
"directory": "packages/dsh-capdiff"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/PerryLink/dsh-next-suite/tree/main/packages/dsh-capdiff#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/PerryLink/dsh-next-suite/issues"
|
|
42
|
+
},
|
|
43
|
+
"author": "PerryLink",
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
}
|
|
47
|
+
}
|