dsh-code-security 0.3.6 → 0.3.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # 更新记录
2
+
3
+ [返回简介](README.md) · [使用说明](docs/USAGE.md) · [验证记录](docs/VALIDATION.md)
4
+
5
+ [历史英文记录](docs/CHANGELOG.en.md)
6
+
7
+ ## 0.3.7 (2026-10-05)
8
+
9
+ - 扫描、策略、报告和 git diff 使用当前会话工作区;支持取消并跳过目录联接。锁定合并历史与基线写入,损坏记录和无效策略明确报错并保留原文件。
10
+
11
+ ## 0.3.6 (2026-09-28)
12
+
13
+ - 更新官方 Harness 0.2.0-rc.1 的兼容声明和共同加载验证;运行时代码未变。验证范围见[验证记录](docs/VALIDATION.md)。
14
+
15
+ ## 0.3.5 (2026-09-28)
16
+
17
+ 扫描参数写错时明确报错,避免把 `paths`、`files` 或空目标误当成扫描整个工作区。修复标准模式中结果卡片误显示“0 个文件、未通过”的问题;文件数量、结论与问题列表现在使用实际扫描结果。
18
+
19
+ ## 更早的改动
20
+
21
+ 完整历史可查阅 [GitHub 提交记录](https://github.com/STARDUSTLC666/dsh-code-security/commits/master)。
package/README.en.md CHANGED
@@ -1,44 +1,46 @@
1
1
  # dsh-code-security
2
2
 
3
- ## 0.3.5 update (2026-09-28)
3
+ [中文](README.md)
4
4
 
5
- Rejects unknown scan arguments and empty targets before scanning or replacing state, preventing misspelled scopes from expanding to the whole workspace. Fixes standard-mode output incorrectly displaying zero files and a failed verdict: counts, verdicts and findings now come from the actual scan result.
5
+ ![dsh-code-security whale girl plugin cover](https://raw.githubusercontent.com/STARDUSTLC666/dsh-code-security/master/assets/cover-whale-girl.png)
6
6
 
7
- Validation host: Harness `0.2.0-rc.1` built from official sources (commit `407e65c8`) with Node `24.16.0` on 2026-09-28. All 31 plugin tests pass in an isolated environment; all 18 plugins mount together in one host registering 10 tools, with tool schemas and health-check contracts passing. No live ports or external services were exercised in this round.
7
+ Run local static security checks on code or Git changes and verify fixes.
8
8
 
9
- ![npm](https://img.shields.io/npm/v/dsh-code-security) ![downloads](https://img.shields.io/npm/dm/dsh-code-security) ![license](https://img.shields.io/github/license/STARDUSTLC666/dsh-code-security) ![stars](https://img.shields.io/github/stars/STARDUSTLC666/dsh-code-security?style=social)
9
+ [![npm](https://img.shields.io/npm/v/dsh-code-security)](https://www.npmjs.com/package/dsh-code-security) [![downloads](https://raw.githubusercontent.com/STARDUSTLC666/dsh-suite/npm-downloads/assets/dsh-code-security-downloads.svg)](https://www.npmjs.com/package/dsh-code-security)
10
10
 
11
- > Every agent code change passes a local security scan before delivery.
11
+ ## What it does
12
12
 
13
- A DeepSeek Harness plugin for AI code security review: deterministic rule engine, git-diff incremental review, fix-verify loop, and policy gate. Methodology borrows from Codex security skills (evidence-first findings, severity ordering, supply-chain layers). Zero runtime dependencies.
13
+ - Scan files, directories or Git diffs with locations and code evidence.
14
+ - Recheck fixes and manage baselines or scan policy.
15
+ - Export Markdown or SARIF reports and inspect dependency manifests.
14
16
 
15
- ## Compatibility
17
+ ## Install
16
18
 
17
- Verified with official `@deepseek-ai/dsh@0.1.5-rc.1` and Node `24.16.0` on 2026-09-11: all 18 components load alongside Modlens, with passing tool-schema, skill-registration and offline read-only invocation checks. Uses the `cordis.patch.yml` + `dsh.bundle.patch` bundle model. Node requirements match this Harness release: 22.19 or later within 22.x, or 24 or later. Live external-service workflows require separate configuration and validation.
19
+ In DSH Desktop, install `dsh-code-security` from the Plugins panel. If the bundled dsh command is available:
18
20
 
19
- 2026-09-13 fix: retain the service receiver when calling `subprocess.spawn`, preventing failures caused by passing the method as an unbound callback. Verified with official `0.1.5-rc.1` and `0.1.5-rc.2` on Node `24.16.0`. `secure_diff` reads a real isolated Git repository through the host subprocess service and reports the changed file and added line.
21
+ ```bash
22
+ dsh plugin --profile desktop add dsh-code-security
23
+ ```
20
24
 
21
- ## Tools
25
+ For the web version, replace `desktop` with `web`. Restart DSH after installation.
22
26
 
23
- | Tool | Purpose | Write |
24
- | :-- | :-- | :-- |
25
- | `secure_scan` | Scan files with CWE/severity/line/snippet evidence | state |
26
- | `secure_diff` | Review only added lines of git diff | state |
27
- | `secure_fix_verify` | Compare with baseline: closed / remaining / fresh | state |
28
- | `secure_report` | Aggregate by rule/file with gate verdict | no |
29
- | `secure_export` | Export SARIF 2.1.0 / Markdown | file write approval |
30
- | `secure_baseline` | Accept current findings as baseline; gate on new issues only | approval |
31
- | `secure_deps` | SBOM-lite: parse dependency manifests and version-risk flags | no |
32
- | `secure_policy_show` | Show .code-security.json | no |
33
- | `secure_policy_set` | Replace policy JSON | approval |
27
+ ## Start using it
34
28
 
35
- 40+ deterministic rules: injection, deserialization, weak crypto (including shell TLS bypass flags), secrets, dangerous config, sensitive logging, path traversal, SSRF.
29
+ Ask: “Review the current Git diff for security findings, show evidence and recheck the fixes.”
36
30
 
37
- ```bash
38
- dsh plugin --profile web add dsh-code-security
39
- ```
31
+ ## Requirements and configuration
32
+
33
+ Deterministic rules run locally. See the usage guide for rule coverage, baselines and policy configuration.
34
+
35
+ Detailed configuration, tool arguments and troubleshooting are in the [usage guide](docs/USAGE.en.md). For standalone development, follow the Node requirement in [package.json](package.json).
36
+
37
+ ## Documentation
38
+
39
+ - [Usage and troubleshooting](docs/USAGE.en.md)
40
+ - [Changelog](CHANGELOG.md)
41
+ - [Validation scope and history](docs/VALIDATION.md)
42
+ - [Report a problem or suggest a feature](https://github.com/STARDUSTLC666/dsh-code-security/issues)
40
43
 
41
- MIT
42
44
  ## License
43
45
 
44
- MIT (see [LICENSE](LICENSE))
46
+ [MIT](LICENSE)
package/README.md CHANGED
@@ -1,91 +1,46 @@
1
1
  # dsh-code-security
2
2
 
3
- ## 0.3.5 更新(2026-09-28)
3
+ [English](README.en.md)
4
4
 
5
- 扫描参数写错时明确报错,避免把 `paths`、`files` 或空目标误当成扫描整个工作区。修复标准模式中结果卡片误显示“0 个文件、未通过”的问题;文件数量、结论与问题列表现在使用实际扫描结果。
5
+ ![dsh-code-security 鲸鱼娘插件封面](https://raw.githubusercontent.com/STARDUSTLC666/dsh-code-security/master/assets/cover-whale-girl.png)
6
6
 
7
- 验证宿主:官方源码构建的 Harness `0.2.0-rc.1`(commit `407e65c8`)+ Node `24.16.0`(2026-09-28)。31 项插件测试在隔离环境全部通过;同一个宿主里 18 个插件共同加载,注册 10 个工具,工具 schema 与健康检查契约通过。本轮未启用真实端口与外部服务。
7
+ 对代码与 Git 改动进行本地静态安全检查,并追踪修复结果。
8
8
 
9
- ![npm](https://img.shields.io/npm/v/dsh-code-security) ![downloads](https://img.shields.io/npm/dm/dsh-code-security) ![license](https://img.shields.io/github/license/STARDUSTLC666/dsh-code-security) ![stars](https://img.shields.io/github/stars/STARDUSTLC666/dsh-code-security?style=social)
9
+ [![npm](https://img.shields.io/npm/v/dsh-code-security)](https://www.npmjs.com/package/dsh-code-security) [![downloads](https://raw.githubusercontent.com/STARDUSTLC666/dsh-suite/npm-downloads/assets/dsh-code-security-downloads.svg)](https://www.npmjs.com/package/dsh-code-security)
10
10
 
11
- > 让 agent 每次改完代码,先过一道本地安全扫描,修复完了才准交付。
11
+ ## 功能
12
12
 
13
- DeepSeek Harness AI 代码安全审查插件:确定性规则引擎 + git diff 增量审查 + 修复闭环 + 策略门禁。借鉴 Codex 安全技能的方法论(漏洞分级、证据优先、供应链分层、Agent 安全),实现为可复现工具。零运行时依赖。
14
-
15
- ## 工具
16
-
17
- | 工具 | 作用 | 写操作 |
18
- | :-- | :-- | :-- |
19
- | `secure_scan` | 扫描文件/目录,输出 CWE/严重度/行号/代码片段证据 | 写状态 |
20
- | `secure_diff` | 只审查 git diff 新增行 | 写状态 |
21
- | `secure_fix_verify` | 修复后复扫:关闭 / 仍存在 / 新引入 | 写状态 |
22
- | `secure_report` | 按规则/文件聚合 + 门禁结论 | 否 |
23
- | `secure_export` | 导出 SARIF 2.1.0 / Markdown 报告 | 写文件审批 |
24
- | `secure_baseline` | 接受当前已知问题为基线,之后只按新增判定 | 审批门 |
25
- | `secure_deps` | SBOM-lite:解析依赖清单与版本约束风险 | 否 |
26
- | `secure_policy_show` | 查看 .code-security.json 策略 | 否 |
27
- | `secure_policy_set` | 写入策略(排除/忽略/阈值) | 审批门 |
28
-
29
- ## 规则覆盖(40+)
30
-
31
- - 注入:eval / exec / shell=True / SQL 拼接 / innerHTML
32
- - 反序列化:pickle / yaml.load / ObjectInputStream / Marshal / unserialize
33
- - 加密:弱哈希、ECB、硬编码 IV/密钥、JWT none、TLS 校验关闭、Shell TLS 绕过(curl -k / wget --no-check-certificate / git sslVerify=false)
34
- - 凭据:硬编码密码、令牌、私钥、高熵密钥
35
- - 配置:chmod 777、Docker privileged / latest、npm audit=false、CORS *
36
- - 泄露:敏感日志、错误堆栈外发、路径穿越、SSRF
37
-
38
- 发现只包含客观证据与 CWE 编号,不附修复建议——修复方案由 agent 基于证据生成。
39
-
40
- ## 兼容性
41
-
42
- 已在官方 `@deepseek-ai/dsh@0.1.5-rc.1`、Node `24.16.0` 上验证(2026-09-11):18 个组件与 Modlens 同载,工具 schema、技能注册及离线只读调用检查通过。采用 `cordis.patch.yml` + `dsh.bundle.patch` 组合包模型。Node 要求与该版本 Harness 一致:22.19 及以上的 22.x,或 24 及以上。外部服务的实际业务操作需按各组件配置单独验证。
43
-
44
- 2026-09-13 修复:保留宿主 `subprocess.spawn` 的服务对象,避免方法独立传递后因 `this` 丢失而报错。已在官方 `0.1.5-rc.1` 和 `0.1.5-rc.2`、Node `24.16.0` 上验证。`secure_diff` 已通过真实宿主进程服务读取隔离 Git 仓库差异,正确识别变更文件和新增行。
13
+ - 扫描文件、目录或 Git diff,报告规则、位置与代码证据。
14
+ - 复扫修复结果,维护问题基线和检查策略。
15
+ - 导出 Markdown / SARIF 报告,检查依赖清单。
45
16
 
46
17
  ## 安装
47
18
 
48
- ```bash
49
- dsh plugin --profile web add dsh-code-security
50
- ```
51
-
52
- ## 卸载
19
+ 桌面版可在「插件」面板按包名 `dsh-code-security` 安装。已配置 dsh 命令时也可使用:
53
20
 
54
21
  ```bash
55
- dsh plugin --profile web remove dsh-code-security
22
+ dsh plugin --profile desktop add dsh-code-security
56
23
  ```
57
24
 
58
- 卸载后重启 Web 服务。如需彻底清理,可再手动删除自己 profile `cordis.patch.yml` 中覆盖的插件行。
25
+ 网页版把命令中的 `desktop` 改为 `web`。安装后重启 DSH。
59
26
 
27
+ ## 开始使用
60
28
 
61
- ## 使用
29
+ 安装后可说:“审查当前 Git 改动的安全问题,给出位置和证据,再复查修复结果。”
62
30
 
63
- ```text
64
- secure_scan { target: src }
65
- secure_diff { base: HEAD }
66
- secure_fix_verify { target: src }
67
- secure_baseline { reason: 历史遗留 }
68
- secure_deps { target: . }
69
- ```
31
+ ## 依赖与配置
70
32
 
71
- 策略示例(`.code-security.json`):
33
+ 默认在本机执行确定性规则检查。规则覆盖与基线、策略配置见使用说明。
72
34
 
73
- ```json
74
- {
75
- "version": 1,
76
- "exclude": ["vendor/**", "generated/**"],
77
- "ignore": [{ "ruleId": "SEC-206", "file": "test/**", "reason": "非安全敏感" }],
78
- "failOn": "high"
79
- }
80
- ```
35
+ 详细配置、工具参数与排错见[使用说明](docs/USAGE.md)。从源码独立开发时,Node 要求以 [package.json](package.json) 为准。
81
36
 
82
- ## 工程
37
+ ## 文档
83
38
 
84
- ```bash
85
- pnpm test # 构建 + 24 个测试
86
- ```
39
+ - [使用与排错](docs/USAGE.md)
40
+ - [更新记录](CHANGELOG.md)
41
+ - [验证范围与历史记录](docs/VALIDATION.md)
42
+ - [问题反馈与功能建议](https://github.com/STARDUSTLC666/dsh-code-security/issues)
87
43
 
88
- MIT
89
44
  ## License
90
45
 
91
- MIT(见 [LICENSE](LICENSE))
46
+ [MIT](LICENSE)
@@ -0,0 +1,15 @@
1
+ # Historical release notes
2
+
3
+ [Current changelog](../CHANGELOG.md) · [Overview](../README.en.md)
4
+
5
+ These English notes preserve the earlier translations. The main changelog contains the consolidated version history.
6
+
7
+ ## 0.3.7 (2026-10-05)
8
+
9
+ - Use the session workspace for scans, policy, reports and git diff. Respect cancellation and skip directory junctions. Lock history/baseline merges and preserve damaged files with explicit errors.
10
+
11
+ ## 0.3.5 (2026-09-28)
12
+
13
+ Rejects unknown scan arguments and empty targets before scanning or replacing state, preventing misspelled scopes from expanding to the whole workspace. Fixes standard-mode output incorrectly displaying zero files and a failed verdict: counts, verdicts and findings now come from the actual scan result.
14
+
15
+ Validation host: Harness `0.2.0-rc.1` built from official sources (commit `407e65c8`) with Node `24.16.0` on 2026-09-28. All 31 plugin tests pass in an isolated environment; all 18 plugins mount together in one host registering 10 tools, with tool schemas and health-check contracts passing. No live ports or external services were exercised in this round.
@@ -0,0 +1,31 @@
1
+ # dsh-code-security usage guide
2
+
3
+ [Overview](../README.en.md) · [Changelog](../CHANGELOG.md) · [Validation](VALIDATION.md)
4
+
5
+ ## Current improvements
6
+
7
+ Relative stateDir defaults to .code-security in the session workspace. Concurrent scans retain both histories. Damaged records must be inspected after backup rather than silently recreated. Scans skip symlinks and junctions; static rules do not replace a full security audit.
8
+
9
+ ## Tools
10
+
11
+ | Tool | Purpose | Write |
12
+ | :-- | :-- | :-- |
13
+ | `secure_scan` | Scan files with CWE/severity/line/snippet evidence | state |
14
+ | `secure_diff` | Review only added lines of git diff | state |
15
+ | `secure_fix_verify` | Compare with baseline: closed / remaining / fresh | state |
16
+ | `secure_report` | Aggregate by rule/file with gate verdict | no |
17
+ | `secure_export` | Export SARIF 2.1.0 / Markdown | file write approval |
18
+ | `secure_baseline` | Accept current findings as baseline; gate on new issues only | approval |
19
+ | `secure_deps` | SBOM-lite: parse dependency manifests and version-risk flags | no |
20
+ | `secure_policy_show` | Show .code-security.json | no |
21
+ | `secure_policy_set` | Replace policy JSON | approval |
22
+
23
+ 40+ deterministic rules: injection, deserialization, weak crypto (including shell TLS bypass flags), secrets, dangerous config, sensitive logging, path traversal, SSRF.
24
+
25
+ ```bash
26
+ dsh plugin --profile web add dsh-code-security
27
+ ```
28
+
29
+ ## License
30
+
31
+ MIT (see [LICENSE](../LICENSE))
package/docs/USAGE.md ADDED
@@ -0,0 +1,77 @@
1
+ # dsh-code-security 使用说明
2
+
3
+ [返回简介](../README.md) · [更新记录](../CHANGELOG.md) · [验证记录](VALIDATION.md)
4
+
5
+ ## 本次改进
6
+
7
+ 相对 stateDir 默认在会话工作区的 .code-security。并发扫描保留双方历史;记录损坏时先备份排查,不会默默重建。扫描不跟随符号链接或目录联接;静态规则不能代替完整安全审计。
8
+
9
+ ## 工具
10
+
11
+ | 工具 | 作用 | 写操作 |
12
+ | :-- | :-- | :-- |
13
+ | `secure_scan` | 扫描文件/目录,输出 CWE/严重度/行号/代码片段证据 | 写状态 |
14
+ | `secure_diff` | 只审查 git diff 新增行 | 写状态 |
15
+ | `secure_fix_verify` | 修复后复扫:关闭 / 仍存在 / 新引入 | 写状态 |
16
+ | `secure_report` | 按规则/文件聚合 + 门禁结论 | 否 |
17
+ | `secure_export` | 导出 SARIF 2.1.0 / Markdown 报告 | 写文件审批 |
18
+ | `secure_baseline` | 接受当前已知问题为基线,之后只按新增判定 | 审批门 |
19
+ | `secure_deps` | SBOM-lite:解析依赖清单与版本约束风险 | 否 |
20
+ | `secure_policy_show` | 查看 .code-security.json 策略 | 否 |
21
+ | `secure_policy_set` | 写入策略(排除/忽略/阈值) | 审批门 |
22
+
23
+ ## 规则覆盖(40+)
24
+
25
+ - 注入:eval / exec / shell=True / SQL 拼接 / innerHTML
26
+ - 反序列化:pickle / yaml.load / ObjectInputStream / Marshal / unserialize
27
+ - 加密:弱哈希、ECB、硬编码 IV/密钥、JWT none、TLS 校验关闭、Shell TLS 绕过(curl -k / wget --no-check-certificate / git sslVerify=false)
28
+ - 凭据:硬编码密码、令牌、私钥、高熵密钥
29
+ - 配置:chmod 777、Docker privileged / latest、npm audit=false、CORS *
30
+ - 泄露:敏感日志、错误堆栈外发、路径穿越、SSRF
31
+
32
+ 发现只包含客观证据与 CWE 编号,不附修复建议——修复方案由 agent 基于证据生成。
33
+
34
+ ## 安装
35
+
36
+ ```bash
37
+ dsh plugin --profile web add dsh-code-security
38
+ ```
39
+
40
+ ## 卸载
41
+
42
+ ```bash
43
+ dsh plugin --profile web remove dsh-code-security
44
+ ```
45
+
46
+ 卸载后重启 Web 服务。如需彻底清理,可再手动删除自己 profile `cordis.patch.yml` 中覆盖的插件行。
47
+
48
+ ## 使用
49
+
50
+ ```text
51
+ secure_scan { target: src }
52
+ secure_diff { base: HEAD }
53
+ secure_fix_verify { target: src }
54
+ secure_baseline { reason: 历史遗留 }
55
+ secure_deps { target: . }
56
+ ```
57
+
58
+ 策略示例(`.code-security.json`):
59
+
60
+ ```json
61
+ {
62
+ "version": 1,
63
+ "exclude": ["vendor/**", "generated/**"],
64
+ "ignore": [{ "ruleId": "SEC-206", "file": "test/**", "reason": "非安全敏感" }],
65
+ "failOn": "high"
66
+ }
67
+ ```
68
+
69
+ ## 工程
70
+
71
+ ```bash
72
+ pnpm test # 构建并执行测试
73
+ ```
74
+
75
+ ## License
76
+
77
+ MIT(见 [LICENSE](../LICENSE))
@@ -0,0 +1,15 @@
1
+ # dsh-code-security 验证记录
2
+
3
+ 本页整理原 README 的历史验证说明,保留当时的版本、日期与范围。自动测试、启动检查、浏览器操作和真实服务验收分别记录,不能相互替代。更详细的版本验收文件仍保留在仓库中。
4
+
5
+ ## 原中文记录
6
+
7
+ 2026-09-13 修复:保留宿主 `subprocess.spawn` 的服务对象,避免方法独立传递后因 `this` 丢失而报错。该路径已在隔离宿主进程服务中实测通过。`secure_diff` 已通过真实宿主进程服务读取隔离 Git 仓库差异,正确识别变更文件和新增行。
8
+
9
+ ## Original English record
10
+
11
+ 2026-09-13 fix: retain the service receiver when calling `subprocess.spawn`, preventing failures caused by passing the method as an unbound callback. Verified against a real isolated host subprocess service. `secure_diff` reads a real isolated Git repository through the host subprocess service and reports the changed file and added line.
12
+
13
+ ## 0.3.6 原验证说明
14
+
15
+ 验证宿主:官方源码构建的 Harness `0.2.0-rc.1`(commit `407e65c8`)+ Node `24.16.0`(2026-09-28)。31 项插件测试在隔离环境全部通过;同一个宿主里 18 个插件共同加载,注册 10 个工具,工具 schema 与健康检查契约通过。本轮未启用真实端口与外部服务。
package/lib/config.d.ts CHANGED
@@ -15,6 +15,7 @@ export interface ResolvedSecureConfig {
15
15
  maxFileBytes: number;
16
16
  failOn: Severity;
17
17
  stateDir: string;
18
+ stateDirRelative?: string;
18
19
  }
19
20
  export declare const DEFAULT_EXCLUDE_DIRS: string[];
20
21
  export declare function resolveConfig(config: SecureConfig | undefined | null, cwd?: string): ResolvedSecureConfig;
package/lib/config.js CHANGED
@@ -21,7 +21,8 @@ export function resolveConfig(config, cwd = process.cwd()) {
21
21
  }
22
22
  const failOn = cfg.failOn === 'critical' || cfg.failOn === 'high' || cfg.failOn === 'medium' || cfg.failOn === 'low' ? cfg.failOn : 'medium';
23
23
  const stateDir = typeof cfg.stateDir === 'string' && cfg.stateDir.trim() !== '' ? path.resolve(cwd, cfg.stateDir.trim()) : path.join(cwd, '.code-security');
24
- return { maxFiles, maxFileBytes, failOn, stateDir };
24
+ const rawStateDir = typeof cfg.stateDir === 'string' && cfg.stateDir.trim() ? cfg.stateDir.trim() : '.code-security';
25
+ return { maxFiles, maxFileBytes, failOn, stateDir, ...(path.isAbsolute(rawStateDir) ? {} : { stateDirRelative: rawStateDir }) };
25
26
  }
26
27
  export function optionalString(args, key) {
27
28
  const value = args[key];
package/lib/diff.js CHANGED
@@ -14,7 +14,7 @@ export async function runGitDiff(runner, cwd, base, target, timeoutMs = 30000, s
14
14
  argv.push('--no-ext-diff', '--unified=0', base);
15
15
  if (target !== undefined && target.trim() !== '')
16
16
  argv.push('--', target.trim());
17
- const result = await runner.run(argv, { timeoutMs });
17
+ const result = await runner.run(argv, { timeoutMs, cwd });
18
18
  if (result.exitCode !== 0 && result.exitCode !== 1) {
19
19
  throw new Error('git diff 失败(退出码 ' + String(result.exitCode) + '):' + result.stderr.trim().split(/\r?\n/).slice(-4).join(' | '));
20
20
  }
package/lib/extra.d.ts CHANGED
@@ -5,4 +5,4 @@
5
5
  */
6
6
  import { type ResolvedSecureConfig } from './config.js';
7
7
  import type { SecureToolDefinition } from './tools.js';
8
- export declare function buildExtraTools(cfg: ResolvedSecureConfig, cwd: string): SecureToolDefinition[];
8
+ export declare function buildExtraTools(cfg: ResolvedSecureConfig, cwd: string, signal?: AbortSignal): SecureToolDefinition[];
package/lib/extra.js CHANGED
@@ -22,7 +22,7 @@ function compileParameters(spec) {
22
22
  }
23
23
  return { type: 'object', properties, ...(required.length > 0 ? { required } : {}) };
24
24
  }
25
- export function buildExtraTools(cfg, cwd) {
25
+ export function buildExtraTools(cfg, cwd, signal) {
26
26
  const stateDir = cfg.stateDir;
27
27
  const secureBaseline = {
28
28
  name: 'secure_baseline',
@@ -51,7 +51,7 @@ export function buildExtraTools(cfg, cwd) {
51
51
  fingerprints: state.last.fingerprints,
52
52
  counts: state.last.counts,
53
53
  };
54
- const file = await saveBaseline(stateDir, baseline);
54
+ const file = await saveBaseline(stateDir, baseline, signal);
55
55
  return { file, acceptedCount: baseline.findings.length, baseline };
56
56
  },
57
57
  timeoutMs: 10000,
package/lib/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 插件导出 apply(ctx, config):注册 secure_scan / secure_diff / secure_fix_verify /
5
5
  * secure_report / secure_export / secure_policy_show / secure_policy_set 七个工具。扫描为纯确定性规则,
6
- * git diff 通过官方 subprocess 服务执行,零运行时依赖。
6
+ * git diff 通过官方 subprocess 服务执行,仅依赖文件锁库。
7
7
  *
8
8
  * @module dsh-code-security
9
9
  */
package/lib/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * 插件导出 apply(ctx, config):注册 secure_scan / secure_diff / secure_fix_verify /
5
5
  * secure_report / secure_export / secure_policy_show / secure_policy_set 七个工具。扫描为纯确定性规则,
6
- * git diff 通过官方 subprocess 服务执行,零运行时依赖。
6
+ * git diff 通过官方 subprocess 服务执行,仅依赖文件锁库。
7
7
  *
8
8
  * @module dsh-code-security
9
9
  */
package/lib/policy.d.ts CHANGED
@@ -19,6 +19,6 @@ export declare const POLICY_FILE = ".code-security.json";
19
19
  export declare function policyPath(cwd: string): string;
20
20
  export declare function loadPolicy(cwd: string): Promise<SecurePolicy>;
21
21
  export declare function normalizePolicy(obj: Record<string, unknown>): SecurePolicy;
22
- export declare function savePolicy(cwd: string, policy: SecurePolicy): Promise<string>;
22
+ export declare function savePolicy(cwd: string, policy: SecurePolicy, signal?: AbortSignal): Promise<string>;
23
23
  export declare function policyExcludesDir(policy: SecurePolicy, rel: string): boolean;
24
24
  export declare function policyIgnores(policy: SecurePolicy, ruleId: string, file: string): boolean;
package/lib/policy.js CHANGED
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import fs from 'node:fs/promises';
7
7
  import path from 'node:path';
8
+ import { randomUUID } from 'node:crypto';
8
9
  export const DEFAULT_POLICY = Object.freeze({
9
10
  version: 1,
10
11
  exclude: [],
@@ -21,7 +22,9 @@ export async function loadPolicy(cwd) {
21
22
  const obj = (parsed ?? {});
22
23
  return normalizePolicy(obj);
23
24
  }
24
- catch {
25
+ catch (error) {
26
+ if (error.code !== 'ENOENT')
27
+ throw new Error('安全策略无法读取或格式无效,原文件已保留:' + (error instanceof Error ? error.message : String(error)));
25
28
  return { ...DEFAULT_POLICY, exclude: [...DEFAULT_POLICY.exclude], ignore: [] };
26
29
  }
27
30
  }
@@ -33,6 +36,8 @@ export function normalizePolicy(obj) {
33
36
  if (typeof item !== 'object' || item === null)
34
37
  continue;
35
38
  const rec = item;
39
+ if (!(typeof rec.ruleId === 'string' && rec.ruleId.trim() !== '') && !(typeof rec.file === 'string' && rec.file.trim() !== ''))
40
+ throw new Error('每条忽略规则必须指定 ruleId 或 file,空规则不能禁用全部检查。');
36
41
  ignore.push({
37
42
  ruleId: typeof rec.ruleId === 'string' && rec.ruleId !== '' ? rec.ruleId : undefined,
38
43
  file: typeof rec.file === 'string' && rec.file !== '' ? rec.file : undefined,
@@ -42,11 +47,18 @@ export function normalizePolicy(obj) {
42
47
  const failOn = obj.failOn === 'critical' || obj.failOn === 'high' || obj.failOn === 'medium' || obj.failOn === 'low' ? obj.failOn : undefined;
43
48
  return { version: 1, exclude, ignore, failOn };
44
49
  }
45
- export async function savePolicy(cwd, policy) {
50
+ export async function savePolicy(cwd, policy, signal) {
51
+ signal?.throwIfAborted();
46
52
  const file = policyPath(cwd);
47
- const tmp = file + '.tmp-' + process.pid;
48
- await fs.writeFile(tmp, JSON.stringify(policy, null, 2) + '\n', 'utf8');
49
- await fs.rename(tmp, file);
53
+ const tmp = file + '.tmp-' + randomUUID();
54
+ try {
55
+ await fs.writeFile(tmp, JSON.stringify(policy, null, 2) + '\n', { flag: 'wx', mode: 0o600, signal });
56
+ signal?.throwIfAborted();
57
+ await fs.rename(tmp, file);
58
+ }
59
+ finally {
60
+ await fs.unlink(tmp).catch(() => { });
61
+ }
50
62
  return file;
51
63
  }
52
64
  function globToRegex(glob) {
package/lib/runner.d.ts CHANGED
@@ -12,6 +12,8 @@ export interface RunResult {
12
12
  export interface ProcessRunner {
13
13
  run(argv: readonly string[], options?: {
14
14
  timeoutMs?: number;
15
+ cwd?: string;
16
+ signal?: AbortSignal;
15
17
  }): Promise<RunResult>;
16
18
  }
17
19
  export interface SubprocessHandleLike {
package/lib/runner.js CHANGED
@@ -9,14 +9,16 @@ export function createSubprocessRunner(spawn, graceMs, defaultTimeoutMs) {
9
9
  async run(argv, options) {
10
10
  const timeoutMs = options?.timeoutMs ?? defaultTimeoutMs;
11
11
  const controller = new AbortController();
12
+ const signal = options?.signal ? AbortSignal.any([options.signal, controller.signal]) : controller.signal;
13
+ signal.throwIfAborted();
12
14
  const timer = setTimeout(() => controller.abort(new Error('git diff timed out')), timeoutMs);
13
15
  try {
14
16
  const handle = spawn({
15
17
  argv,
16
- cwd: process.cwd(),
18
+ cwd: options?.cwd ?? process.cwd(),
17
19
  stdio: { stdin: 'ignore', stdout: { maxBytes: COLLECT_BYTES }, stderr: { maxBytes: COLLECT_BYTES } },
18
20
  graceMs,
19
- signal: controller.signal,
21
+ signal,
20
22
  });
21
23
  const outcome = await handle.done;
22
24
  return {
package/lib/scanner.d.ts CHANGED
@@ -30,6 +30,7 @@ export interface ScanOptions {
30
30
  maxFiles: number;
31
31
  maxFileBytes: number;
32
32
  policy: SecurePolicy;
33
+ signal?: AbortSignal;
33
34
  }
34
35
  export interface LineEntry {
35
36
  line: number;
package/lib/scanner.js CHANGED
@@ -179,14 +179,20 @@ export async function scanPath(options) {
179
179
  const findings = [];
180
180
  const relRoot = path.relative(cwd, root) || '.';
181
181
  const visit = async (absolute, rel) => {
182
+ options.signal?.throwIfAborted();
182
183
  let stat;
183
184
  try {
184
- stat = await fs.stat(absolute);
185
+ stat = await fs.lstat(absolute);
185
186
  }
186
187
  catch {
187
188
  filesSkipped += 1;
188
189
  return;
189
190
  }
191
+ options.signal?.throwIfAborted();
192
+ if (stat.isSymbolicLink()) {
193
+ filesSkipped += 1;
194
+ return;
195
+ }
190
196
  if (stat.isDirectory()) {
191
197
  if (DEFAULT_EXCLUDE_DIRS.includes(path.basename(absolute)) || policyExcludesDir(policy, rel))
192
198
  return;
@@ -226,6 +232,7 @@ export async function scanPath(options) {
226
232
  return;
227
233
  }
228
234
  filesScanned += 1;
235
+ options.signal?.throwIfAborted();
229
236
  const text = new TextDecoder('utf-8', { fatal: false }).decode(buffer);
230
237
  findings.push(...scanText(text, absolute, rel, policy));
231
238
  };
package/lib/state.d.ts CHANGED
@@ -40,8 +40,8 @@ export interface StateDocument {
40
40
  export declare function findingFingerprint(finding: Finding): string;
41
41
  export declare function statePath(stateDir: string): string;
42
42
  export declare function loadState(stateDir: string): Promise<StateDocument>;
43
- export declare function saveState(stateDir: string, entry: ScanStateEntry): Promise<string>;
44
- export declare function saveBaseline(stateDir: string, baseline: BaselineEntry): Promise<string>;
43
+ export declare function saveState(stateDir: string, entry: ScanStateEntry, signal?: AbortSignal): Promise<string>;
44
+ export declare function saveBaseline(stateDir: string, baseline: BaselineEntry, signal?: AbortSignal): Promise<string>;
45
45
  export declare function countFindings(findings: Finding[]): {
46
46
  critical: number;
47
47
  high: number;
package/lib/state.js CHANGED
@@ -6,7 +6,8 @@
6
6
  */
7
7
  import fs from 'node:fs/promises';
8
8
  import path from 'node:path';
9
- import { createHash } from 'node:crypto';
9
+ import { createHash, randomUUID } from 'node:crypto';
10
+ import lockfile from 'proper-lockfile';
10
11
  const STATE_FILE = 'state.json';
11
12
  const HISTORY_LIMIT = 50;
12
13
  export function findingFingerprint(finding) {
@@ -18,43 +19,96 @@ export function statePath(stateDir) {
18
19
  }
19
20
  export async function loadState(stateDir) {
20
21
  try {
22
+ const info = await fs.lstat(statePath(stateDir));
23
+ if (!info.isFile() || info.isSymbolicLink() || info.size > 16 * 1024 * 1024)
24
+ throw new Error('记录不是普通文件或超过 16 MiB');
21
25
  const text = await fs.readFile(statePath(stateDir), 'utf8');
22
26
  const parsed = JSON.parse(text);
23
27
  const obj = (parsed ?? {});
28
+ if (!Array.isArray(obj.history) || obj.history.some(entry => !isEntry(entry)) || obj.last !== null && !isEntry(obj.last) || obj.baseline !== undefined && obj.baseline !== null && !isBaseline(obj.baseline))
29
+ throw new Error('记录结构无效');
24
30
  const last = isEntry(obj.last) ? obj.last : null;
25
31
  const history = Array.isArray(obj.history) ? obj.history.filter(isEntry) : [];
26
32
  const baseline = isBaseline(obj.baseline) ? obj.baseline : null;
27
33
  return { last, history, baseline };
28
34
  }
29
- catch {
35
+ catch (error) {
36
+ if (error.code !== 'ENOENT')
37
+ throw new Error('安全扫描记录无法读取,原文件已保留:' + (error instanceof Error ? error.message : String(error)));
30
38
  return { last: null, history: [], baseline: null };
31
39
  }
32
40
  }
33
41
  function isEntry(value) {
34
- return typeof value === 'object' && value !== null && Array.isArray(value.findings);
42
+ if (typeof value !== 'object' || value === null)
43
+ return false;
44
+ const row = value;
45
+ return typeof row.mode === 'string' && typeof row.target === 'string' && typeof row.time === 'string'
46
+ && Number.isInteger(row.filesScanned) && row.filesScanned >= 0 && validFindings(row.findings)
47
+ && validFingerprints(row.fingerprints) && validCounts(row.counts);
35
48
  }
36
49
  function isBaseline(value) {
37
- return typeof value === 'object' && value !== null && Array.isArray(value.findings) && Array.isArray(value.fingerprints);
50
+ if (typeof value !== 'object' || value === null)
51
+ return false;
52
+ const row = value;
53
+ return typeof row.time === 'string' && typeof row.target === 'string' && typeof row.reason === 'string'
54
+ && validFindings(row.findings) && validFingerprints(row.fingerprints) && validCounts(row.counts);
38
55
  }
39
- export async function saveState(stateDir, entry) {
56
+ function validFingerprints(value) { return Array.isArray(value) && value.every(item => typeof item === 'string'); }
57
+ function validCounts(value) {
58
+ if (typeof value !== 'object' || value === null)
59
+ return false;
60
+ const counts = value;
61
+ return ['critical', 'high', 'medium', 'low'].every(key => typeof counts[key] === 'number' && Number.isInteger(counts[key]) && counts[key] >= 0);
62
+ }
63
+ function validFindings(value) {
64
+ return Array.isArray(value) && value.every(item => typeof item === 'object' && item !== null
65
+ && typeof item.ruleId === 'string' && typeof item.file === 'string' && typeof item.snippet === 'string'
66
+ && Number.isInteger(item.line) && item.line >= 1 && ['critical', 'high', 'medium', 'low'].includes(item.severity));
67
+ }
68
+ export async function saveState(stateDir, entry, signal) {
69
+ signal?.throwIfAborted();
40
70
  await fs.mkdir(stateDir, { recursive: true });
41
- const state = await loadState(stateDir);
42
- state.last = entry;
43
- state.history.unshift(entry);
44
- state.history = state.history.slice(0, HISTORY_LIMIT);
45
- return writeState(stateDir, state);
71
+ const unlock = await lockfile.lock(stateDir, { retries: { retries: 30, minTimeout: 25, maxTimeout: 100 } });
72
+ try {
73
+ signal?.throwIfAborted();
74
+ const state = await loadState(stateDir);
75
+ state.last = entry;
76
+ state.history.unshift(entry);
77
+ state.history = state.history.slice(0, HISTORY_LIMIT);
78
+ return await writeState(stateDir, state, signal);
79
+ }
80
+ finally {
81
+ await unlock();
82
+ }
46
83
  }
47
- export async function saveBaseline(stateDir, baseline) {
84
+ export async function saveBaseline(stateDir, baseline, signal) {
85
+ signal?.throwIfAborted();
48
86
  await fs.mkdir(stateDir, { recursive: true });
49
- const state = await loadState(stateDir);
50
- state.baseline = baseline;
51
- return writeState(stateDir, state);
87
+ const unlock = await lockfile.lock(stateDir, { retries: { retries: 30, minTimeout: 25, maxTimeout: 100 } });
88
+ try {
89
+ signal?.throwIfAborted();
90
+ const state = await loadState(stateDir);
91
+ state.baseline = baseline;
92
+ return await writeState(stateDir, state, signal);
93
+ }
94
+ finally {
95
+ await unlock();
96
+ }
52
97
  }
53
- async function writeState(stateDir, state) {
98
+ async function writeState(stateDir, state, signal) {
99
+ signal?.throwIfAborted();
54
100
  const file = statePath(stateDir);
55
- const tmp = file + '.tmp-' + process.pid;
56
- await fs.writeFile(tmp, JSON.stringify(state, null, 2), 'utf8');
57
- await fs.rename(tmp, file);
101
+ const tmp = file + '.tmp-' + randomUUID(), bytes = JSON.stringify(state, null, 2);
102
+ if (Buffer.byteLength(bytes) > 16 * 1024 * 1024)
103
+ throw new Error('安全扫描记录超过 16 MiB,本次没有保存。');
104
+ try {
105
+ await fs.writeFile(tmp, bytes, { flag: 'wx', mode: 0o600, signal });
106
+ signal?.throwIfAborted();
107
+ await fs.rename(tmp, file);
108
+ }
109
+ finally {
110
+ await fs.unlink(tmp).catch(() => { });
111
+ }
58
112
  return file;
59
113
  }
60
114
  export function countFindings(findings) {
package/lib/tools.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * @module dsh-code-security/tools
7
7
  */
8
8
  import { writeFile } from 'node:fs/promises';
9
+ import { readFileSync } from 'node:fs';
9
10
  import path from 'node:path';
10
11
  import { pathToFileURL } from 'node:url';
11
12
  import { optionalString, requiredString } from './config.js';
@@ -16,6 +17,7 @@ import { ruleCategory } from './rules.js';
16
17
  import { scanPath } from './scanner.js';
17
18
  import { compareFingerprints, countFindings, findingFingerprint, loadState, saveState, splitByBaseline } from './state.js';
18
19
  const SEVERITY_ORDER = { critical: 4, high: 3, medium: 2, low: 1 };
20
+ const PACKAGE_VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
19
21
  function compileParameters(spec) {
20
22
  const properties = {};
21
23
  const required = [];
@@ -100,6 +102,18 @@ function renderFindings(_args, value) {
100
102
  return [{ type: 'text', text: lines.join('\n') }];
101
103
  }
102
104
  export function buildSecureTools(cfg, cwd, runner) {
105
+ return buildWorkspaceTools(cfg, cwd, runner).map(tool => ({ ...tool, async execute(args, exec) {
106
+ const context = exec;
107
+ const signal = context?.signal;
108
+ signal?.throwIfAborted();
109
+ const workspace = context?.agent?.session?.header?.cwd || cwd;
110
+ const resolved = { ...cfg, stateDir: cfg.stateDirRelative ? path.resolve(workspace, cfg.stateDirRelative) : cfg.stateDir };
111
+ const scopedRunner = { run: (argv, options) => runner.run(argv, { ...options, cwd: workspace, signal }) };
112
+ const selected = buildWorkspaceTools(resolved, workspace, scopedRunner, signal).find(row => row.name === tool.name);
113
+ return selected.execute(args, exec);
114
+ } }));
115
+ }
116
+ function buildWorkspaceTools(cfg, cwd, runner, signal) {
103
117
  const stateDir = cfg.stateDir;
104
118
  const secureScan = {
105
119
  name: 'secure_scan',
@@ -113,7 +127,7 @@ export function buildSecureTools(cfg, cwd, runner) {
113
127
  const target = optionalString(args, 'target');
114
128
  const policy = await loadPolicy(cwd);
115
129
  const failOn = policy.failOn ?? cfg.failOn;
116
- const result = await scanPath({ cwd, target, maxFiles: cfg.maxFiles, maxFileBytes: cfg.maxFileBytes, policy });
130
+ const result = await scanPath({ cwd, target, maxFiles: cfg.maxFiles, maxFileBytes: cfg.maxFileBytes, policy, signal });
117
131
  const state = await loadState(stateDir);
118
132
  const counts = countFindings(result.findings);
119
133
  const split = splitByBaseline(result.findings, state.baseline);
@@ -126,7 +140,7 @@ export function buildSecureTools(cfg, cwd, runner) {
126
140
  fingerprints: result.findings.map(findingFingerprint),
127
141
  counts,
128
142
  };
129
- const stateFile = await saveState(stateDir, entry);
143
+ const stateFile = await saveState(stateDir, entry, signal);
130
144
  return { ...result, counts, newFindings: split.fresh, newCounts: countFindings(split.fresh), acceptedFindings: split.accepted, baseline: state.baseline, passed: verdictOf(split.fresh, failOn), failOn, stateFile };
131
145
  },
132
146
  timeoutMs: 120000,
@@ -160,7 +174,7 @@ export function buildSecureTools(cfg, cwd, runner) {
160
174
  fingerprints: result.findings.map(findingFingerprint),
161
175
  counts,
162
176
  };
163
- await saveState(stateDir, entry);
177
+ await saveState(stateDir, entry, signal);
164
178
  return { root: base, filesScanned: result.filesChanged, filesSkipped: 0, addedLines: result.addedLines, findings: result.findings, counts, newFindings: split.fresh, newCounts: countFindings(split.fresh), acceptedFindings: split.accepted, baseline: state.baseline, passed: verdictOf(split.fresh, failOn), failOn, durationMs: 0 };
165
179
  },
166
180
  timeoutMs: 60000,
@@ -192,7 +206,7 @@ export function buildSecureTools(cfg, cwd, runner) {
192
206
  const target = optionalString(args, 'target');
193
207
  const policy = await loadPolicy(cwd);
194
208
  const state = await loadState(stateDir);
195
- const result = await scanPath({ cwd, target, maxFiles: cfg.maxFiles, maxFileBytes: cfg.maxFileBytes, policy });
209
+ const result = await scanPath({ cwd, target, maxFiles: cfg.maxFiles, maxFileBytes: cfg.maxFileBytes, policy, signal });
196
210
  if (state.last === null) {
197
211
  return { baselineMissing: true, baselineTime: '', closedCount: 0, remainingCount: 0, freshCount: result.findings.length, closed: [], remaining: [], fresh: result.findings, passed: false };
198
212
  }
@@ -306,7 +320,7 @@ export function buildSecureTools(cfg, cwd, runner) {
306
320
  throw new Error('policy 参数不是合法 JSON。');
307
321
  }
308
322
  const policy = normalizePolicy((parsed ?? {}));
309
- const file = await savePolicy(cwd, policy);
323
+ const file = await savePolicy(cwd, policy, signal);
310
324
  return { file, policy };
311
325
  },
312
326
  timeoutMs: 10000,
@@ -337,6 +351,7 @@ export function buildSecureTools(cfg, cwd, runner) {
337
351
  if (state.last === null)
338
352
  throw new Error('尚无扫描结果,请先执行 secure_scan 或 secure_diff。');
339
353
  const text = format === 'sarif' ? buildSarif(state.last.findings, cwd) : buildMarkdown(state.last.findings, state.last.target);
354
+ signal?.throwIfAborted();
340
355
  if (target !== undefined)
341
356
  await writeFile(target, text, 'utf8');
342
357
  return { format, path: target ?? '', text, findingCount: state.last.findings.length };
@@ -369,7 +384,7 @@ export function buildSecureTools(cfg, cwd, runner) {
369
384
  return { ok: true, plugin: 'dsh-code-security', checks };
370
385
  },
371
386
  };
372
- return [secureScan, secureDiff, secureFixVerify, secureReport, secureExport, ...buildExtraTools(cfg, cwd), policyShow, policySet, secureHealth];
387
+ return [secureScan, secureDiff, secureFixVerify, secureReport, secureExport, ...buildExtraTools(cfg, cwd, signal), policyShow, policySet, secureHealth];
373
388
  }
374
389
  function buildMarkdown(findings, target) {
375
390
  const lines = ['# 安全审查报告', '', '目标:' + target, '', '| 严重度 | 规则 | CWE | 文件:行 | 说明 |', '| :-- | :-- | :-- | :-- | :-- |'];
@@ -394,7 +409,7 @@ function buildSarif(findings, cwd) {
394
409
  version: '2.1.0',
395
410
  $schema: 'https://json.schemastore.org/sarif-2.1.0.json',
396
411
  runs: [{
397
- tool: { driver: { name: 'dsh-code-security', version: '0.1.0', informationUri: 'https://github.com/STARDUSTLC666/dsh-code-security', rules: [...rules.values()] } },
412
+ tool: { driver: { name: 'dsh-code-security', version: PACKAGE_VERSION, informationUri: 'https://github.com/STARDUSTLC666/dsh-code-security', rules: [...rules.values()] } },
398
413
  originalUriBaseIds: { ROOTPATH: { uri: pathToFileURL(path.resolve(cwd) + path.sep).href } },
399
414
  results,
400
415
  }],
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-code-security",
3
- "version": "0.3.6",
4
- "description": "DSH 代码安全审查插件:40+ 确定性规则与密钥熵检测,支持 git diff/staged 审查、SARIF 导出、基线接受与 SBOM-lite。",
3
+ "version": "0.3.7",
4
+ "description": "对代码与 Git 改动进行本地静态安全检查,并追踪修复结果。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
@@ -17,7 +17,9 @@
17
17
  "lib",
18
18
  "cordis.patch.yml",
19
19
  "README.md",
20
- "README.en.md"
20
+ "README.en.md",
21
+ "docs",
22
+ "CHANGELOG.md"
21
23
  ],
22
24
  "scripts": {
23
25
  "build": "tsc -p tsconfig.json",
@@ -46,6 +48,7 @@
46
48
  },
47
49
  "devDependencies": {
48
50
  "@types/node": "^24.0.0",
51
+ "@types/proper-lockfile": "4.1.4",
49
52
  "typescript": "^5.6.0"
50
53
  },
51
54
  "repository": {
@@ -57,5 +60,8 @@
57
60
  },
58
61
  "homepage": "https://github.com/STARDUSTLC666/dsh-code-security#readme",
59
62
  "author": "stardustlc",
60
- "packageManager": "pnpm@11.7.0"
63
+ "packageManager": "pnpm@11.7.0",
64
+ "dependencies": {
65
+ "proper-lockfile": "4.1.2"
66
+ }
61
67
  }