dsh-code-security 0.4.0 → 0.4.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 CHANGED
@@ -4,6 +4,11 @@
4
4
 
5
5
  [历史英文记录](docs/CHANGELOG.en.md)
6
6
 
7
+ ## 0.4.1 (2026-10-15)
8
+
9
+ - 修复同一行同时命中高熵密钥和其他安全规则时,其他规则的证据仍带出原始字符串的问题;所有证据统一使用同一套高熵脱敏判断,也覆盖截断的字符串。
10
+ - 回归检查模型返回值、历史、已接受基线、SARIF 与 Markdown,保留原始证据指纹用于跟踪。
11
+
7
12
  ## 0.4.0 (2026-10-15)
8
13
 
9
14
  - 增加 DSH 中英文设置工作台:项目选择、扫描/增量/复查、覆盖情况、问题筛选、脱敏证据、复制位置与报告下载。
@@ -2,6 +2,10 @@
2
2
 
3
3
  本页整理原 README 的历史验证说明,保留当时的版本、日期与范围。自动测试、启动检查、浏览器操作和真实服务验收分别记录,不能相互替代。更详细的版本验收文件仍保留在仓库中。
4
4
 
5
+ ## 0.4.1(2026-10-15)
6
+
7
+ 79 项 Windows 测试全部通过,包含下列三个真实原生引擎。新增同一行同时命中注入与高熵密钥的回归,确认其他规则的证据、返回值、历史、基线和导出也不再带出原值;截断的字符串使用相同判断。其余功能与客户端沿用 0.4.0 的验收范围。
8
+
5
9
  ## 0.4.0(2026-10-15)
6
10
 
7
11
  本地环境:Windows、Node 24.16.0、官方 npm DSH 0.2.1-alpha.3。全部使用 E 盘隔离目录与合成数据,没有复制用户密钥或修改前台桌面窗口。
package/lib/privacy.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Finding } from './scanner.js';
2
+ export declare function isHighEntropyToken(value: string): boolean;
2
3
  export declare function evidenceFingerprint(finding: Pick<Finding, 'ruleId' | 'file' | 'snippet'> & {
3
4
  fingerprint?: string;
4
5
  }): string;
package/lib/privacy.js CHANGED
@@ -1,6 +1,19 @@
1
1
  /** Findings crossing the scanner boundary never contain complete credentials. */
2
2
  import { createHash } from 'node:crypto';
3
3
  import path from 'node:path';
4
+ export function isHighEntropyToken(value) {
5
+ if (value.length < 24 || !/\d/.test(value) || /^[0-9]+$/.test(value))
6
+ return false;
7
+ const counts = new Map();
8
+ for (const char of value)
9
+ counts.set(char, (counts.get(char) ?? 0) + 1);
10
+ let entropy = 0;
11
+ for (const count of counts.values()) {
12
+ const frequency = count / value.length;
13
+ entropy -= frequency * Math.log2(frequency);
14
+ }
15
+ return entropy >= 3.8;
16
+ }
4
17
  export function evidenceFingerprint(finding) {
5
18
  if (/^[a-f0-9]{64}$/.test(finding.fingerprint ?? ''))
6
19
  return finding.fingerprint;
@@ -15,7 +28,9 @@ export function redactText(text) {
15
28
  .replace(/(^\s*[A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASS)[A-Z0-9_]*\s*=\s*)([^\r\n]+)/gm, '$1[REDACTED]')
16
29
  .replace(/\b(?:gh[pousr]_[A-Za-z0-9_]{16,}|github_pat_[A-Za-z0-9_]{20,}|sk-(?:proj-)?[A-Za-z0-9_-]{16,}|AKIA[A-Z0-9]{16})\b/g, '[REDACTED]')
17
30
  .replace(/(\bBearer\s+)[A-Za-z0-9._~+\/-]{12,}/gi, '$1[REDACTED]')
18
- .replace(/(https?:\/\/)[^\s/@]+:[^\s/@]+@/g, '$1[REDACTED]@');
31
+ .replace(/(https?:\/\/)[^\s/@]+:[^\s/@]+@/g, '$1[REDACTED]@')
32
+ // A credential must stay redacted when another rule reports the same source line.
33
+ .replace(/(['"`])([A-Za-z0-9+/=_-]{24,})(?:\1|$)/g, (match, quote, value) => isHighEntropyToken(value) ? quote + '[REDACTED]' + quote : match);
19
34
  }
20
35
  export function protectFinding(finding) {
21
36
  const fingerprint = evidenceFingerprint(finding);
package/lib/scanner.js CHANGED
@@ -9,7 +9,7 @@ import { createHash } from 'node:crypto';
9
9
  import { DEFAULT_EXCLUDE_DIRS } from './config.js';
10
10
  import { languageForFile, RULES } from './rules.js';
11
11
  import { policyExcludesDir, policyIgnores } from './policy.js';
12
- import { protectFinding } from './privacy.js';
12
+ import { protectFinding, isHighEntropyToken } from './privacy.js';
13
13
  import { sanitizedHtmlLines } from './syntax.js';
14
14
  const DISABLE_NEXT = /secure-review-disable-next-line(?:\s+([\w,-]+))?/;
15
15
  const DISABLE_LINE = /secure-review-disable-line(?:\s+([\w,-]+))?/;
@@ -50,31 +50,12 @@ function shouldScanFile(file) {
50
50
  return true;
51
51
  return TEXT_EXTENSIONS.test(file);
52
52
  }
53
- /** Shannon 熵;只对 24 位以上且以字母数字为主的 token 判分。 */
54
- function shannon(text) {
55
- if (text.length < 16)
56
- return 0;
57
- const counts = new Map();
58
- for (const char of text)
59
- counts.set(char, (counts.get(char) ?? 0) + 1);
60
- let entropy = 0;
61
- for (const count of counts.values()) {
62
- const p = count / text.length;
63
- entropy -= p * Math.log2(p);
64
- }
65
- return entropy;
66
- }
67
53
  const TOKEN_PATTERN = /['"]([A-Za-z0-9+/=_-]{24,})['"]/g;
68
- const HIGH_ENTROPY_THRESHOLD = 3.8;
69
54
  function detectHighEntropy(line, lineNumber, rel, rule) {
70
55
  const findings = [];
71
56
  for (const match of line.matchAll(TOKEN_PATTERN)) {
72
57
  const token = match[1];
73
- if (/^[0-9]+$/.test(token))
74
- continue;
75
- if (!/\d/.test(token))
76
- continue;
77
- if (shannon(token) < HIGH_ENTROPY_THRESHOLD)
58
+ if (!isHighEntropyToken(token))
78
59
  continue;
79
60
  const id = makeId(rule.id, rel, lineNumber, token.slice(0, 32));
80
61
  findings.push({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-code-security",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "对代码与 Git 改动进行本地静态安全检查,并追踪修复结果。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",