dsh-config-manager 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/README.md +216 -0
- package/README.zh-CN.md +218 -0
- package/cordis.patch.yml +45 -0
- package/dsh.bundle.patch +21 -0
- package/dsh.client +39 -0
- package/lib/adapters/agent-presets.d.ts +13 -0
- package/lib/adapters/agent-presets.js +14 -0
- package/lib/adapters/agent-presets.js.map +1 -0
- package/lib/adapters/credentials.d.ts +36 -0
- package/lib/adapters/credentials.js +121 -0
- package/lib/adapters/credentials.js.map +1 -0
- package/lib/adapters/file-collection.d.ts +14 -0
- package/lib/adapters/file-collection.js +102 -0
- package/lib/adapters/file-collection.js.map +1 -0
- package/lib/adapters/index.d.ts +38 -0
- package/lib/adapters/index.js +46 -0
- package/lib/adapters/index.js.map +1 -0
- package/lib/adapters/mcp.d.ts +27 -0
- package/lib/adapters/mcp.js +175 -0
- package/lib/adapters/mcp.js.map +1 -0
- package/lib/adapters/plugin-files.d.ts +15 -0
- package/lib/adapters/plugin-files.js +91 -0
- package/lib/adapters/plugin-files.js.map +1 -0
- package/lib/adapters/plugins.d.ts +13 -0
- package/lib/adapters/plugins.js +177 -0
- package/lib/adapters/plugins.js.map +1 -0
- package/lib/adapters/prompts.d.ts +29 -0
- package/lib/adapters/prompts.js +228 -0
- package/lib/adapters/prompts.js.map +1 -0
- package/lib/adapters/providers.d.ts +33 -0
- package/lib/adapters/providers.js +143 -0
- package/lib/adapters/providers.js.map +1 -0
- package/lib/adapters/sessions.d.ts +14 -0
- package/lib/adapters/sessions.js +15 -0
- package/lib/adapters/sessions.js.map +1 -0
- package/lib/adapters/settings.d.ts +23 -0
- package/lib/adapters/settings.js +142 -0
- package/lib/adapters/settings.js.map +1 -0
- package/lib/adapters/skills.d.ts +13 -0
- package/lib/adapters/skills.js +14 -0
- package/lib/adapters/skills.js.map +1 -0
- package/lib/adapters/test-helpers.d.ts +99 -0
- package/lib/adapters/test-helpers.js +192 -0
- package/lib/adapters/test-helpers.js.map +1 -0
- package/lib/adapters/ui.d.ts +26 -0
- package/lib/adapters/ui.js +88 -0
- package/lib/adapters/ui.js.map +1 -0
- package/lib/adapters/workspaces.d.ts +12 -0
- package/lib/adapters/workspaces.js +92 -0
- package/lib/adapters/workspaces.js.map +1 -0
- package/lib/client.d.ts +377 -0
- package/lib/client.js +2598 -0
- package/lib/client.js.map +1 -0
- package/lib/core/analyzer.d.ts +35 -0
- package/lib/core/analyzer.js +563 -0
- package/lib/core/analyzer.js.map +1 -0
- package/lib/core/backup.d.ts +29 -0
- package/lib/core/backup.js +184 -0
- package/lib/core/backup.js.map +1 -0
- package/lib/core/exporter.d.ts +37 -0
- package/lib/core/exporter.js +222 -0
- package/lib/core/exporter.js.map +1 -0
- package/lib/core/importer.d.ts +42 -0
- package/lib/core/importer.js +25 -0
- package/lib/core/importer.js.map +1 -0
- package/lib/core/index.d.ts +12 -0
- package/lib/core/index.js +11 -0
- package/lib/core/index.js.map +1 -0
- package/lib/core/rollback.d.ts +9 -0
- package/lib/core/rollback.js +136 -0
- package/lib/core/rollback.js.map +1 -0
- package/lib/core/types.d.ts +331 -0
- package/lib/core/types.js +18 -0
- package/lib/core/types.js.map +1 -0
- package/lib/core/validator.d.ts +18 -0
- package/lib/core/validator.js +77 -0
- package/lib/core/validator.js.map +1 -0
- package/lib/index.d.ts +51 -0
- package/lib/index.js +875 -0
- package/lib/index.js.map +1 -0
- package/lib/migrations/index.d.ts +29 -0
- package/lib/migrations/index.js +56 -0
- package/lib/migrations/index.js.map +1 -0
- package/lib/migrations/v1-to-v2.d.ts +9 -0
- package/lib/migrations/v1-to-v2.js +10 -0
- package/lib/migrations/v1-to-v2.js.map +1 -0
- package/lib/profiles/index.d.ts +4 -0
- package/lib/profiles/index.js +5 -0
- package/lib/profiles/index.js.map +1 -0
- package/lib/profiles/profile-manager.d.ts +102 -0
- package/lib/profiles/profile-manager.js +522 -0
- package/lib/profiles/profile-manager.js.map +1 -0
- package/lib/schema/config.d.ts +28 -0
- package/lib/schema/config.js +136 -0
- package/lib/schema/config.js.map +1 -0
- package/lib/schema/manifest.d.ts +28 -0
- package/lib/schema/manifest.js +109 -0
- package/lib/schema/manifest.js.map +1 -0
- package/lib/schema/types.d.ts +169 -0
- package/lib/schema/types.js +8 -0
- package/lib/schema/types.js.map +1 -0
- package/lib/schema/versions.d.ts +27 -0
- package/lib/schema/versions.js +50 -0
- package/lib/schema/versions.js.map +1 -0
- package/lib/security/encryption.d.ts +41 -0
- package/lib/security/encryption.js +152 -0
- package/lib/security/encryption.js.map +1 -0
- package/lib/security/index.d.ts +16 -0
- package/lib/security/index.js +17 -0
- package/lib/security/index.js.map +1 -0
- package/lib/security/integrity.d.ts +30 -0
- package/lib/security/integrity.js +78 -0
- package/lib/security/integrity.js.map +1 -0
- package/lib/security/redaction.d.ts +26 -0
- package/lib/security/redaction.js +95 -0
- package/lib/security/redaction.js.map +1 -0
- package/lib/security/secret-scanner.d.ts +48 -0
- package/lib/security/secret-scanner.js +253 -0
- package/lib/security/secret-scanner.js.map +1 -0
- package/lib/security/zip-security.d.ts +27 -0
- package/lib/security/zip-security.js +154 -0
- package/lib/security/zip-security.js.map +1 -0
- package/lib/ui/conflict-view.d.ts +28 -0
- package/lib/ui/conflict-view.js +49 -0
- package/lib/ui/conflict-view.js.map +1 -0
- package/lib/ui/errors.d.ts +17 -0
- package/lib/ui/errors.js +76 -0
- package/lib/ui/errors.js.map +1 -0
- package/lib/ui/export-flow.d.ts +62 -0
- package/lib/ui/export-flow.js +79 -0
- package/lib/ui/export-flow.js.map +1 -0
- package/lib/ui/import-wizard.d.ts +67 -0
- package/lib/ui/import-wizard.js +169 -0
- package/lib/ui/import-wizard.js.map +1 -0
- package/lib/ui/path-mapping.d.ts +38 -0
- package/lib/ui/path-mapping.js +75 -0
- package/lib/ui/path-mapping.js.map +1 -0
- package/lib/ui/progress.d.ts +27 -0
- package/lib/ui/progress.js +76 -0
- package/lib/ui/progress.js.map +1 -0
- package/lib/ui/report.d.ts +21 -0
- package/lib/ui/report.js +146 -0
- package/lib/ui/report.js.map +1 -0
- package/lib/ui/test-helpers.d.ts +62 -0
- package/lib/ui/test-helpers.js +147 -0
- package/lib/ui/test-helpers.js.map +1 -0
- package/lib/ui/types.d.ts +129 -0
- package/lib/ui/types.js +21 -0
- package/lib/ui/types.js.map +1 -0
- package/lib/utils/hashing.d.ts +17 -0
- package/lib/utils/hashing.js +41 -0
- package/lib/utils/hashing.js.map +1 -0
- package/lib/utils/json.d.ts +24 -0
- package/lib/utils/json.js +76 -0
- package/lib/utils/json.js.map +1 -0
- package/lib/utils/logger.d.ts +39 -0
- package/lib/utils/logger.js +82 -0
- package/lib/utils/logger.js.map +1 -0
- package/lib/utils/paths.d.ts +31 -0
- package/lib/utils/paths.js +132 -0
- package/lib/utils/paths.js.map +1 -0
- package/lib/utils/zip.d.ts +54 -0
- package/lib/utils/zip.js +285 -0
- package/lib/utils/zip.js.map +1 -0
- package/package.json +109 -0
- package/src/adapters/agent-presets.ts +14 -0
- package/src/adapters/credentials.test.ts +90 -0
- package/src/adapters/credentials.ts +141 -0
- package/src/adapters/file-collection.ts +110 -0
- package/src/adapters/files.test.ts +113 -0
- package/src/adapters/index.ts +69 -0
- package/src/adapters/mcp.test.ts +96 -0
- package/src/adapters/mcp.ts +181 -0
- package/src/adapters/plugin-files.ts +96 -0
- package/src/adapters/plugins.test.ts +72 -0
- package/src/adapters/plugins.ts +176 -0
- package/src/adapters/prompts.test.ts +110 -0
- package/src/adapters/prompts.ts +226 -0
- package/src/adapters/providers.test.ts +125 -0
- package/src/adapters/providers.ts +161 -0
- package/src/adapters/roundtrip.test.ts +188 -0
- package/src/adapters/sessions.ts +15 -0
- package/src/adapters/settings.test.ts +93 -0
- package/src/adapters/settings.ts +165 -0
- package/src/adapters/skills.ts +14 -0
- package/src/adapters/test-helpers.ts +207 -0
- package/src/adapters/ui.test.ts +56 -0
- package/src/adapters/ui.ts +106 -0
- package/src/adapters/workspaces.test.ts +79 -0
- package/src/adapters/workspaces.ts +95 -0
- package/src/client/CLIENT_DEPENDENCIES.md +101 -0
- package/src/client/ConfigManagerSection.tsx +60 -0
- package/src/client/api.ts +270 -0
- package/src/client/client-types.ts +27 -0
- package/src/client/common/ErrorBanner.tsx +68 -0
- package/src/client/common/ProgressBar.tsx +49 -0
- package/src/client/common/ReportView.tsx +117 -0
- package/src/client/common/ui.tsx +165 -0
- package/src/client/config-manager.module.css +759 -0
- package/src/client/css-modules.d.ts +8 -0
- package/src/client/export/ExportView.tsx +222 -0
- package/src/client/import/ConflictList.tsx +72 -0
- package/src/client/import/ImportWizardView.tsx +406 -0
- package/src/client/import/PathMappingForm.tsx +77 -0
- package/src/client/index.ts +65 -0
- package/src/client/locales.ts +182 -0
- package/src/core/analyzer.ts +631 -0
- package/src/core/backup.ts +212 -0
- package/src/core/exporter.ts +246 -0
- package/src/core/importer.ts +66 -0
- package/src/core/index.ts +39 -0
- package/src/core/rollback.ts +140 -0
- package/src/core/smoke.test.ts +717 -0
- package/src/core/types.ts +343 -0
- package/src/core/validator.ts +84 -0
- package/src/index.ts +942 -0
- package/src/migrations/index.ts +71 -0
- package/src/migrations/v1-to-v2.ts +17 -0
- package/src/profiles/index.ts +8 -0
- package/src/profiles/profile-manager.test.ts +280 -0
- package/src/profiles/profile-manager.ts +606 -0
- package/src/schema/config.ts +137 -0
- package/src/schema/manifest.ts +124 -0
- package/src/schema/types.ts +179 -0
- package/src/schema/versions.ts +55 -0
- package/src/security/encryption.ts +180 -0
- package/src/security/index.ts +16 -0
- package/src/security/integrity.ts +102 -0
- package/src/security/redaction.ts +101 -0
- package/src/security/secret-scanner.ts +275 -0
- package/src/security/security.test.ts +796 -0
- package/src/security/zip-security.ts +180 -0
- package/src/ui/conflict-view.test.ts +62 -0
- package/src/ui/conflict-view.ts +65 -0
- package/src/ui/errors.test.ts +44 -0
- package/src/ui/errors.ts +95 -0
- package/src/ui/export-flow.test.ts +75 -0
- package/src/ui/export-flow.ts +128 -0
- package/src/ui/import-wizard.test.ts +128 -0
- package/src/ui/import-wizard.ts +208 -0
- package/src/ui/path-mapping.test.ts +67 -0
- package/src/ui/path-mapping.ts +103 -0
- package/src/ui/progress.test.ts +42 -0
- package/src/ui/progress.ts +90 -0
- package/src/ui/report.test.ts +93 -0
- package/src/ui/report.ts +140 -0
- package/src/ui/test-helpers.ts +177 -0
- package/src/ui/types.ts +193 -0
- package/src/utils/hashing.ts +53 -0
- package/src/utils/json.ts +77 -0
- package/src/utils/logger.ts +114 -0
- package/src/utils/paths.ts +122 -0
- package/src/utils/zip.ts +332 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 敏感字段扫描器(规范 §6 / 设计 §7.2 / core `SecretScanner` 契约的强化实现)。
|
|
3
|
+
*
|
|
4
|
+
* 定位:`ctx.settings.describe({redactSecrets:true})` 已剥离 DSH 已知秘密(设计 §7.2),
|
|
5
|
+
* 本扫描器是**第二道防线**,兜底插件自定义字段、MCP headers、patch config、文件类分区文本。
|
|
6
|
+
*
|
|
7
|
+
* 判定策略(两级,避免误伤/漏报):
|
|
8
|
+
* - 字段名:规范化(小写、去 `_-. ` 分隔符)后做「精确命中 / 敏感后缀 / 敏感前缀」三级匹配;
|
|
9
|
+
* 只剥离**字符串叶值**(对象/数组整体递归,不整块剥离——保证 `credentials: [...]` 状态数组存活)。
|
|
10
|
+
* - 引用豁免:`apiKeyEnv` 等「只存引用名」字段、以及形如 `DEEPSEEK_API_KEY` 的全大写环境变量名
|
|
11
|
+
* (`isEnvVarName`)一律保留——值是名字不是秘密。
|
|
12
|
+
* - 值形状启发式(规范 §6「不能只根据固定字段判断」):字段名未命中但值长得像 secret
|
|
13
|
+
* (sk- / JWT / AKIA / GitHub PAT / PEM 私钥 / Bearer)也剥离;高熵 base64 默认关(误伤率高)。
|
|
14
|
+
*
|
|
15
|
+
* 实现不变量:返回 sanitized 为**全新对象**(不改原数据);迭代式显式栈(防爆栈)+ 深度上限;
|
|
16
|
+
* 命中记录只含路径与字段名,**值永不外泄**。
|
|
17
|
+
*/
|
|
18
|
+
import { JsonDepthError } from "../utils/json.js";
|
|
19
|
+
/** 剥离占位(与 core defaultSecretScanner 语义一致:空串 = 需补录提示) */
|
|
20
|
+
export const REDACTED_PLACEHOLDER = '';
|
|
21
|
+
/**
|
|
22
|
+
* 敏感字段名单(规范化名:小写、无分隔符)。
|
|
23
|
+
* 覆盖规范 §6 原始清单 + 设计 §7.2 + core logger 名单,并保持克制以防误伤
|
|
24
|
+
* (不收录 `key`/`session` 等过宽子串——`sessionIds`/`monkey` 不得中招)。
|
|
25
|
+
*/
|
|
26
|
+
export const DEFAULT_SECRET_FIELD_NAMES = [
|
|
27
|
+
// 规范 §6 原文
|
|
28
|
+
'password', 'passwd', 'token', 'accesstoken', 'refreshtoken',
|
|
29
|
+
'apikey', 'secret', 'credential', 'authorization', 'cookie',
|
|
30
|
+
'privatekey', 'clientsecret',
|
|
31
|
+
// 设计 §7.2 / core logger 扩充
|
|
32
|
+
'pwd', 'passphrase', 'authtoken', 'sessionkey', 'apisecret',
|
|
33
|
+
'authheader', 'bearer', 'webhooksecret',
|
|
34
|
+
];
|
|
35
|
+
/** 引用字段(值只是「名字」而非秘密):规范化精确命中即豁免 */
|
|
36
|
+
export const DEFAULT_REFERENCE_FIELDS = [
|
|
37
|
+
'apikeyenv', 'apikeyname', 'apikeyref',
|
|
38
|
+
'tokenenv', 'accesstokenenv', 'refreshtokenenv',
|
|
39
|
+
'clientsecretenv', 'passwordenv', 'secretref', 'credentialref',
|
|
40
|
+
];
|
|
41
|
+
/** 敏感后缀(规范化后以这些结尾 → 命中;`key` 故意不收,避免 monkey/whiskey 误伤) */
|
|
42
|
+
const SENSITIVE_SUFFIXES = ['token', 'secret', 'password', 'passwd', 'credential'];
|
|
43
|
+
/** 敏感前缀(规范化后以这些开头 → 命中;token/secret 等过宽词不收入) */
|
|
44
|
+
const SENSITIVE_PREFIXES = [
|
|
45
|
+
'apikey', 'accesstoken', 'refreshtoken', 'authtoken',
|
|
46
|
+
'clientsecret', 'privatekey', 'authorization', 'authheader',
|
|
47
|
+
'webhooksecret', 'apisecret',
|
|
48
|
+
];
|
|
49
|
+
export const SECRET_VALUE_PATTERNS = [
|
|
50
|
+
{ name: 'openai-style-key', re: /sk-[A-Za-z0-9_-]{8,}/ },
|
|
51
|
+
{ name: 'jwt', re: /eyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/ },
|
|
52
|
+
{ name: 'aws-access-key', re: /AKIA[0-9A-Z]{16}/ },
|
|
53
|
+
{ name: 'github-token', re: /gh[pousr]_[A-Za-z0-9]{20,}/ },
|
|
54
|
+
{ name: 'github-pat', re: /github_pat_[A-Za-z0-9_]{20,}/ },
|
|
55
|
+
{ name: 'pem-private-key', re: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |ENCRYPTED )?PRIVATE KEY-----/ },
|
|
56
|
+
{ name: 'bearer-token', re: /Bearer [A-Za-z0-9._~+/=-]{8,}/ },
|
|
57
|
+
];
|
|
58
|
+
/** 规范化字段名:小写 + 去 `_-. ` 分隔符(用于名单匹配) */
|
|
59
|
+
export function normalizeFieldName(name) {
|
|
60
|
+
return name.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
61
|
+
}
|
|
62
|
+
/** 是否为「环境变量名」形态(全大写下划线,如 DEEPSEEK_API_KEY)→ 视为引用名 */
|
|
63
|
+
export function isEnvVarName(value) {
|
|
64
|
+
return /^[A-Z][A-Z0-9_]{2,}$/.test(value);
|
|
65
|
+
}
|
|
66
|
+
/** 字段名是否命中敏感名单(规范化后精确 / 敏感后缀 / 敏感前缀) */
|
|
67
|
+
export function isSensitiveFieldName(field, extra = []) {
|
|
68
|
+
const norm = normalizeFieldName(field);
|
|
69
|
+
if (norm === '')
|
|
70
|
+
return false;
|
|
71
|
+
if ([...DEFAULT_SECRET_FIELD_NAMES, ...extra].includes(norm))
|
|
72
|
+
return true;
|
|
73
|
+
if (SENSITIVE_SUFFIXES.some((s) => norm.endsWith(s) && norm.length > s.length))
|
|
74
|
+
return true;
|
|
75
|
+
return SENSITIVE_PREFIXES.some((p) => norm.startsWith(p));
|
|
76
|
+
}
|
|
77
|
+
/** 是否为引用字段(值只是名字,不剥离) */
|
|
78
|
+
export function isReferenceField(field, extra = []) {
|
|
79
|
+
const norm = normalizeFieldName(field);
|
|
80
|
+
return [...DEFAULT_REFERENCE_FIELDS, ...extra].includes(norm);
|
|
81
|
+
}
|
|
82
|
+
/** 值是否命中强模式 secret 形状(返回命中模式名;未命中返回 null) */
|
|
83
|
+
export function matchSecretValuePattern(value) {
|
|
84
|
+
for (const p of SECRET_VALUE_PATTERNS) {
|
|
85
|
+
if (p.re.test(value))
|
|
86
|
+
return p.name;
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
/** 高熵启发式:长 base64/hex 串且 Shannon 熵高(默认关) */
|
|
91
|
+
function isHighEntropySecret(value) {
|
|
92
|
+
if (!/^[A-Za-z0-9+/=_-]{32,}$/.test(value))
|
|
93
|
+
return false;
|
|
94
|
+
if (/^[A-Za-z0-9_-]+$/.test(value) && !/[0-9]/.test(value))
|
|
95
|
+
return false; // 纯单词串(如文件名)
|
|
96
|
+
const counts = new Map();
|
|
97
|
+
for (const ch of value)
|
|
98
|
+
counts.set(ch, (counts.get(ch) ?? 0) + 1);
|
|
99
|
+
let entropy = 0;
|
|
100
|
+
for (const n of counts.values()) {
|
|
101
|
+
const p = n / value.length;
|
|
102
|
+
entropy -= p * Math.log2(p);
|
|
103
|
+
}
|
|
104
|
+
return entropy > 4.2;
|
|
105
|
+
}
|
|
106
|
+
/** 判定单个「字段名 + 字符串值」:strip | keep(含引用豁免与值形状)
|
|
107
|
+
* 顺序:值形状优先(AKIA 等全大写 secret 形状不能被 env 名豁免误放)→ 引用字段 → env 名 → 字段名。 */
|
|
108
|
+
function judgeFieldValue(field, value, opts) {
|
|
109
|
+
if (value === '')
|
|
110
|
+
return false;
|
|
111
|
+
if (opts.valuePatterns && matchSecretValuePattern(value) !== null)
|
|
112
|
+
return true;
|
|
113
|
+
if (isReferenceField(field, opts.extraReferenceFields))
|
|
114
|
+
return false;
|
|
115
|
+
if (isEnvVarName(value))
|
|
116
|
+
return false; // 值是 env 变量名 → 引用
|
|
117
|
+
if (isSensitiveFieldName(field, opts.extraFieldNames))
|
|
118
|
+
return true;
|
|
119
|
+
if (opts.highEntropy && isHighEntropySecret(value))
|
|
120
|
+
return true;
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
/** 递归扫描 + 剥离(纯函数,不改原数据;深度保护;返回清洗后数据与命中清单) */
|
|
124
|
+
export function scanAndRedact(data, opts = {}) {
|
|
125
|
+
const maxDepth = opts.maxDepth ?? 64;
|
|
126
|
+
const cfg = {
|
|
127
|
+
extraFieldNames: opts.extraFieldNames ?? [],
|
|
128
|
+
extraReferenceFields: opts.extraReferenceFields ?? [],
|
|
129
|
+
valuePatterns: opts.valuePatterns ?? true,
|
|
130
|
+
highEntropy: opts.highEntropy ?? false,
|
|
131
|
+
};
|
|
132
|
+
const hits = [];
|
|
133
|
+
if (data === null || typeof data !== 'object')
|
|
134
|
+
return { sanitized: data, hits };
|
|
135
|
+
if (data instanceof Uint8Array)
|
|
136
|
+
return { sanitized: data, hits };
|
|
137
|
+
const rootOut = Array.isArray(data) ? [] : {};
|
|
138
|
+
const stack = [{ src: data, dst: rootOut, path: '', depth: 1 }];
|
|
139
|
+
const visited = new WeakSet();
|
|
140
|
+
visited.add(data);
|
|
141
|
+
while (stack.length > 0) {
|
|
142
|
+
const frame = stack.pop();
|
|
143
|
+
const { src, dst, path, depth } = frame;
|
|
144
|
+
if (depth > maxDepth)
|
|
145
|
+
throw new JsonDepthError(`扫描深度 ${depth} 超过上限 ${maxDepth}`);
|
|
146
|
+
if (src instanceof Uint8Array)
|
|
147
|
+
continue;
|
|
148
|
+
if (Array.isArray(src)) {
|
|
149
|
+
const outArr = dst;
|
|
150
|
+
for (let i = 0; i < src.length; i++) {
|
|
151
|
+
const v = src[i];
|
|
152
|
+
const childPath = `${path}[${i}]`;
|
|
153
|
+
if (v !== null && typeof v === 'object' && !(v instanceof Uint8Array)) {
|
|
154
|
+
if (visited.has(v))
|
|
155
|
+
throw new Error(`检测到循环引用,无法扫描(路径 ${childPath})`);
|
|
156
|
+
visited.add(v);
|
|
157
|
+
const childOut = Array.isArray(v) ? [] : {};
|
|
158
|
+
outArr.push(childOut);
|
|
159
|
+
stack.push({ src: v, dst: childOut, path: childPath, depth: depth + 1 });
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
outArr.push(v);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
const outObj = dst;
|
|
168
|
+
for (const [k, v] of Object.entries(src)) {
|
|
169
|
+
const childPath = path === '' ? k : `${path}.${k}`;
|
|
170
|
+
if (typeof v === 'string') {
|
|
171
|
+
if (judgeFieldValue(k, v, cfg)) {
|
|
172
|
+
hits.push({ path: childPath, field: k });
|
|
173
|
+
outObj[k] = REDACTED_PLACEHOLDER;
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
outObj[k] = v;
|
|
177
|
+
}
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (v !== null && typeof v === 'object' && !(v instanceof Uint8Array)) {
|
|
181
|
+
if (visited.has(v))
|
|
182
|
+
throw new Error(`检测到循环引用,无法扫描(路径 ${childPath})`);
|
|
183
|
+
visited.add(v);
|
|
184
|
+
const childOut = Array.isArray(v) ? [] : {};
|
|
185
|
+
outObj[k] = childOut;
|
|
186
|
+
stack.push({ src: v, dst: childOut, path: childPath, depth: depth + 1 });
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
outObj[k] = v;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return { sanitized: rootOut, hits };
|
|
194
|
+
}
|
|
195
|
+
/* ---------------- 文本级扫描(scanText,文件类分区内容用) ---------------- */
|
|
196
|
+
const FIELD_VALUE_RE = [
|
|
197
|
+
// "field": "value"(JSON 形态,保留引号)
|
|
198
|
+
{ re: /"([A-Za-z0-9_.\-]+)"\s*:\s*"([^"]*)"/g, json: true },
|
|
199
|
+
// field=value
|
|
200
|
+
{ re: /([A-Za-z0-9_.\-]+)\s*=\s*([^\s,;]+)/g, json: false },
|
|
201
|
+
// field: value
|
|
202
|
+
{ re: /([A-Za-z0-9_.\-]+)\s*:\s*([^\s,;}]+)/g, json: false },
|
|
203
|
+
];
|
|
204
|
+
/** 文本级扫描:逐行找「敏感字段名 = 值」与「值形状」命中(只报告,不修改文本) */
|
|
205
|
+
export function scanText(text, opts = {}) {
|
|
206
|
+
const hits = [];
|
|
207
|
+
const extra = opts.extraFieldNames ?? [];
|
|
208
|
+
const valuePatterns = opts.valuePatterns ?? true;
|
|
209
|
+
const lines = text.split(/\r?\n/);
|
|
210
|
+
for (let i = 0; i < lines.length; i++) {
|
|
211
|
+
const line = lines[i];
|
|
212
|
+
const linePath = `line:${i + 1}`;
|
|
213
|
+
// 字段名形态
|
|
214
|
+
for (const { re, json } of FIELD_VALUE_RE) {
|
|
215
|
+
re.lastIndex = 0;
|
|
216
|
+
let m;
|
|
217
|
+
while ((m = re.exec(line)) !== null) {
|
|
218
|
+
const field = m[1];
|
|
219
|
+
const value = m[2];
|
|
220
|
+
if (!isSensitiveFieldName(field, extra))
|
|
221
|
+
continue;
|
|
222
|
+
if (json) {
|
|
223
|
+
// JSON 形态下引号内值才报告(引用名豁免由调用方结合场景决定,这里保守报告)
|
|
224
|
+
hits.push({ path: linePath, field });
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
if (isEnvVarName(value))
|
|
228
|
+
continue; // 引用名不报告
|
|
229
|
+
hits.push({ path: linePath, field });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// 值形状(字段名无关)
|
|
233
|
+
if (valuePatterns) {
|
|
234
|
+
const trimmed = line.trim();
|
|
235
|
+
const name = matchSecretValuePattern(trimmed);
|
|
236
|
+
if (name !== null)
|
|
237
|
+
hits.push({ path: linePath, field: `(${name})` });
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return hits;
|
|
241
|
+
}
|
|
242
|
+
/** 强化版 SecretScanner(对齐 core SecretScanner 契约,可注入 Exporter.scanner) */
|
|
243
|
+
export function createSecretScanner(opts = {}) {
|
|
244
|
+
return {
|
|
245
|
+
scanAndRedact(data) {
|
|
246
|
+
return scanAndRedact(data, opts);
|
|
247
|
+
},
|
|
248
|
+
scanText(text) {
|
|
249
|
+
return scanText(text, opts);
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=secret-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secret-scanner.js","sourceRoot":"","sources":["../../src/security/secret-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGlD,wDAAwD;AACxD,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEvC;;;;GAIG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAsB;IAC3D,WAAW;IACX,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,aAAa,EAAE,cAAc;IAC5D,QAAQ,EAAE,QAAQ,EAAE,YAAY,EAAE,eAAe,EAAE,QAAQ;IAC3D,YAAY,EAAE,cAAc;IAC5B,2BAA2B;IAC3B,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW;IAC3D,YAAY,EAAE,QAAQ,EAAE,eAAe;CACxC,CAAC;AAEF,mCAAmC;AACnC,MAAM,CAAC,MAAM,wBAAwB,GAAsB;IACzD,WAAW,EAAE,YAAY,EAAE,WAAW;IACtC,UAAU,EAAE,gBAAgB,EAAE,iBAAiB;IAC/C,iBAAiB,EAAE,aAAa,EAAE,WAAW,EAAE,eAAe;CAC/D,CAAC;AAEF,2DAA2D;AAC3D,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,YAAY,CAAU,CAAC;AAE5F,gDAAgD;AAChD,MAAM,kBAAkB,GAAG;IACzB,QAAQ,EAAE,aAAa,EAAE,cAAc,EAAE,WAAW;IACpD,cAAc,EAAE,YAAY,EAAE,eAAe,EAAE,YAAY;IAC3D,eAAe,EAAE,WAAW;CACpB,CAAC;AAIX,MAAM,CAAC,MAAM,qBAAqB,GAA4B;IAC5D,EAAE,IAAI,EAAE,kBAAkB,EAAE,EAAE,EAAE,sBAAsB,EAAE;IACxD,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,+DAA+D,EAAE;IACpF,EAAE,IAAI,EAAE,gBAAgB,EAAE,EAAE,EAAE,kBAAkB,EAAE;IAClD,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,4BAA4B,EAAE;IAC1D,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,8BAA8B,EAAE;IAC1D,EAAE,IAAI,EAAE,iBAAiB,EAAE,EAAE,EAAE,mEAAmE,EAAE;IACpG,EAAE,IAAI,EAAE,cAAc,EAAE,EAAE,EAAE,+BAA+B,EAAE;CAC9D,CAAC;AAeF,uCAAuC;AACvC,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,sBAAsB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC5C,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,oBAAoB,CAClC,KAAa,EACb,QAA2B,EAAE;IAE7B,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACvC,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IAC9B,IAAI,CAAC,GAAG,0BAA0B,EAAE,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1E,IAAI,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC5F,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED,yBAAyB;AACzB,MAAM,UAAU,gBAAgB,CAC9B,KAAa,EACb,QAA2B,EAAE;IAE7B,MAAM,IAAI,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,wBAAwB,EAAE,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAChE,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,uBAAuB,CAAC,KAAa;IACnD,KAAK,MAAM,CAAC,IAAI,qBAAqB,EAAE,CAAC;QACtC,IAAI,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC,IAAI,CAAC;IACtC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,4CAA4C;AAC5C,SAAS,mBAAmB,CAAC,KAAa;IACxC,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACzD,IAAI,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,aAAa;IACvF,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,KAAK,MAAM,EAAE,IAAI,KAAK;QAAE,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAClE,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC;QAChC,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;QAC3B,OAAO,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,OAAO,GAAG,GAAG,CAAC;AACvB,CAAC;AAED;qEACqE;AACrE,SAAS,eAAe,CAAC,KAAa,EAAE,KAAa,EAAE,IAAwH;IAC7K,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IAC/B,IAAI,IAAI,CAAC,aAAa,IAAI,uBAAuB,CAAC,KAAK,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/E,IAAI,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,oBAAoB,CAAC;QAAE,OAAO,KAAK,CAAC;IACrE,IAAI,YAAY,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,CAAC,kBAAkB;IACzD,IAAI,oBAAoB,CAAC,KAAK,EAAE,IAAI,CAAC,eAAe,CAAC;QAAE,OAAO,IAAI,CAAC;IACnE,IAAI,IAAI,CAAC,WAAW,IAAI,mBAAmB,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAChE,OAAO,KAAK,CAAC;AACf,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,aAAa,CAAC,IAAa,EAAE,OAA6B,EAAE;IAC1E,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC;IACrC,MAAM,GAAG,GAAuH;QAC9H,eAAe,EAAE,IAAI,CAAC,eAAe,IAAI,EAAE;QAC3C,oBAAoB,EAAE,IAAI,CAAC,oBAAoB,IAAI,EAAE;QACrD,aAAa,EAAE,IAAI,CAAC,aAAa,IAAI,IAAI;QACzC,WAAW,EAAE,IAAI,CAAC,WAAW,IAAI,KAAK;KACvC,CAAC;IACF,MAAM,IAAI,GAAmB,EAAE,CAAC;IAEhC,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAChF,IAAI,IAAI,YAAY,UAAU;QAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IAEjE,MAAM,OAAO,GAAY,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAEvD,MAAM,KAAK,GAAY,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;IACzE,MAAM,OAAO,GAAG,IAAI,OAAO,EAAU,CAAC;IACtC,OAAO,CAAC,GAAG,CAAC,IAAc,CAAC,CAAC;IAE5B,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAG,CAAC;QAC3B,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC;QACxC,IAAI,KAAK,GAAG,QAAQ;YAAE,MAAM,IAAI,cAAc,CAAC,QAAQ,KAAK,SAAS,QAAQ,EAAE,CAAC,CAAC;QACjF,IAAI,GAAG,YAAY,UAAU;YAAE,SAAS;QAExC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACvB,MAAM,MAAM,GAAG,GAAgB,CAAC;YAChC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBACpC,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;gBACjB,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;gBAClC,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,YAAY,UAAU,CAAC,EAAE,CAAC;oBACtE,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;wBAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,SAAS,GAAG,CAAC,CAAC;oBACrE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;oBACf,MAAM,QAAQ,GAAY,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACrD,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;oBACtB,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC;gBAC3E,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBACjB,CAAC;YACH,CAAC;YACD,SAAS;QACX,CAAC;QAED,MAAM,MAAM,GAAG,GAA8B,CAAC;QAC9C,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAA8B,CAAC,EAAE,CAAC;YACpE,MAAM,SAAS,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,IAAI,CAAC,EAAE,CAAC;YACnD,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;gBAC1B,IAAI,eAAe,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;oBAC/B,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;oBACzC,MAAM,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAC;gBACnC,CAAC;qBAAM,CAAC;oBACN,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;gBAChB,CAAC;gBACD,SAAS;YACX,CAAC;YACD,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,YAAY,UAAU,CAAC,EAAE,CAAC;gBACtE,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;oBAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,SAAS,GAAG,CAAC,CAAC;gBACrE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBACf,MAAM,QAAQ,GAAY,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACrD,MAAM,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC;gBACrB,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,CAAC,EAAE,CAAC,CAAC;YAC3E,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAChB,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACtC,CAAC;AAED,gEAAgE;AAEhE,MAAM,cAAc,GAAG;IACrB,iCAAiC;IACjC,EAAE,EAAE,EAAE,uCAAuC,EAAE,IAAI,EAAE,IAAI,EAAE;IAC3D,cAAc;IACd,EAAE,EAAE,EAAE,sCAAsC,EAAE,IAAI,EAAE,KAAK,EAAE;IAC3D,eAAe;IACf,EAAE,EAAE,EAAE,uCAAuC,EAAE,IAAI,EAAE,KAAK,EAAE;CACpD,CAAC;AAEX,8CAA8C;AAC9C,MAAM,UAAU,QAAQ,CAAC,IAAY,EAAE,OAA6B,EAAE;IACpE,MAAM,IAAI,GAAmB,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,IAAI,CAAC,eAAe,IAAI,EAAE,CAAC;IACzC,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,IAAI,CAAC;IACjD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAElC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;QACvB,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACjC,QAAQ;QACR,KAAK,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,cAAc,EAAE,CAAC;YAC1C,EAAE,CAAC,SAAS,GAAG,CAAC,CAAC;YACjB,IAAI,CAAyB,CAAC;YAC9B,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;gBACpC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;gBACpB,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC;gBACpB,IAAI,CAAC,oBAAoB,CAAC,KAAK,EAAE,KAAK,CAAC;oBAAE,SAAS;gBAClD,IAAI,IAAI,EAAE,CAAC;oBACT,0CAA0C;oBAC1C,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;oBACrC,SAAS;gBACX,CAAC;gBACD,IAAI,YAAY,CAAC,KAAK,CAAC;oBAAE,SAAS,CAAC,SAAS;gBAC5C,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC;YACvC,CAAC;QACH,CAAC;QACD,aAAa;QACb,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,uBAAuB,CAAC,OAAO,CAAC,CAAC;YAC9C,IAAI,IAAI,KAAK,IAAI;gBAAE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,IAAI,GAAG,EAAE,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,mBAAmB,CAAC,OAA6B,EAAE;IACjE,OAAO;QACL,aAAa,CAAC,IAAa;YACzB,OAAO,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QACnC,CAAC;QACD,QAAQ,CAAC,IAAY;YACnB,OAAO,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC9B,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ZipArchive, type ZipSafetyLimits } from '../utils/zip.ts';
|
|
2
|
+
export { isPathSafe } from '../utils/paths.ts';
|
|
3
|
+
/** 可执行文件扩展名黑名单(§19.6:只警告,本插件不执行任何脚本) */
|
|
4
|
+
export declare const EXECUTABLE_EXTENSIONS: Set<string>;
|
|
5
|
+
/** 条目名是否疑似可执行文件(按扩展名) */
|
|
6
|
+
export declare function isExecutableName(name: string): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* 强化版 ZIP 解析:在 core 全部安全校验(条目名/条目数/压缩体积)之上,
|
|
9
|
+
* 增加 symlink 拒绝与重复条目名拒绝。返回 core `ZipArchive`(解压校验完全复用)。
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseZipHardened(buf: Uint8Array, limits?: ZipSafetyLimits): ZipArchive;
|
|
12
|
+
/** 工厂:带默认限额的强化解析器(对齐 ImporterOptions.parseZipOverride 签名) */
|
|
13
|
+
export declare function createHardenedZipParser(defaultLimits?: ZipSafetyLimits): (buf: Uint8Array, limits?: ZipSafetyLimits) => ZipArchive;
|
|
14
|
+
export interface SafeExtractResult {
|
|
15
|
+
/** 已解压条目的 ZIP 相对路径(正斜杠) */
|
|
16
|
+
files: string[];
|
|
17
|
+
/** 非阻塞告警(可执行文件条目等) */
|
|
18
|
+
warnings: string[];
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 安全解压到受控目录(对齐 core safeExtract 语义 + 强化):
|
|
22
|
+
* - 条目名/限额/symlink/重复名检查(parseZipHardened);
|
|
23
|
+
* - 逐条 CRC32/尺寸/预算校验(ZipArchive.readEntry);
|
|
24
|
+
* - 解压后 lstat 复查「全部产物必须是普通文件」(防符号链接/非常规写入);
|
|
25
|
+
* - 任何异常 → 中止并**完整清理** destDir 后抛出(不残留部分落盘)。
|
|
26
|
+
*/
|
|
27
|
+
export declare function safeExtractHardened(zipPath: string, destDir: string, limits?: ZipSafetyLimits): Promise<SafeExtractResult>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ZIP 解压安全(规范 §19 / 设计 §9 / core utils/zip.ts 的强化层)。
|
|
3
|
+
*
|
|
4
|
+
* 分工(避免与 core 重复):
|
|
5
|
+
* - core `utils/zip.ts` 已实现:条目名 isPathSafe、条目数/压缩体积/解压体积/单条/压缩比限额、
|
|
6
|
+
* CRC32 与尺寸校验、ZipArchive.readEntry 预算、safeExtract 基础解压。
|
|
7
|
+
* - 本模块在其上强化 core 未覆盖的攻击面:
|
|
8
|
+
* 1. **symlink 条目拒绝**:解析中央目录 external attrs 的 Unix mode(S_IFLNK 0xA000)→ 拒绝;
|
|
9
|
+
* 2. **重复条目名拒绝**:同名条目(含目录/文件互撞)→ 拒绝(防读取歧义攻击);
|
|
10
|
+
* 3. **安全解压强化**:解压后逐条 lstat 复查「必须是普通文件」,任何异常 → 中止并**完整清理**目标目录;
|
|
11
|
+
* 4. **可执行文件告警**:扩展名黑名单 → warnings(本插件从不执行 ZIP 内文件)。
|
|
12
|
+
*
|
|
13
|
+
* 与 core 注入点对齐:`parseZipHardened` / `createHardenedZipParser` 满足
|
|
14
|
+
* `ImporterOptions.parseZipOverride` 签名 `(buf, limits?) => ZipArchive`。
|
|
15
|
+
*/
|
|
16
|
+
import fs from 'node:fs/promises';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import { isPathSafe, isSameOrChild } from "../utils/paths.js";
|
|
19
|
+
import { DEFAULT_ZIP_SAFETY_LIMITS, ZipArchive, ZipSafetyError, } from "../utils/zip.js";
|
|
20
|
+
export { isPathSafe } from "../utils/paths.js";
|
|
21
|
+
/* ---------------- 常量 ---------------- */
|
|
22
|
+
const EOCD_SIG = Buffer.from([0x50, 0x4b, 0x05, 0x06]);
|
|
23
|
+
const CENTRAL_SIG = 0x02014b50;
|
|
24
|
+
/** Unix 文件类型掩码(external attrs 高 16 位) */
|
|
25
|
+
const S_IFMT = 0xf000;
|
|
26
|
+
/** 符号链接 */
|
|
27
|
+
const S_IFLNK = 0xa000;
|
|
28
|
+
/** 可执行文件扩展名黑名单(§19.6:只警告,本插件不执行任何脚本) */
|
|
29
|
+
export const EXECUTABLE_EXTENSIONS = new Set([
|
|
30
|
+
'.exe', '.bat', '.cmd', '.sh', '.ps1', '.dll', '.so', '.dylib', '.bin', '.jar',
|
|
31
|
+
]);
|
|
32
|
+
/** 条目名是否疑似可执行文件(按扩展名) */
|
|
33
|
+
export function isExecutableName(name) {
|
|
34
|
+
const ext = name.slice(name.lastIndexOf('.')).toLowerCase();
|
|
35
|
+
return EXECUTABLE_EXTENSIONS.has(ext);
|
|
36
|
+
}
|
|
37
|
+
/* ---------------- 强化解析(parseZipOverride 注入点) ---------------- */
|
|
38
|
+
/**
|
|
39
|
+
* 强化版 ZIP 解析:在 core 全部安全校验(条目名/条目数/压缩体积)之上,
|
|
40
|
+
* 增加 symlink 拒绝与重复条目名拒绝。返回 core `ZipArchive`(解压校验完全复用)。
|
|
41
|
+
*/
|
|
42
|
+
export function parseZipHardened(buf, limits = {}) {
|
|
43
|
+
const merged = { ...DEFAULT_ZIP_SAFETY_LIMITS, ...limits };
|
|
44
|
+
const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
|
|
45
|
+
if (b.length < 22)
|
|
46
|
+
throw new ZipSafetyError('不是合法的 ZIP 文件(体积过小)');
|
|
47
|
+
const eocdIdx = b.lastIndexOf(EOCD_SIG);
|
|
48
|
+
if (eocdIdx < 0 || eocdIdx + 22 > b.length) {
|
|
49
|
+
throw new ZipSafetyError('不是合法的 ZIP 文件(缺少中央目录结束记录)');
|
|
50
|
+
}
|
|
51
|
+
const totalEntries = b.readUInt16LE(eocdIdx + 10);
|
|
52
|
+
const cdSize = b.readUInt32LE(eocdIdx + 12);
|
|
53
|
+
const cdOffset = b.readUInt32LE(eocdIdx + 16);
|
|
54
|
+
if (cdOffset + cdSize > b.length)
|
|
55
|
+
throw new ZipSafetyError('中央目录越界(ZIP 损坏)');
|
|
56
|
+
if (totalEntries > merged.maxEntries) {
|
|
57
|
+
throw new ZipSafetyError(`ZIP 条目数 ${totalEntries} 超过上限 ${merged.maxEntries}`);
|
|
58
|
+
}
|
|
59
|
+
const metas = [];
|
|
60
|
+
const seenNames = new Set();
|
|
61
|
+
let pos = cdOffset;
|
|
62
|
+
let compressedTotal = 0;
|
|
63
|
+
for (let i = 0; i < totalEntries; i++) {
|
|
64
|
+
if (pos + 46 > cdOffset + cdSize)
|
|
65
|
+
throw new ZipSafetyError('中央目录条目越界(ZIP 损坏)');
|
|
66
|
+
if (b.readUInt32LE(pos) !== CENTRAL_SIG)
|
|
67
|
+
throw new ZipSafetyError('中央目录签名损坏');
|
|
68
|
+
const method = b.readUInt16LE(pos + 10);
|
|
69
|
+
const crc = b.readUInt32LE(pos + 16);
|
|
70
|
+
const compSize = b.readUInt32LE(pos + 20);
|
|
71
|
+
const uncompSize = b.readUInt32LE(pos + 24);
|
|
72
|
+
const nameLen = b.readUInt16LE(pos + 28);
|
|
73
|
+
const extraLen = b.readUInt16LE(pos + 30);
|
|
74
|
+
const commentLen = b.readUInt16LE(pos + 32);
|
|
75
|
+
const externalAttrs = b.readUInt32LE(pos + 38);
|
|
76
|
+
const localOffset = b.readUInt32LE(pos + 42);
|
|
77
|
+
const name = b.subarray(pos + 46, pos + 46 + nameLen).toString('utf8');
|
|
78
|
+
// —— 强化检查 ——
|
|
79
|
+
if (!isPathSafe(name))
|
|
80
|
+
throw new ZipSafetyError(`ZIP 条目名不安全: ${name}`);
|
|
81
|
+
if (seenNames.has(name))
|
|
82
|
+
throw new ZipSafetyError(`ZIP 条目名重复: ${name}`);
|
|
83
|
+
seenNames.add(name);
|
|
84
|
+
const mode = (externalAttrs >>> 16) & S_IFMT;
|
|
85
|
+
if (mode === S_IFLNK) {
|
|
86
|
+
throw new ZipSafetyError(`ZIP 含符号链接条目,已拒绝: ${name}`);
|
|
87
|
+
}
|
|
88
|
+
if (localOffset + 30 > b.length) {
|
|
89
|
+
throw new ZipSafetyError(`条目 "${name}" 本地文件头越界`);
|
|
90
|
+
}
|
|
91
|
+
compressedTotal += compSize;
|
|
92
|
+
if (compressedTotal > merged.maxCompressedBytes) {
|
|
93
|
+
throw new ZipSafetyError('ZIP 压缩数据总量超过上限');
|
|
94
|
+
}
|
|
95
|
+
metas.push({
|
|
96
|
+
name,
|
|
97
|
+
method,
|
|
98
|
+
compressedSize: compSize,
|
|
99
|
+
uncompressedSize: uncompSize,
|
|
100
|
+
crc32: crc,
|
|
101
|
+
isDirectory: name.endsWith('/'),
|
|
102
|
+
localOffset,
|
|
103
|
+
});
|
|
104
|
+
pos += 46 + nameLen + extraLen + commentLen;
|
|
105
|
+
}
|
|
106
|
+
return new ZipArchive(b, metas, merged);
|
|
107
|
+
}
|
|
108
|
+
/** 工厂:带默认限额的强化解析器(对齐 ImporterOptions.parseZipOverride 签名) */
|
|
109
|
+
export function createHardenedZipParser(defaultLimits) {
|
|
110
|
+
return (buf, limits) => parseZipHardened(buf, { ...defaultLimits, ...limits });
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 安全解压到受控目录(对齐 core safeExtract 语义 + 强化):
|
|
114
|
+
* - 条目名/限额/symlink/重复名检查(parseZipHardened);
|
|
115
|
+
* - 逐条 CRC32/尺寸/预算校验(ZipArchive.readEntry);
|
|
116
|
+
* - 解压后 lstat 复查「全部产物必须是普通文件」(防符号链接/非常规写入);
|
|
117
|
+
* - 任何异常 → 中止并**完整清理** destDir 后抛出(不残留部分落盘)。
|
|
118
|
+
*/
|
|
119
|
+
export async function safeExtractHardened(zipPath, destDir, limits = {}) {
|
|
120
|
+
const data = await fs.readFile(zipPath);
|
|
121
|
+
const archive = parseZipHardened(data, limits);
|
|
122
|
+
const extracted = [];
|
|
123
|
+
const warnings = [];
|
|
124
|
+
try {
|
|
125
|
+
for (const meta of archive.entries()) {
|
|
126
|
+
if (meta.isDirectory)
|
|
127
|
+
continue;
|
|
128
|
+
const target = path.join(destDir, ...meta.name.split('/'));
|
|
129
|
+
if (!isSameOrChild(target, destDir)) {
|
|
130
|
+
throw new ZipSafetyError(`解压目标越界: ${meta.name}`);
|
|
131
|
+
}
|
|
132
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
133
|
+
await fs.writeFile(target, archive.readEntry(meta.name));
|
|
134
|
+
extracted.push(meta.name);
|
|
135
|
+
if (isExecutableName(meta.name)) {
|
|
136
|
+
warnings.push(`条目 "${meta.name}" 是潜在可执行文件,本插件不会执行它`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// 解压后复查:全部产物必须是普通文件(防 symlink 逃逸 / 非常规写入)
|
|
140
|
+
for (const rel of extracted) {
|
|
141
|
+
const p = path.join(destDir, ...rel.split('/'));
|
|
142
|
+
const st = await fs.lstat(p);
|
|
143
|
+
if (!st.isFile()) {
|
|
144
|
+
throw new ZipSafetyError(`解压产物不是普通文件: ${rel}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { files: extracted, warnings };
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
await fs.rm(destDir, { recursive: true, force: true });
|
|
151
|
+
throw err;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=zip-security.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"zip-security.js","sourceRoot":"","sources":["../../src/security/zip-security.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 冲突决策收集(规范 §11,m6-ui)。
|
|
3
|
+
*
|
|
4
|
+
* 输入:ImportPlan.items 中的 Conflict 项(目标存在且不同)。
|
|
5
|
+
* 输出:逐项决策 Keep Current / Use Imported / Review,转为 core ImportDecisions.resolutions。
|
|
6
|
+
*
|
|
7
|
+
* 纯逻辑:只收集用户选择,不触碰 core;未决(review/未选)项在预览时如实展示。
|
|
8
|
+
*/
|
|
9
|
+
import type { ImportPlan, ItemResolution, PlanItem } from '../core/types.ts';
|
|
10
|
+
import type { ConflictViewItem } from './types.ts';
|
|
11
|
+
export declare class ConflictCollector {
|
|
12
|
+
private readonly plan;
|
|
13
|
+
private readonly decisions;
|
|
14
|
+
constructor(plan: ImportPlan);
|
|
15
|
+
/** 需要用户决策的冲突项(kind === 'Conflict') */
|
|
16
|
+
get conflicts(): PlanItem[];
|
|
17
|
+
/** 设置某项决策;未出现在冲突列表中的 id 忽略并返回 false */
|
|
18
|
+
resolve(itemId: string, resolution: ItemResolution): boolean;
|
|
19
|
+
/** 单项当前决策(未决策返回 null) */
|
|
20
|
+
decisionOf(itemId: string): ItemResolution | null;
|
|
21
|
+
/** 仍未解决(review 或未决策)的冲突项 */
|
|
22
|
+
unresolved(): PlanItem[];
|
|
23
|
+
get hasUnresolved(): boolean;
|
|
24
|
+
/** 转 core 决策表(供 createImportPlan / executeImportPlan 使用) */
|
|
25
|
+
toResolutions(): Record<string, ItemResolution>;
|
|
26
|
+
/** 构造视图项列表(React 绑定用):含当前/导入摘要占位 */
|
|
27
|
+
viewItems(): ConflictViewItem[];
|
|
28
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export class ConflictCollector {
|
|
2
|
+
plan;
|
|
3
|
+
decisions = new Map();
|
|
4
|
+
constructor(plan) {
|
|
5
|
+
this.plan = plan;
|
|
6
|
+
}
|
|
7
|
+
/** 需要用户决策的冲突项(kind === 'Conflict') */
|
|
8
|
+
get conflicts() {
|
|
9
|
+
return this.plan.items.filter((i) => i.kind === 'Conflict');
|
|
10
|
+
}
|
|
11
|
+
/** 设置某项决策;未出现在冲突列表中的 id 忽略并返回 false */
|
|
12
|
+
resolve(itemId, resolution) {
|
|
13
|
+
if (!this.conflicts.some((i) => i.id === itemId))
|
|
14
|
+
return false;
|
|
15
|
+
this.decisions.set(itemId, resolution);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
/** 单项当前决策(未决策返回 null) */
|
|
19
|
+
decisionOf(itemId) {
|
|
20
|
+
return this.decisions.get(itemId) ?? null;
|
|
21
|
+
}
|
|
22
|
+
/** 仍未解决(review 或未决策)的冲突项 */
|
|
23
|
+
unresolved() {
|
|
24
|
+
return this.conflicts.filter((i) => {
|
|
25
|
+
const d = this.decisions.get(i.id);
|
|
26
|
+
return d === undefined || d === 'review';
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
get hasUnresolved() {
|
|
30
|
+
return this.unresolved().length > 0;
|
|
31
|
+
}
|
|
32
|
+
/** 转 core 决策表(供 createImportPlan / executeImportPlan 使用) */
|
|
33
|
+
toResolutions() {
|
|
34
|
+
const out = {};
|
|
35
|
+
for (const [id, res] of this.decisions)
|
|
36
|
+
out[id] = res;
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
/** 构造视图项列表(React 绑定用):含当前/导入摘要占位 */
|
|
40
|
+
viewItems() {
|
|
41
|
+
return this.conflicts.map((item) => ({
|
|
42
|
+
item,
|
|
43
|
+
currentSummary: item.detail?.split('\n')[0],
|
|
44
|
+
importedSummary: undefined,
|
|
45
|
+
resolution: this.decisionOf(item.id),
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=conflict-view.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conflict-view.js","sourceRoot":"","sources":["../../src/ui/conflict-view.ts"],"names":[],"mappings":"AAWA,MAAM,OAAO,iBAAiB;IACX,IAAI,CAAa;IACjB,SAAS,GAAG,IAAI,GAAG,EAA0B,CAAC;IAE/D,YAAY,IAAgB;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,sCAAsC;IACtC,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;IAC9D,CAAC;IAED,uCAAuC;IACvC,OAAO,CAAC,MAAc,EAAE,UAA0B;QAChD,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,MAAM,CAAC;YAAE,OAAO,KAAK,CAAC;QAC/D,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QACvC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,yBAAyB;IACzB,UAAU,CAAC,MAAc;QACvB,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC;IAC5C,CAAC;IAED,4BAA4B;IAC5B,UAAU;QACR,OAAO,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;YACjC,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACnC,OAAO,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,QAAQ,CAAC;QAC3C,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,aAAa;QACf,OAAO,IAAI,CAAC,UAAU,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IACtC,CAAC;IAED,4DAA4D;IAC5D,aAAa;QACX,MAAM,GAAG,GAAmC,EAAE,CAAC;QAC/C,KAAK,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,IAAI,CAAC,SAAS;YAAE,GAAG,CAAC,EAAE,CAAC,GAAG,GAAG,CAAC;QACtD,OAAO,GAAG,CAAC;IACb,CAAC;IAED,oCAAoC;IACpC,SAAS;QACP,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YACnC,IAAI;YACJ,cAAc,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAC3C,eAAe,EAAE,SAAS;YAC1B,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;SACrC,CAAC,CAAC,CAAC;IACN,CAAC;CACF"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** 可操作错误(UI 渲染为 Reason / Suggested action 两段) */
|
|
2
|
+
export interface ActionableError {
|
|
3
|
+
title: string;
|
|
4
|
+
reason: string;
|
|
5
|
+
suggestedAction?: string;
|
|
6
|
+
/** 相关项(如 MCP 服务器名 / 插件包名);可空 */
|
|
7
|
+
item?: string;
|
|
8
|
+
/** 是否可重试(true 显示重试按钮) */
|
|
9
|
+
retryable: boolean;
|
|
10
|
+
}
|
|
11
|
+
/** 将任意错误转换为可操作错误(消息已脱敏,不泄漏 Secret) */
|
|
12
|
+
export declare function toActionableError(err: unknown, opts?: {
|
|
13
|
+
item?: string;
|
|
14
|
+
fallbackTitle?: string;
|
|
15
|
+
}): ActionableError;
|
|
16
|
+
/** 格式化为用户可读的多行文本(Reason / Suggested action) */
|
|
17
|
+
export declare function formatActionableError(e: ActionableError): string;
|
package/lib/ui/errors.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 可操作错误模型(规范 §23,m6-ui)。
|
|
3
|
+
*
|
|
4
|
+
* 绝不只有 "Something went wrong.":
|
|
5
|
+
* - 解析错误 → Reason + Suggested action(可操作);
|
|
6
|
+
* - 输出前强制脱敏(redact),Secret 永不进入 UI/日志。
|
|
7
|
+
*/
|
|
8
|
+
import { redact } from "../security/redaction.js";
|
|
9
|
+
const ERROR_RULES = [
|
|
10
|
+
{
|
|
11
|
+
match: (m) => /SETTINGS_CONFLICT|revision.*conflict/i.test(m),
|
|
12
|
+
title: '配置已被并发修改',
|
|
13
|
+
suggestedAction: '目标 DSH 的该配置在导入期间被其他进程修改,为避免覆盖请重新导出或手动核对后再试。',
|
|
14
|
+
retryable: true,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
match: (m) => /ImportNotConfirmed|未确认/i.test(m),
|
|
18
|
+
title: '导入未确认',
|
|
19
|
+
suggestedAction: '请在预览页确认导入内容后重试。',
|
|
20
|
+
retryable: true,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
match: (m) => /backup integrity|完整性校验失败|checksum/i.test(m),
|
|
24
|
+
title: '备份完整性校验失败',
|
|
25
|
+
suggestedAction: '备份文件可能已损坏或被篡改,请重新导出备份后再试。',
|
|
26
|
+
retryable: false,
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
match: (m) => /schema.*(不支持|超出)|无法导入|Unsupported/i.test(m),
|
|
30
|
+
title: '备份格式版本不受支持',
|
|
31
|
+
suggestedAction: '请升级 DSH Config Manager 或使用相同版本的备份文件。',
|
|
32
|
+
retryable: false,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
match: (m) => /ENOENT|not found|无法读取|不存在/i.test(m),
|
|
36
|
+
title: '文件或路径不存在',
|
|
37
|
+
suggestedAction: '请检查文件路径是否正确、文件是否已被移动或删除。',
|
|
38
|
+
retryable: true,
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
match: (m) => /EACCES|permission|权限/i.test(m),
|
|
42
|
+
title: '权限不足',
|
|
43
|
+
suggestedAction: '请检查目标目录的读写权限后重试。',
|
|
44
|
+
retryable: true,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
match: (m) => /install.*(plugin|插件)|needsRestart|重启/i.test(m),
|
|
48
|
+
title: '插件安装需要重启生效',
|
|
49
|
+
suggestedAction: '导入已完成,插件将在重启 DSH 后生效;请在 DSH Desktop 中重启服务。',
|
|
50
|
+
retryable: false,
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
/** 将任意错误转换为可操作错误(消息已脱敏,不泄漏 Secret) */
|
|
54
|
+
export function toActionableError(err, opts) {
|
|
55
|
+
const raw = err instanceof Error ? err.message : String(err ?? '未知错误');
|
|
56
|
+
const message = redact(raw);
|
|
57
|
+
const rule = ERROR_RULES.find((r) => r.match(message));
|
|
58
|
+
return {
|
|
59
|
+
title: rule?.title ?? opts?.fallbackTitle ?? '操作失败',
|
|
60
|
+
reason: message,
|
|
61
|
+
suggestedAction: rule?.suggestedAction,
|
|
62
|
+
item: opts?.item !== undefined ? redact(opts.item) : undefined,
|
|
63
|
+
retryable: rule?.retryable ?? true,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** 格式化为用户可读的多行文本(Reason / Suggested action) */
|
|
67
|
+
export function formatActionableError(e) {
|
|
68
|
+
const lines = [e.title];
|
|
69
|
+
lines.push(`Reason: ${e.reason}`);
|
|
70
|
+
if (e.suggestedAction)
|
|
71
|
+
lines.push(`Suggested action: ${e.suggestedAction}`);
|
|
72
|
+
if (e.item)
|
|
73
|
+
lines.push(`Item: ${e.item}`);
|
|
74
|
+
return lines.join('\n');
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/ui/errors.ts"],"names":[],"mappings":"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"}
|