@moonquake2004/dsh-security 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 +102 -0
- package/package.json +25 -0
- package/src/checks/index.mjs +24 -0
- package/src/checks/sl1-supply-chain.mjs +157 -0
- package/src/checks/sl2-update-integrity.mjs +59 -0
- package/src/checks/sl3-reputation-score.mjs +73 -0
- package/src/checks/sl4-release-compat.mjs +159 -0
- package/src/checks/sp1-dependency-audit.mjs +157 -0
- package/src/checks/sp2-secret-scan.mjs +131 -0
- package/src/checks/sp3-sandbox-consistency.mjs +220 -0
- package/src/checks/sp4-entry-poison.mjs +74 -0
- package/src/checks/sp5-permission-model.mjs +58 -0
- package/src/checks/sp6-vuln-match.mjs +67 -0
- package/src/checks/sr1-sandbox-violation.mjs +177 -0
- package/src/checks/sr2-privilege-escalation.mjs +67 -0
- package/src/checks/sr3-data-exfiltration.mjs +163 -0
- package/src/checks/sr4-isolation-verify.mjs +64 -0
- package/src/checks/ss1-credential-leak.mjs +158 -0
- package/src/checks/ss2-pii-exposure.mjs +70 -0
- package/src/checks/ss3-sensitive-output.mjs +63 -0
- package/src/config.mjs +43 -0
- package/src/index.mjs +49 -0
- package/src/integrations/ecosystem.mjs +95 -0
- package/src/integrations/index.mjs +37 -0
- package/src/integrations/plugin-reducer.mjs +66 -0
- package/src/integrations/poison-guard.mjs +83 -0
- package/src/integrations/sandbox-audit.mjs +68 -0
- package/src/protocol/check.mjs +49 -0
- package/src/protocol/index.mjs +3 -0
- package/src/protocol/phase.mjs +16 -0
- package/src/protocol/severity.mjs +40 -0
- package/src/registry.mjs +122 -0
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# dsh-security
|
|
2
|
+
|
|
3
|
+
[](#)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
[](https://www.npmjs.com/package/@moonquake2004/dsh-security)
|
|
6
|
+
|
|
7
|
+
**DSH 生态统一安全检查框架** — 覆盖插件全生命周期(发现→安装→运行→更新→退役)。
|
|
8
|
+
|
|
9
|
+
> Community security tool. Not an official DeepSeek project.
|
|
10
|
+
|
|
11
|
+
## 定位
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
dsh-doctor ← 执行者(运行检查)
|
|
15
|
+
dsh-security ← 检查库(定义检查)
|
|
16
|
+
dsh-poison-guard ← 外部集成(投毒扫描)
|
|
17
|
+
dsh-sandbox-audit ← 外部集成(沙箱审计)
|
|
18
|
+
dsh-redact ← 外部集成(日志脱敏)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## 架构
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
┌──────────────────────────────────────────────────┐
|
|
25
|
+
│ Layer 0: Check Protocol(统一协议) │
|
|
26
|
+
│ • Severity: CRITICAL/HIGH/MEDIUM/LOW/INFO │
|
|
27
|
+
│ • Phase: PRE/POST/RUNTIME/LIFECYCLE │
|
|
28
|
+
│ • Plugin Interface(外部工具注册检查) │
|
|
29
|
+
├──────────────────────────────────────────────────┤
|
|
30
|
+
│ Layer 1: Static Checks(静态检查) │
|
|
31
|
+
│ • SP2: 密钥扫描(硬编码 API key/token/私钥) │
|
|
32
|
+
│ • SS1: 凭据泄露检测(会话日志扫描) │
|
|
33
|
+
├──────────────────────────────────────────────────┤
|
|
34
|
+
│ Layer 2: Runtime Checks(运行时检查) │
|
|
35
|
+
│ • SR1-SR4: 即将实现 │
|
|
36
|
+
├──────────────────────────────────────────────────┤
|
|
37
|
+
│ Layer 3: Lifecycle Checks(生命周期检查) │
|
|
38
|
+
│ • SL1-SL4: 即将实现 │
|
|
39
|
+
└──────────────────────────────────────────────────┘
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 快速开始
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
import { createDefaultRegistry } from '@moonquake2004/dsh-security';
|
|
46
|
+
|
|
47
|
+
const registry = await createDefaultRegistry();
|
|
48
|
+
const { results, exitCode, summary } = await registry.runAll(
|
|
49
|
+
(check) => profileDir // 为每个检查提供上下文
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
console.log({ exitCode, summary });
|
|
53
|
+
// { exitCode: 0, summary: { critical: 0, high: 0, medium: 0, low: 0, info: 0 } }
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 内置检查
|
|
57
|
+
|
|
58
|
+
| ID | 名称 | Severity | Phase | 描述 |
|
|
59
|
+
|---|---|---|---|---|
|
|
60
|
+
| SP2 | secret-scan | HIGH/CRITICAL | POST_INSTALL | 配置文件硬编码密钥检测 |
|
|
61
|
+
| SS1 | credential-leak | CRITICAL | POST_INSTALL | 会话日志凭据泄露检测 |
|
|
62
|
+
|
|
63
|
+
## 外部工具集成
|
|
64
|
+
|
|
65
|
+
通过 `~/.dsh/security/checks.d/*.json` 注册外部检查:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"source": "dsh-poison-guard",
|
|
70
|
+
"version": "0.2.0",
|
|
71
|
+
"checks": [
|
|
72
|
+
{
|
|
73
|
+
"id": "EXT-PG-1",
|
|
74
|
+
"name": "poison-scan",
|
|
75
|
+
"severity": "high",
|
|
76
|
+
"phase": "pre-install",
|
|
77
|
+
"command": "dsh-poison-guard scan --json"
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 开发
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# 运行测试
|
|
87
|
+
node --test test/*.mjs
|
|
88
|
+
|
|
89
|
+
# 添加新检查
|
|
90
|
+
# 1. 创建 src/checks/xx-check-name.mjs
|
|
91
|
+
# 2. 导出 runner 函数和 check 对象
|
|
92
|
+
# 3. 在 src/checks/index.mjs 中注册
|
|
93
|
+
# 4. 添加测试
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 设计文档
|
|
97
|
+
|
|
98
|
+
- [完整设计](docs/plans/2026-08-18-security-framework-design.md)
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@moonquake2004/dsh-security",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Unified security check framework for DeepSeek Harness ecosystem — covers the full plugin lifecycle (discovery → install → runtime → update → retirement)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.mjs",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.mjs",
|
|
9
|
+
"./protocol": "./src/protocol/index.mjs",
|
|
10
|
+
"./checks": "./src/checks/index.mjs"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"src",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"test": "node --test test/*.mjs"
|
|
18
|
+
},
|
|
19
|
+
"keywords": ["dsh", "deepseek-harness", "security", "audit", "plugin"],
|
|
20
|
+
"author": "moonquake2004",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"engines": {
|
|
23
|
+
"node": ">=22.19.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Layer 1: Static Checks
|
|
2
|
+
export { sp1Check } from './sp1-dependency-audit.mjs';
|
|
3
|
+
export { sp2Check } from './sp2-secret-scan.mjs';
|
|
4
|
+
export { sp3Check } from './sp3-sandbox-consistency.mjs';
|
|
5
|
+
export { sp4Check } from './sp4-entry-poison.mjs';
|
|
6
|
+
export { sp5Check } from './sp5-permission-model.mjs';
|
|
7
|
+
export { sp6Check } from './sp6-vuln-match.mjs';
|
|
8
|
+
|
|
9
|
+
// Layer 2: Runtime Checks
|
|
10
|
+
export { sr1Check } from './sr1-sandbox-violation.mjs';
|
|
11
|
+
export { sr2Check } from './sr2-privilege-escalation.mjs';
|
|
12
|
+
export { sr3Check } from './sr3-data-exfiltration.mjs';
|
|
13
|
+
export { sr4Check } from './sr4-isolation-verify.mjs';
|
|
14
|
+
|
|
15
|
+
// Layer 3: Lifecycle Checks
|
|
16
|
+
export { sl1Check } from './sl1-supply-chain.mjs';
|
|
17
|
+
export { sl2Check } from './sl2-update-integrity.mjs';
|
|
18
|
+
export { sl3Check } from './sl3-reputation-score.mjs';
|
|
19
|
+
export { sl4Check } from './sl4-release-compat.mjs';
|
|
20
|
+
|
|
21
|
+
// Session Checks
|
|
22
|
+
export { ss1Check } from './ss1-credential-leak.mjs';
|
|
23
|
+
export { ss2Check } from './ss2-pii-exposure.mjs';
|
|
24
|
+
export { ss3Check } from './ss3-sensitive-output.mjs';
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SL1: Supply Chain Integrity — 供应链完整性验证
|
|
3
|
+
*
|
|
4
|
+
* 验证 profile 中已安装的包与 npm registry 上的版本一致:
|
|
5
|
+
* - 版本号匹配
|
|
6
|
+
* - integrity hash 匹配(SHA-512)
|
|
7
|
+
* - 检测潜在的包劫持(版本存在但内容不匹配)
|
|
8
|
+
*
|
|
9
|
+
* Severity: HIGH
|
|
10
|
+
* Phase: LIFECYCLE
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { execSync } from 'node:child_process';
|
|
16
|
+
import { Severity, maxSeverity } from '../protocol/severity.mjs';
|
|
17
|
+
import { CheckPhase } from '../protocol/phase.mjs';
|
|
18
|
+
import { pass, fail } from '../protocol/check.mjs';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 获取 npm registry 上的包信息
|
|
22
|
+
*/
|
|
23
|
+
async function fetchRegistryInfo(pkgName) {
|
|
24
|
+
try {
|
|
25
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(pkgName).replace(/%2f/g, '/')}`;
|
|
26
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(10000) });
|
|
27
|
+
if (!response.ok) return null;
|
|
28
|
+
return await response.json();
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 比较本地安装版本与 registry 版本
|
|
36
|
+
*/
|
|
37
|
+
function compareVersions(localPkg, registryInfo) {
|
|
38
|
+
const issues = [];
|
|
39
|
+
const localVersion = localPkg.version;
|
|
40
|
+
const latestVersion = registryInfo['dist-tags']?.latest;
|
|
41
|
+
|
|
42
|
+
// 版本号匹配
|
|
43
|
+
if (latestVersion && localVersion !== latestVersion) {
|
|
44
|
+
issues.push({
|
|
45
|
+
type: 'version-mismatch',
|
|
46
|
+
severity: 'medium',
|
|
47
|
+
detail: `本地版本 ${localVersion} ≠ registry latest ${latestVersion}`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// integrity 比较
|
|
52
|
+
const localIntegrity = localPkg._integrity || localPkg.dist?.integrity;
|
|
53
|
+
const registryVersion = registryInfo.versions?.[localVersion];
|
|
54
|
+
const registryIntegrity = registryVersion?.dist?.integrity;
|
|
55
|
+
|
|
56
|
+
if (localIntegrity && registryIntegrity && localIntegrity !== registryIntegrity) {
|
|
57
|
+
issues.push({
|
|
58
|
+
type: 'integrity-mismatch',
|
|
59
|
+
severity: 'critical',
|
|
60
|
+
detail: `integrity hash 不匹配:本地 ${localIntegrity.slice(0, 30)}... ≠ registry ${registryIntegrity.slice(0, 30)}...`,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return issues;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* SL1 检查:验证 profile 中包的供应链完整性
|
|
69
|
+
* @param {string} profileDir - profile 目录路径
|
|
70
|
+
* @returns {Promise<import('../protocol/check.mjs').SecurityCheckResult>}
|
|
71
|
+
*/
|
|
72
|
+
export async function run(profileDir) {
|
|
73
|
+
const id = 'SL1';
|
|
74
|
+
|
|
75
|
+
const packageJsonPath = join(profileDir, 'package.json');
|
|
76
|
+
if (!existsSync(packageJsonPath)) {
|
|
77
|
+
return pass(id, Severity.HIGH, 'profile 无 package.json,跳过供应链验证');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let manifest;
|
|
81
|
+
try {
|
|
82
|
+
manifest = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
83
|
+
} catch {
|
|
84
|
+
return pass(id, Severity.HIGH, 'package.json 解析失败,跳过供应链验证');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const deps = manifest.dependencies || {};
|
|
88
|
+
const bundleNames = manifest.dsh?.profile?.bundles || [];
|
|
89
|
+
|
|
90
|
+
// 只检查 dsh 相关的包(减少 API 调用)
|
|
91
|
+
const dshPackages = Object.keys(deps).filter(name =>
|
|
92
|
+
name.includes('dsh') || name.includes('deepseek') || name.includes('cordis')
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
if (dshPackages.length === 0) {
|
|
96
|
+
return pass(id, Severity.HIGH, 'profile 无 dsh 相关依赖,跳过供应链验证');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const allIssues = [];
|
|
100
|
+
let checked = 0;
|
|
101
|
+
let errors = 0;
|
|
102
|
+
|
|
103
|
+
for (const pkgName of dshPackages.slice(0, 10)) { // 最多检查 10 个
|
|
104
|
+
const localPkgPath = join(profileDir, 'node_modules', pkgName, 'package.json');
|
|
105
|
+
if (!existsSync(localPkgPath)) continue;
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
const localPkg = JSON.parse(readFileSync(localPkgPath, 'utf8'));
|
|
109
|
+
const registryInfo = await fetchRegistryInfo(pkgName);
|
|
110
|
+
|
|
111
|
+
if (!registryInfo) {
|
|
112
|
+
errors++;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const issues = compareVersions(localPkg, registryInfo);
|
|
117
|
+
for (const issue of issues) {
|
|
118
|
+
allIssues.push({ package: pkgName, ...issue });
|
|
119
|
+
}
|
|
120
|
+
checked++;
|
|
121
|
+
} catch {
|
|
122
|
+
errors++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (allIssues.length === 0) {
|
|
127
|
+
return pass(id, Severity.HIGH,
|
|
128
|
+
`验证 ${checked} 个包的供应链完整性,未发现异常${errors > 0 ? `(${errors} 个包查询失败)` : ''}`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const criticalIssues = allIssues.filter(i => i.severity === 'critical');
|
|
133
|
+
const overallSeverity = criticalIssues.length > 0 ? Severity.CRITICAL : Severity.HIGH;
|
|
134
|
+
|
|
135
|
+
const details = allIssues
|
|
136
|
+
.map(i => `[${i.severity}] ${i.package} — ${i.type}: ${i.detail}`)
|
|
137
|
+
.join('\n');
|
|
138
|
+
|
|
139
|
+
const fix = criticalIssues.length > 0
|
|
140
|
+
? '检测到 integrity hash 不匹配,可能是包被篡改。立即重新安装受影响的包'
|
|
141
|
+
: '部分包版本落后于 registry latest,建议更新';
|
|
142
|
+
|
|
143
|
+
return fail(id, overallSeverity,
|
|
144
|
+
`检测到 ${allIssues.length} 个供应链问题(${checked} 个包已验证):\n${details}`,
|
|
145
|
+
fix
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const sl1Check = {
|
|
150
|
+
id: 'SL1',
|
|
151
|
+
name: 'supply-chain-integrity',
|
|
152
|
+
severity: Severity.HIGH,
|
|
153
|
+
phase: CheckPhase.LIFECYCLE,
|
|
154
|
+
description: '供应链完整性验证(npm registry 一致性)',
|
|
155
|
+
src: 'builtin',
|
|
156
|
+
runner: (profileDir) => run(profileDir),
|
|
157
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SL2: Update Integrity — 更新完整性检查
|
|
3
|
+
*
|
|
4
|
+
* 检查 profile 中包的更新完整性:
|
|
5
|
+
* - 检测版本回退
|
|
6
|
+
* - 检测异常的版本跳跃
|
|
7
|
+
*
|
|
8
|
+
* Severity: MEDIUM
|
|
9
|
+
* Phase: LIFECYCLE
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import { Severity } from '../protocol/severity.mjs';
|
|
15
|
+
import { CheckPhase } from '../protocol/phase.mjs';
|
|
16
|
+
import { pass, fail } from '../protocol/check.mjs';
|
|
17
|
+
|
|
18
|
+
function parseVersion(v) {
|
|
19
|
+
const parts = v.replace(/^[~^>=<]/, '').split('.').map(Number);
|
|
20
|
+
return { major: parts[0] || 0, minor: parts[1] || 0, patch: parts[2] || 0 };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function versionGt(a, b) {
|
|
24
|
+
if (a.major !== b.major) return a.major > b.major;
|
|
25
|
+
if (a.minor !== b.minor) return a.minor > b.minor;
|
|
26
|
+
return a.patch > b.patch;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function run(profileDir) {
|
|
30
|
+
const id = 'SL2';
|
|
31
|
+
const packageJsonPath = join(profileDir, 'package.json');
|
|
32
|
+
if (!existsSync(packageJsonPath)) return pass(id, Severity.MEDIUM, '无 package.json,跳过更新完整性检查');
|
|
33
|
+
|
|
34
|
+
let manifest;
|
|
35
|
+
try { manifest = JSON.parse(readFileSync(packageJsonPath, 'utf8')); } catch { return pass(id, Severity.MEDIUM, 'package.json 解析失败'); }
|
|
36
|
+
|
|
37
|
+
const deps = manifest.dependencies || {};
|
|
38
|
+
const issues = [];
|
|
39
|
+
|
|
40
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
41
|
+
if (!name.includes('dsh') && !name.includes('deepseek')) continue;
|
|
42
|
+
const pkgPath = join(profileDir, 'node_modules', name, 'package.json');
|
|
43
|
+
if (!existsSync(pkgPath)) continue;
|
|
44
|
+
try {
|
|
45
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
46
|
+
const localVer = parseVersion(pkg.version);
|
|
47
|
+
const specVer = parseVersion(version);
|
|
48
|
+
if (versionGt(specVer, localVer) && specVer.major > localVer.major) {
|
|
49
|
+
issues.push({ package: name, type: 'major-behind', detail: `本地 ${pkg.version} 落后于声明 ${version}(major 差异)` });
|
|
50
|
+
}
|
|
51
|
+
} catch { /* skip */ }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (issues.length === 0) return pass(id, Severity.MEDIUM, '包版本更新完整性正常');
|
|
55
|
+
const details = issues.map(i => `${i.package}: ${i.detail}`).join('\n');
|
|
56
|
+
return fail(id, Severity.MEDIUM, `检测到 ${issues.length} 个更新完整性问题:\n${details}`, '更新受影响的包到声明版本');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const sl2Check = { id: 'SL2', name: 'update-integrity', severity: Severity.MEDIUM, phase: CheckPhase.LIFECYCLE, description: '更新完整性检查', src: 'builtin', runner: (d) => run(d) };
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SL3: Reputation Score — 插件信誉评分
|
|
3
|
+
*
|
|
4
|
+
* 基于 npm registry 信号评估插件信誉:
|
|
5
|
+
* - 下载量
|
|
6
|
+
* - 维护者数量
|
|
7
|
+
* - 最近更新时间
|
|
8
|
+
* - 依赖数量
|
|
9
|
+
*
|
|
10
|
+
* Severity: LOW
|
|
11
|
+
* Phase: LIFECYCLE
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
15
|
+
import { join } from 'node:path';
|
|
16
|
+
import { Severity } from '../protocol/severity.mjs';
|
|
17
|
+
import { CheckPhase } from '../protocol/phase.mjs';
|
|
18
|
+
import { pass, fail } from '../protocol/check.mjs';
|
|
19
|
+
|
|
20
|
+
async function fetchNpmInfo(pkgName) {
|
|
21
|
+
try {
|
|
22
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(pkgName).replace(/%2f/g, '/')}`;
|
|
23
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(10000) });
|
|
24
|
+
if (!response.ok) return null;
|
|
25
|
+
return await response.json();
|
|
26
|
+
} catch { return null; }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function run(profileDir) {
|
|
30
|
+
const id = 'SL3';
|
|
31
|
+
const packageJsonPath = join(profileDir, 'package.json');
|
|
32
|
+
if (!existsSync(packageJsonPath)) return pass(id, Severity.LOW, '无 package.json,跳过信誉评分');
|
|
33
|
+
|
|
34
|
+
let manifest;
|
|
35
|
+
try { manifest = JSON.parse(readFileSync(packageJsonPath, 'utf8')); } catch { return pass(id, Severity.LOW, 'package.json 解析失败'); }
|
|
36
|
+
|
|
37
|
+
const deps = manifest.dependencies || {};
|
|
38
|
+
// 过滤:只检查 npm 上的包,跳过 @local/*、file: 依赖、github: 依赖
|
|
39
|
+
const dshPackages = Object.keys(deps).filter(n => {
|
|
40
|
+
if (!n.includes('dsh') && !n.includes('deepseek')) return false;
|
|
41
|
+
if (n.startsWith('@local/')) return false; // 本地包
|
|
42
|
+
if (deps[n].startsWith('file:')) return false; // 文件依赖
|
|
43
|
+
if (deps[n].startsWith('github:')) return false; // GitHub 依赖
|
|
44
|
+
return true;
|
|
45
|
+
});
|
|
46
|
+
if (dshPackages.length === 0) return pass(id, Severity.LOW, '无 npm 上的 dsh 相关依赖,跳过信誉评分');
|
|
47
|
+
|
|
48
|
+
const concerns = [];
|
|
49
|
+
for (const name of dshPackages.slice(0, 10)) {
|
|
50
|
+
const info = await fetchNpmInfo(name);
|
|
51
|
+
if (!info) { concerns.push({ package: name, concern: 'registry 查询失败' }); continue; }
|
|
52
|
+
const latest = info['dist-tags']?.latest;
|
|
53
|
+
const ver = info.versions?.[latest];
|
|
54
|
+
if (!ver) continue;
|
|
55
|
+
|
|
56
|
+
// 检查维护者数量
|
|
57
|
+
const maintainers = info.maintainers || [];
|
|
58
|
+
if (maintainers.length === 0) concerns.push({ package: name, concern: '无维护者' });
|
|
59
|
+
|
|
60
|
+
// 检查最近更新时间
|
|
61
|
+
const time = info.time?.[latest];
|
|
62
|
+
if (time) {
|
|
63
|
+
const daysSinceUpdate = (Date.now() - new Date(time).getTime()) / (1000 * 60 * 60 * 24);
|
|
64
|
+
if (daysSinceUpdate > 180) concerns.push({ package: name, concern: `最近更新 ${Math.floor(daysSinceUpdate)} 天前` });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (concerns.length === 0) return pass(id, Severity.LOW, `评估 ${dshPackages.length} 个包的信誉,无异常`);
|
|
69
|
+
const details = concerns.map(c => `${c.package}: ${c.concern}`).join('\n');
|
|
70
|
+
return fail(id, Severity.LOW, `检测到 ${concerns.length} 个信誉关注点:\n${details}`, '考虑替换长期未维护的包');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export const sl3Check = { id: 'SL3', name: 'reputation-score', severity: Severity.LOW, phase: CheckPhase.LIFECYCLE, description: '插件信誉评分', src: 'builtin', runner: (d) => run(d) };
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SL4: Release Compatibility — 发布兼容性验证
|
|
3
|
+
*
|
|
4
|
+
* 验证 profile 中包的 dist-tags 与 DSH 版本的兼容性:
|
|
5
|
+
* - 检测 latest 指向 broken 版本
|
|
6
|
+
* - 检测 next/rc 标签与本地版本的差异
|
|
7
|
+
* - 验证 peerDependencies 兼容性
|
|
8
|
+
*
|
|
9
|
+
* Severity: MEDIUM
|
|
10
|
+
* Phase: LIFECYCLE
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
14
|
+
import { join } from 'node:path';
|
|
15
|
+
import { Severity, maxSeverity } from '../protocol/severity.mjs';
|
|
16
|
+
import { CheckPhase } from '../protocol/phase.mjs';
|
|
17
|
+
import { pass, fail } from '../protocol/check.mjs';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 获取 npm registry dist-tags
|
|
21
|
+
*/
|
|
22
|
+
async function fetchDistTags(pkgName) {
|
|
23
|
+
try {
|
|
24
|
+
const url = `https://registry.npmjs.org/${encodeURIComponent(pkgName).replace(/%2f/g, '/')}`;
|
|
25
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(10000) });
|
|
26
|
+
if (!response.ok) return null;
|
|
27
|
+
const data = await response.json();
|
|
28
|
+
return data['dist-tags'] || null;
|
|
29
|
+
} catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 检查版本兼容性
|
|
36
|
+
*/
|
|
37
|
+
function checkCompatibility(localVersion, distTags) {
|
|
38
|
+
const issues = [];
|
|
39
|
+
|
|
40
|
+
// latest 标签检查
|
|
41
|
+
if (distTags.latest) {
|
|
42
|
+
const latestParts = distTags.latest.split('.').map(Number);
|
|
43
|
+
const localParts = localVersion.split('.').map(Number);
|
|
44
|
+
|
|
45
|
+
// 检测 major 版本差异
|
|
46
|
+
if (latestParts[0] > localParts[0]) {
|
|
47
|
+
issues.push({
|
|
48
|
+
type: 'major-behind',
|
|
49
|
+
severity: 'medium',
|
|
50
|
+
detail: `本地版本 ${localVersion} 落后于 latest ${distTags.latest}(major 版本差异)`,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 检测 latest 是否是 rc/beta
|
|
55
|
+
if (distTags.latest.includes('-rc') || distTags.latest.includes('-beta')) {
|
|
56
|
+
issues.push({
|
|
57
|
+
type: 'latest-is-prerelease',
|
|
58
|
+
severity: 'medium',
|
|
59
|
+
detail: `latest 标签指向预发布版本 ${distTags.latest},可能不稳定`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// next 标签检查
|
|
65
|
+
if (distTags.next && distTags.next !== distTags.latest) {
|
|
66
|
+
issues.push({
|
|
67
|
+
type: 'next-available',
|
|
68
|
+
severity: 'low',
|
|
69
|
+
detail: `有 next 标签 ${distTags.next} 可用(当前 latest: ${distTags.latest})`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return issues;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* SL4 检查:验证发布兼容性
|
|
78
|
+
* @param {string} profileDir - profile 目录路径
|
|
79
|
+
* @returns {Promise<import('../protocol/check.mjs').SecurityCheckResult>}
|
|
80
|
+
*/
|
|
81
|
+
export async function run(profileDir) {
|
|
82
|
+
const id = 'SL4';
|
|
83
|
+
|
|
84
|
+
const packageJsonPath = join(profileDir, 'package.json');
|
|
85
|
+
if (!existsSync(packageJsonPath)) {
|
|
86
|
+
return pass(id, Severity.MEDIUM, 'profile 无 package.json,跳过发布兼容性验证');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let manifest;
|
|
90
|
+
try {
|
|
91
|
+
manifest = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
92
|
+
} catch {
|
|
93
|
+
return pass(id, Severity.MEDIUM, 'package.json 解析失败,跳过发布兼容性验证');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const deps = manifest.dependencies || {};
|
|
97
|
+
|
|
98
|
+
// 只检查 dsh 相关的包
|
|
99
|
+
const dshPackages = Object.keys(deps).filter(name =>
|
|
100
|
+
name.includes('dsh') || name.includes('deepseek') || name.includes('cordis')
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (dshPackages.length === 0) {
|
|
104
|
+
return pass(id, Severity.MEDIUM, 'profile 无 dsh 相关依赖,跳过发布兼容性验证');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const allIssues = [];
|
|
108
|
+
let checked = 0;
|
|
109
|
+
|
|
110
|
+
for (const pkgName of dshPackages.slice(0, 10)) {
|
|
111
|
+
const localPkgPath = join(profileDir, 'node_modules', pkgName, 'package.json');
|
|
112
|
+
if (!existsSync(localPkgPath)) continue;
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
const localPkg = JSON.parse(readFileSync(localPkgPath, 'utf8'));
|
|
116
|
+
const distTags = await fetchDistTags(pkgName);
|
|
117
|
+
|
|
118
|
+
if (!distTags) continue;
|
|
119
|
+
|
|
120
|
+
const issues = checkCompatibility(localPkg.version, distTags);
|
|
121
|
+
for (const issue of issues) {
|
|
122
|
+
allIssues.push({ package: pkgName, ...issue });
|
|
123
|
+
}
|
|
124
|
+
checked++;
|
|
125
|
+
} catch {
|
|
126
|
+
// 跳过查询失败的包
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (allIssues.length === 0) {
|
|
131
|
+
return pass(id, Severity.MEDIUM,
|
|
132
|
+
`验证 ${checked} 个包的发布兼容性,无异常`
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const mediumIssues = allIssues.filter(i => i.severity === 'medium');
|
|
137
|
+
const overallSeverity = mediumIssues.length > 0 ? Severity.MEDIUM : Severity.LOW;
|
|
138
|
+
|
|
139
|
+
const details = allIssues
|
|
140
|
+
.map(i => `[${i.severity}] ${i.package} — ${i.type}: ${i.detail}`)
|
|
141
|
+
.join('\n');
|
|
142
|
+
|
|
143
|
+
const fix = '检查是否有重要更新需要应用,或确认当前版本满足需求';
|
|
144
|
+
|
|
145
|
+
return fail(id, overallSeverity,
|
|
146
|
+
`检测到 ${allIssues.length} 个发布兼容性问题(${checked} 个包已验证):\n${details}`,
|
|
147
|
+
fix
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export const sl4Check = {
|
|
152
|
+
id: 'SL4',
|
|
153
|
+
name: 'release-compat',
|
|
154
|
+
severity: Severity.MEDIUM,
|
|
155
|
+
phase: CheckPhase.LIFECYCLE,
|
|
156
|
+
description: '发布兼容性验证(dist-tags 一致性)',
|
|
157
|
+
src: 'builtin',
|
|
158
|
+
runner: (profileDir) => run(profileDir),
|
|
159
|
+
};
|