dsh-code-security 0.5.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/docs/USAGE.en.md +1 -1
- package/docs/USAGE.md +1 -1
- package/lib/audit.js +3 -3
- package/lib/diff.d.ts +7 -0
- package/lib/diff.js +40 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
[历史英文记录](docs/CHANGELOG.en.md)
|
|
6
6
|
|
|
7
|
+
## 0.5.1(2026-10-20)
|
|
8
|
+
|
|
9
|
+
- 修复审查外来仓库时可能执行对方 `.git/config` 命令的问题:所有 git 调用加上 `-c core.fsmonitor=false`,`secure_diff` 与 `secure_scan` 的 `includeUntracked` 都不再触发被扫仓库的 fsmonitor 命令
|
|
10
|
+
- 修复 `filter.<驱动>.clean` 能把新增的危险行从 diff 里删掉、让检查误判「通过」的问题:diff 改用 `--attr-source=<空树>`(Git 2.40+),工作区 `.gitattributes` 不再生效,clean 过滤器既不执行也不参与内容比较;更老的 Git 退回只关闭 fsmonitor
|
|
11
|
+
- 新增 4 项回归测试:fsmonitor 不执行、clean 过滤器不执行且藏不掉 `eval(input)`、`--attr-source` 参数、`includeUntracked` 的 `ls-files` 路径;同步更新既有的 argv 断言
|
|
12
|
+
|
|
7
13
|
## 0.5.0(2026-10-17)
|
|
8
14
|
|
|
9
15
|
- 设置面板顶部新增「首次使用」分步引导:一次只讲一步,支持上一步/下一步/跳过,看完记在本机不再出现。
|
package/docs/USAGE.en.md
CHANGED
|
@@ -85,7 +85,7 @@ Policy file: `.code-security.json`.
|
|
|
85
85
|
{"version":1,"exclude":["vendor/**","generated/**"],"ignore":[{"ruleId":"SEC-206","file":"test/**","reason":"Not security-sensitive"}],"failOn":"high"}
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
Git runs with external diff/textconv disabled. Paths are relative to the session workspace. Staged scans read the index and only added-line findings are reported. Invalid arguments, Git refs and incomplete hunks preserve previous state.
|
|
88
|
+
Git runs with external diff/textconv disabled, and the reviewed repository's own executable config is neutralised too: every git call passes `-c core.fsmonitor=false`, and diff runs with `--attr-source=<empty tree>` so the worktree `.gitattributes` no longer applies (a `filter.<driver>.clean` is neither executed nor able to rewrite the diff). `--attr-source` needs Git 2.40+; older versions fall back to disabling fsmonitor only. Paths are relative to the session workspace. Staged scans read the index and only added-line findings are reported. Invalid arguments, Git refs and incomplete hunks preserve previous state.
|
|
89
89
|
|
|
90
90
|
## Development and licenses
|
|
91
91
|
|
package/docs/USAGE.md
CHANGED
|
@@ -88,7 +88,7 @@ OSV 2.7.0 当前实现使用 `缓存目录/osv-scalibr/npm/all.zip`(Python 使
|
|
|
88
88
|
}
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
Git 输出禁用外部 diff/textconv
|
|
91
|
+
Git 输出禁用外部 diff/textconv,并额外关掉被扫仓库自带的可执行配置:所有 git 调用都带 `-c core.fsmonitor=false`,diff 还用 `--attr-source=<空树>` 让 `.gitattributes` 不生效(`filter.<驱动>.clean` 既不执行、也无法改写 diff 内容)。`--attr-source` 需要 Git 2.40+,更老的版本会退回只关闭 fsmonitor 的调用。路径相对当前会话工作区。暂存检查读取 Git index;新增行才计入差异发现。错写参数、无效 Git ref 或不完整 diff 不会覆盖已有记录。
|
|
92
92
|
|
|
93
93
|
## 开发与许可
|
|
94
94
|
|
package/lib/audit.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { policyIgnores } from './policy.js';
|
|
4
|
-
import { parseDiff, runGitDiff, scanDiff } from './diff.js';
|
|
4
|
+
import { GIT_SAFE_CONFIG_ARGS, parseDiff, runGitDiff, scanDiff } from './diff.js';
|
|
5
5
|
import { isExcludedPath, scanPath } from './scanner.js';
|
|
6
6
|
import { mergeFindings, scanExternal } from './engines.js';
|
|
7
7
|
export function sourceEngines(value, defaults) {
|
|
@@ -39,7 +39,7 @@ export async function runSourceCheck(scope, cfg, cwd, runner, engines, policy, s
|
|
|
39
39
|
throw new Error('增量文件超出当前工作区');
|
|
40
40
|
let text;
|
|
41
41
|
if (scope.staged) {
|
|
42
|
-
const shown = await runner.run(['git', 'show', ':./' + file.file], { cwd, timeoutMs: 30000, signal });
|
|
42
|
+
const shown = await runner.run(['git', ...GIT_SAFE_CONFIG_ARGS, 'show', ':./' + file.file], { cwd, timeoutMs: 30000, signal });
|
|
43
43
|
if (shown.exitCode !== 0)
|
|
44
44
|
throw new Error('无法读取暂存区文件,请刷新后重试');
|
|
45
45
|
text = shown.stdout;
|
|
@@ -67,7 +67,7 @@ export async function runSourceCheck(scope, cfg, cwd, runner, engines, policy, s
|
|
|
67
67
|
throw new Error('扫描期间 Git 改动已变化,请重试');
|
|
68
68
|
}
|
|
69
69
|
if (scope.includeUntracked && !scope.staged) {
|
|
70
|
-
const untracked = await runner.run(['git', 'ls-files', '--others', '--exclude-standard', '-z', '--', scope.target ?? '.'], { cwd, signal, timeoutMs: 30000 });
|
|
70
|
+
const untracked = await runner.run(['git', ...GIT_SAFE_CONFIG_ARGS, 'ls-files', '--others', '--exclude-standard', '-z', '--', scope.target ?? '.'], { cwd, signal, timeoutMs: 30000 });
|
|
71
71
|
if (untracked.exitCode !== 0)
|
|
72
72
|
throw new Error('无法读取未跟踪文件列表');
|
|
73
73
|
if (untracked.stdout && !untracked.stdout.endsWith('\0'))
|
package/lib/diff.d.ts
CHANGED
|
@@ -24,6 +24,13 @@ export interface DiffScanResult {
|
|
|
24
24
|
findings: Finding[];
|
|
25
25
|
diff: string;
|
|
26
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* 屏蔽仓库自带配置里的可执行钩子。
|
|
29
|
+
*
|
|
30
|
+
* `-c core.fsmonitor=false`:否则 `git diff` / `git ls-files --others` 会执行
|
|
31
|
+
* 被扫仓库 `.git/config` 里的 fsmonitor 命令。
|
|
32
|
+
*/
|
|
33
|
+
export declare const GIT_SAFE_CONFIG_ARGS: readonly string[];
|
|
27
34
|
/** 运行 git diff(默认 HEAD,工作区未提交改动)。 */
|
|
28
35
|
export declare function runGitDiff(runner: ProcessRunner, cwd: string, base: string, target?: string, timeoutMs?: number, staged?: boolean): Promise<string>;
|
|
29
36
|
/** 解析 unified diff,返回每个文件的新增行与新行号。 */
|
package/lib/diff.js
CHANGED
|
@@ -6,18 +6,56 @@
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { createHash } from 'node:crypto';
|
|
8
8
|
import { isExcludedPath, scanLineSet } from './scanner.js';
|
|
9
|
+
/**
|
|
10
|
+
* 屏蔽仓库自带配置里的可执行钩子。
|
|
11
|
+
*
|
|
12
|
+
* `-c core.fsmonitor=false`:否则 `git diff` / `git ls-files --others` 会执行
|
|
13
|
+
* 被扫仓库 `.git/config` 里的 fsmonitor 命令。
|
|
14
|
+
*/
|
|
15
|
+
export const GIT_SAFE_CONFIG_ARGS = ['-c', 'core.fsmonitor=false', '-c', 'core.untrackedCache=false'];
|
|
16
|
+
/**
|
|
17
|
+
* 空树对象 ID:git 对空 tree 的内容做对象头哈希("tree 0\0")。
|
|
18
|
+
*
|
|
19
|
+
* 现算而不是写死常量,既跟随仓库的哈希算法,也不会让一串十六进制被
|
|
20
|
+
* 密钥类规则当成可疑字符串。
|
|
21
|
+
*/
|
|
22
|
+
function emptyTreeOidFor(format) {
|
|
23
|
+
return createHash(format).update('tree 0\u0000').digest('hex');
|
|
24
|
+
}
|
|
25
|
+
const emptyTreeCache = new Map();
|
|
26
|
+
/**
|
|
27
|
+
* 取当前仓库的空树对象 ID,用作 `--attr-source`。
|
|
28
|
+
*
|
|
29
|
+
* 把属性查询指向空树后,被扫仓库工作区里的 `.gitattributes` 不再生效,
|
|
30
|
+
* 因此 `filter.<驱动>.clean` 不会被调用,diff 也不会被 clean 输出替换。
|
|
31
|
+
*/
|
|
32
|
+
async function emptyTreeOid(runner, cwd, timeoutMs) {
|
|
33
|
+
const cached = emptyTreeCache.get(cwd);
|
|
34
|
+
if (cached !== undefined)
|
|
35
|
+
return cached;
|
|
36
|
+
const result = await runner.run(['git', ...GIT_SAFE_CONFIG_ARGS, 'rev-parse', '--show-object-format'], { cwd, timeoutMs: Math.min(timeoutMs, 10000) });
|
|
37
|
+
const oid = emptyTreeOidFor(result.exitCode === 0 && /sha256/i.test(result.stdout) ? 'sha256' : 'sha1');
|
|
38
|
+
emptyTreeCache.set(cwd, oid);
|
|
39
|
+
return oid;
|
|
40
|
+
}
|
|
9
41
|
/** 运行 git diff(默认 HEAD,工作区未提交改动)。 */
|
|
10
42
|
export async function runGitDiff(runner, cwd, base, target, timeoutMs = 30000, staged = false) {
|
|
11
43
|
if (typeof base !== 'string' || base.trim() === '' || base.trim().startsWith('-') || base.includes('\0')) {
|
|
12
44
|
throw new Error('参数 base 必须是非空的 Git ref,不能是 Git 命令选项。');
|
|
13
45
|
}
|
|
14
|
-
const
|
|
46
|
+
const attrSource = '--attr-source=' + await emptyTreeOid(runner, cwd, timeoutMs);
|
|
47
|
+
const argv = ['git', ...GIT_SAFE_CONFIG_ARGS, attrSource, 'diff'];
|
|
15
48
|
if (staged)
|
|
16
49
|
argv.push('--cached');
|
|
17
50
|
argv.push('--no-ext-diff', '--no-textconv', '--no-color', '--relative', '--src-prefix=a/', '--dst-prefix=b/', '--unified=3', '--output-indicator-new=+', '--output-indicator-old=-', '--output-indicator-context= ', base.trim(), '--');
|
|
18
51
|
if (target !== undefined && target.trim() !== '')
|
|
19
52
|
argv.push(target.trim());
|
|
20
|
-
|
|
53
|
+
let result = await runner.run(argv, { timeoutMs, cwd });
|
|
54
|
+
// git < 2.40 不认识 --attr-source:退回只关闭 fsmonitor 的调用(filter 加固缺席,
|
|
55
|
+
// 但不会因此拒绝整个扫描)。
|
|
56
|
+
if (result.exitCode !== 0 && /attr-source/i.test(result.stderr)) {
|
|
57
|
+
result = await runner.run(argv.filter((arg) => arg !== attrSource), { timeoutMs, cwd });
|
|
58
|
+
}
|
|
21
59
|
if (result.exitCode !== 0 && result.exitCode !== 1) {
|
|
22
60
|
throw new Error('git diff 失败(退出码 ' + String(result.exitCode) + '):' + result.stderr.trim().split(/\r?\n/).slice(-4).join(' | '));
|
|
23
61
|
}
|