dsh-code-security 0.3.3 → 0.3.6

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/README.en.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # dsh-code-security
2
2
 
3
+ ## 0.3.5 update (2026-09-28)
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.
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.
8
+
3
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)
4
10
 
5
11
  > Every agent code change passes a local security scan before delivery.
package/README.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # dsh-code-security
2
2
 
3
+ ## 0.3.5 更新(2026-09-28)
4
+
5
+ 扫描参数写错时明确报错,避免把 `paths`、`files` 或空目标误当成扫描整个工作区。修复标准模式中结果卡片误显示“0 个文件、未通过”的问题;文件数量、结论与问题列表现在使用实际扫描结果。
6
+
7
+ 验证宿主:官方源码构建的 Harness `0.2.0-rc.1`(commit `407e65c8`)+ Node `24.16.0`(2026-09-28)。31 项插件测试在隔离环境全部通过;同一个宿主里 18 个插件共同加载,注册 10 个工具,工具 schema 与健康检查契约通过。本轮未启用真实端口与外部服务。
8
+
3
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)
4
10
 
5
11
  > 让 agent 每次改完代码,先过一道本地安全扫描,修复完了才准交付。
package/lib/tools.d.ts CHANGED
@@ -18,6 +18,7 @@ export interface SecureToolDefinition {
18
18
  type: 'object';
19
19
  properties: Record<string, unknown>;
20
20
  required?: string[];
21
+ additionalProperties?: boolean;
21
22
  };
22
23
  output: {
23
24
  schema: Record<string, unknown>;
package/lib/tools.js CHANGED
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import { writeFile } from 'node:fs/promises';
9
9
  import path from 'node:path';
10
+ import { pathToFileURL } from 'node:url';
10
11
  import { optionalString, requiredString } from './config.js';
11
12
  import { buildExtraTools } from './extra.js';
12
13
  import { runGitDiff, scanDiff } from './diff.js';
@@ -32,6 +33,21 @@ function compileParameters(spec) {
32
33
  }
33
34
  return { type: 'object', properties, ...(required.length > 0 ? { required } : {}) };
34
35
  }
36
+ /** Reject ambiguous scopes before reading files, running git, or replacing scan state. */
37
+ function scanArguments(raw, allowed = ['target']) {
38
+ if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
39
+ throw new Error('扫描参数必须是对象。请用 { target: "文件或目录" } 限定范围;扫描整个工作区请传 {}。');
40
+ }
41
+ const args = raw;
42
+ const unknown = Object.keys(args).filter(key => !allowed.includes(key));
43
+ if (unknown.length > 0) {
44
+ throw new Error('不支持的扫描参数:' + unknown.join('、') + '。请使用 target 指定一个文件或目录;仅省略 target 或使用 target: "." 才扫描整个工作区。');
45
+ }
46
+ if (Object.hasOwn(args, 'target') && (typeof args.target !== 'string' || args.target.trim() === '')) {
47
+ throw new Error('扫描参数 target 必须是非空的文件或目录字符串。扫描整个工作区请省略 target 或使用 "."。');
48
+ }
49
+ return args;
50
+ }
35
51
  const findingSchema = {
36
52
  type: 'object',
37
53
  properties: {
@@ -54,7 +70,7 @@ const scanSchema = {
54
70
  failOn: { type: 'string' }, durationMs: { type: 'integer' },
55
71
  newFindings: { type: 'array', items: findingSchema }, newCounts: countsSchema,
56
72
  acceptedFindings: { type: 'array', items: findingSchema },
57
- baseline: { type: 'object', additionalProperties: true },
73
+ baseline: { oneOf: [{ type: 'object', additionalProperties: true }, { type: 'null' }] },
58
74
  },
59
75
  additionalProperties: true,
60
76
  };
@@ -69,7 +85,7 @@ function baselineSummary(value) {
69
85
  accepted: Array.isArray(rec.acceptedFindings) ? rec.acceptedFindings : [],
70
86
  };
71
87
  }
72
- function renderFindings(value) {
88
+ function renderFindings(_args, value) {
73
89
  const rec = (value ?? {});
74
90
  const findings = Array.isArray(rec.findings) ? rec.findings : [];
75
91
  const counts = rec.counts;
@@ -87,13 +103,13 @@ export function buildSecureTools(cfg, cwd, runner) {
87
103
  const stateDir = cfg.stateDir;
88
104
  const secureScan = {
89
105
  name: 'secure_scan',
90
- description: '扫描工作区(或指定文件/目录),用确定性规则检测注入、弱加密、硬编码密钥、危险配置等问题。结果写入 .code-security 状态供 secure_fix_verify 对比。',
91
- parameters: compileParameters({
92
- target: { type: 'string', description: '要扫描的文件或目录(可选,缺省扫描整个工作区)。' },
93
- }),
106
+ description: '扫描工作区(或指定文件/目录),用确定性规则检测注入、弱加密、硬编码密钥、危险配置等问题。使用 target 字符串限定范围,不接受 paths/files 等参数;{} 表示整个工作区。结果写入 .code-security 状态供 secure_fix_verify 对比。',
107
+ parameters: { ...compileParameters({
108
+ target: { type: 'string', description: '要扫描的文件或目录(可选,缺省扫描整个工作区)。' },
109
+ }), additionalProperties: false },
94
110
  output: { schema: scanSchema, render: renderFindings },
95
111
  async execute(rawArgs) {
96
- const args = (rawArgs ?? {});
112
+ const args = scanArguments(rawArgs);
97
113
  const target = optionalString(args, 'target');
98
114
  const policy = await loadPolicy(cwd);
99
115
  const failOn = policy.failOn ?? cfg.failOn;
@@ -118,14 +134,14 @@ export function buildSecureTools(cfg, cwd, runner) {
118
134
  const secureDiff = {
119
135
  name: 'secure_diff',
120
136
  description: '只审查 git diff 的新增行(默认 HEAD,即未提交改动)。base 可用任意 git ref;staged=true 审查已暂存改动;target 可限定文件。结果同样写入状态。',
121
- parameters: compileParameters({
122
- base: { type: 'string', description: 'git 基线(默认 HEAD)。' },
123
- target: { type: 'string', description: '限定文件路径(可选)。' },
124
- staged: { type: 'boolean', description: '审查已暂存改动(git diff --cached,默认 false)。' },
125
- }),
137
+ parameters: { ...compileParameters({
138
+ base: { type: 'string', description: 'git 基线(默认 HEAD)。' },
139
+ target: { type: 'string', description: '限定文件路径(可选)。' },
140
+ staged: { type: 'boolean', description: '审查已暂存改动(git diff --cached,默认 false)。' },
141
+ }), additionalProperties: false },
126
142
  output: { schema: scanSchema, render: renderFindings },
127
143
  async execute(rawArgs) {
128
- const args = (rawArgs ?? {});
144
+ const args = scanArguments(rawArgs, ['base', 'target', 'staged']);
129
145
  const base = optionalString(args, 'base') ?? 'HEAD';
130
146
  const target = optionalString(args, 'target');
131
147
  const policy = await loadPolicy(cwd);
@@ -152,9 +168,9 @@ export function buildSecureTools(cfg, cwd, runner) {
152
168
  const secureFixVerify = {
153
169
  name: 'secure_fix_verify',
154
170
  description: '复扫并与上次扫描基线对比,输出已关闭、仍存在、新引入的问题指纹。用于修复后确认没有按下葫芦浮起瓢。',
155
- parameters: compileParameters({
156
- target: { type: 'string', description: '复扫文件或目录(可选,缺省扫描整个工作区)。' },
157
- }),
171
+ parameters: { ...compileParameters({
172
+ target: { type: 'string', description: '复扫文件或目录(可选,缺省扫描整个工作区)。' },
173
+ }), additionalProperties: false },
158
174
  output: {
159
175
  schema: {
160
176
  type: 'object',
@@ -172,7 +188,7 @@ export function buildSecureTools(cfg, cwd, runner) {
172
188
  },
173
189
  },
174
190
  async execute(rawArgs) {
175
- const args = (rawArgs ?? {});
191
+ const args = scanArguments(rawArgs);
176
192
  const target = optionalString(args, 'target');
177
193
  const policy = await loadPolicy(cwd);
178
194
  const state = await loadState(stateDir);
@@ -320,7 +336,7 @@ export function buildSecureTools(cfg, cwd, runner) {
320
336
  const state = await loadState(stateDir);
321
337
  if (state.last === null)
322
338
  throw new Error('尚无扫描结果,请先执行 secure_scan 或 secure_diff。');
323
- const text = format === 'sarif' ? buildSarif(state.last.findings, state.last.target) : buildMarkdown(state.last.findings, state.last.target);
339
+ const text = format === 'sarif' ? buildSarif(state.last.findings, cwd) : buildMarkdown(state.last.findings, state.last.target);
324
340
  if (target !== undefined)
325
341
  await writeFile(target, text, 'utf8');
326
342
  return { format, path: target ?? '', text, findingCount: state.last.findings.length };
@@ -362,15 +378,15 @@ function buildMarkdown(findings, target) {
362
378
  }
363
379
  return lines.join('\n') + '\n';
364
380
  }
365
- function buildSarif(findings, target) {
381
+ function buildSarif(findings, cwd) {
366
382
  const rules = new Map();
367
383
  const results = findings.map((finding, index) => {
368
- rules.set(finding.ruleId, { id: finding.ruleId, name: finding.title, shortDescription: finding.title });
384
+ rules.set(finding.ruleId, { id: finding.ruleId, name: finding.title, shortDescription: { text: finding.title } });
369
385
  return {
370
386
  ruleId: finding.ruleId,
371
387
  level: finding.severity === 'critical' || finding.severity === 'high' ? 'error' : finding.severity === 'medium' ? 'warning' : 'note',
372
388
  message: { text: finding.message },
373
- locations: [{ physicalLocation: { artifactLocation: { uri: finding.file.replace(/\\/g, '/') }, region: { startLine: finding.line } } }],
389
+ locations: [{ physicalLocation: { artifactLocation: { uri: finding.file.replace(/\\/g, '/').split('/').map(encodeURIComponent).join('/'), uriBaseId: 'ROOTPATH' }, region: { startLine: finding.line } } }],
374
390
  partialFingerprints: { primaryLocationLineHash: finding.id + ':' + String(index) },
375
391
  };
376
392
  });
@@ -379,7 +395,7 @@ function buildSarif(findings, target) {
379
395
  $schema: 'https://json.schemastore.org/sarif-2.1.0.json',
380
396
  runs: [{
381
397
  tool: { driver: { name: 'dsh-code-security', version: '0.1.0', informationUri: 'https://github.com/STARDUSTLC666/dsh-code-security', rules: [...rules.values()] } },
382
- originalUriBaseIds: { ROOTPATH: { uri: 'file:///' + target.replace(/\\/g, '/') } },
398
+ originalUriBaseIds: { ROOTPATH: { uri: pathToFileURL(path.resolve(cwd) + path.sep).href } },
383
399
  results,
384
400
  }],
385
401
  }, null, 2);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-code-security",
3
- "version": "0.3.3",
4
- "description": "DSH AI 代码安全审查插件:secure_scan/secure_diff/secure_fix_verify/secure_report/secure_export/secure_baseline/secure_deps/secure_policy_show/secure_policy_set 九工具,40+ 确定性规则、密钥熵检测、git diff/staged 审查、SARIF 导出、基线接受与 SBOM-lite,零运行时依赖。",
3
+ "version": "0.3.6",
4
+ "description": "DSH 代码安全审查插件:40+ 确定性规则与密钥熵检测,支持 git diff/staged 审查、SARIF 导出、基线接受与 SBOM-lite。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",