@moonquake2004/dsh-security 0.1.6 → 0.2.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/package.json +8 -2
- package/src/checks/index.mjs +3 -0
- package/src/checks/sl1-supply-chain.mjs +11 -6
- package/src/checks/sl4-release-compat.mjs +11 -3
- package/src/checks/sp1-dependency-audit.mjs +155 -65
- package/src/checks/sp11-patch-security-override.mjs +132 -0
- package/src/checks/sp12-config-as-code-tag.mjs +125 -0
- package/src/checks/sp13-tools-mode-sandbox.mjs +631 -0
- package/src/checks/sp3-sandbox-consistency.mjs +293 -147
- package/src/checks/sp5-permission-model.mjs +267 -53
- package/src/checks/sp8-dist-tag-health.mjs +17 -1
- package/src/checks/sp9-dual-instance-guard.mjs +241 -57
- package/src/checks/sr1-sandbox-violation.mjs +22 -23
- package/src/checks/sr2-privilege-escalation.mjs +26 -11
- package/src/checks/sr3-data-exfiltration.mjs +6 -14
- package/src/checks/sr4-isolation-verify.mjs +4 -5
- package/src/checks/ss2-pii-exposure.mjs +8 -3
- package/src/checks/ss3-sensitive-output.mjs +9 -8
- package/src/checks/ss4-session-integrity.mjs +20 -18
- package/src/dsh-config.mjs +204 -0
- package/src/index.mjs +3 -0
- package/src/install-tree.mjs +293 -0
- package/src/integrations/ecosystem.mjs +232 -48
- package/src/integrations/index.mjs +72 -24
- package/src/integrations/plugin-reducer.mjs +190 -40
- package/src/integrations/poison-guard.mjs +162 -39
- package/src/integrations/sandbox-audit.mjs +51 -47
- package/src/integrations/tool-exec.mjs +130 -0
- package/src/registry.mjs +3 -0
- package/src/session-reader.mjs +134 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SP12: `!!js` 配置即代码标签 + `dsh.bundle.patch` 路径健全性
|
|
3
|
+
*
|
|
4
|
+
* 威胁(#454 / #587 / #3354):宿主用 js-yaml 解析 patch,并**支持 `!!js` 标签**——
|
|
5
|
+
* 该标签会在**加载期求值任意 JavaScript**。也就是说一个插件的 cordis.patch.yml 里写
|
|
6
|
+
* mode: !!js <任意表达式>
|
|
7
|
+
* 就能在 boot 时执行代码(已实测的宿主 RCE 面)。SP4 的正则只看投毒关键词,**不识别 `!!js` 标签本身**,
|
|
8
|
+
* 也不区分"第三方层"与"用户自己写的 patch"。
|
|
9
|
+
*
|
|
10
|
+
* 另查 `dsh.bundle.patch` 的路径健全性:声明的 patch 必须存在且不逃出包目录(`../` 逃逸 = 越权读宿主文件)。
|
|
11
|
+
*
|
|
12
|
+
* 分级:第三方 bundle → error(用户未同意);用户自有 patch → 提示(用户有权自己写 JS)。
|
|
13
|
+
* Severity: CRITICAL Phase: POST_INSTALL
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFileSync, existsSync, realpathSync, readdirSync } from 'node:fs';
|
|
17
|
+
import { join, resolve, relative } from 'node:path';
|
|
18
|
+
import { Severity } from '../protocol/severity.mjs';
|
|
19
|
+
import { CheckPhase } from '../protocol/phase.mjs';
|
|
20
|
+
import { pass, fail, skip } from '../protocol/check.mjs';
|
|
21
|
+
import { collectPatchLayers } from './sp11-patch-security-override.mjs';
|
|
22
|
+
|
|
23
|
+
/** 找出 `!!js` 标签出现处(行级,带上下文),排除注释行 */
|
|
24
|
+
export function jsTagHits(text) {
|
|
25
|
+
const hits = [];
|
|
26
|
+
const lines = text.split('\n');
|
|
27
|
+
for (let i = 0; i < lines.length; i++) {
|
|
28
|
+
const t = lines[i].trim();
|
|
29
|
+
if (!t || t.startsWith('#')) continue;
|
|
30
|
+
if (/!!js\b/.test(t)) hits.push({ line: i + 1, text: t.slice(0, 120) });
|
|
31
|
+
}
|
|
32
|
+
return hits;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 检查每个已装 bundle 的 dsh.bundle.patch 是否健在且未逃出包目录。 */
|
|
36
|
+
export function bundlePatchIssues(profileDir) {
|
|
37
|
+
const issues = [];
|
|
38
|
+
const nmDir = join(profileDir, 'node_modules');
|
|
39
|
+
if (!existsSync(nmDir)) return issues;
|
|
40
|
+
let entries = [];
|
|
41
|
+
try { entries = readdirSync(nmDir, { withFileTypes: true }); } catch { return issues; }
|
|
42
|
+
|
|
43
|
+
const checkPkg = (dir, name) => {
|
|
44
|
+
const manifest = join(dir, 'package.json');
|
|
45
|
+
if (!existsSync(manifest)) return;
|
|
46
|
+
let pkg;
|
|
47
|
+
try { pkg = JSON.parse(readFileSync(manifest, 'utf8')); } catch { return; }
|
|
48
|
+
const declared = pkg?.dsh?.bundle?.patch ?? pkg?.dsh?.bundle;
|
|
49
|
+
const rel = typeof declared === 'string' ? declared : (declared && typeof declared.patch === 'string' ? declared.patch : null);
|
|
50
|
+
if (!rel) return;
|
|
51
|
+
const target = resolve(dir, rel);
|
|
52
|
+
if (!existsSync(target)) { issues.push(`${name}: dsh.bundle.patch 指向的 ${rel} 不存在(安装不完整)`); return; }
|
|
53
|
+
// 两侧都必须做 realpath:macOS 的 /var → /private/var 等 symlink 会让单侧解析出假的 `../..`
|
|
54
|
+
let realDir = resolve(dir);
|
|
55
|
+
let realTarget = target;
|
|
56
|
+
try { realDir = realpathSync(dir); } catch { /* 用 resolve 结果 */ }
|
|
57
|
+
try { realTarget = realpathSync(target); } catch { /* 用 resolve 结果 */ }
|
|
58
|
+
if (relative(realDir, realTarget).startsWith('..')) {
|
|
59
|
+
issues.push(`${name}: dsh.bundle.patch 指向包目录之外(${rel})——可读宿主任意文件`);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
if (entry.name.startsWith('.')) continue;
|
|
65
|
+
if (entry.name.startsWith('@')) {
|
|
66
|
+
const scopeDir = join(nmDir, entry.name);
|
|
67
|
+
let pkgs = [];
|
|
68
|
+
try { pkgs = readdirSync(scopeDir, { withFileTypes: true }); } catch { continue; }
|
|
69
|
+
for (const p of pkgs) checkPkg(join(scopeDir, p.name), `${entry.name}/${p.name}`);
|
|
70
|
+
} else {
|
|
71
|
+
checkPkg(join(nmDir, entry.name), entry.name);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return issues;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function run(profileDir) {
|
|
78
|
+
const id = 'SP12';
|
|
79
|
+
if (!profileDir || !existsSync(profileDir)) {
|
|
80
|
+
return skip(id, Severity.CRITICAL, '无 profile 目录,跳过 !!js 配置即代码检测');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const layers = collectPatchLayers(profileDir);
|
|
84
|
+
const bundleHits = [];
|
|
85
|
+
const userHits = [];
|
|
86
|
+
for (const { file, layer, pkg } of layers) {
|
|
87
|
+
let text;
|
|
88
|
+
try { text = readFileSync(file, 'utf8'); } catch { continue; }
|
|
89
|
+
const hits = jsTagHits(text);
|
|
90
|
+
if (!hits.length) continue;
|
|
91
|
+
const sample = hits.slice(0, 3).map((h) => `行${h.line}: ${h.text}`).join('; ');
|
|
92
|
+
if (layer === 'bundle') bundleHits.push(`${pkg || file}(${sample})`);
|
|
93
|
+
else userHits.push(`${file}(${sample})`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const patchIssues = bundlePatchIssues(profileDir);
|
|
97
|
+
|
|
98
|
+
if (bundleHits.length > 0 || patchIssues.length > 0) {
|
|
99
|
+
const parts = [];
|
|
100
|
+
if (bundleHits.length) {
|
|
101
|
+
parts.push(`第三方 patch 使用 !!js 标签(**加载期执行任意 JavaScript**,#454/#587/#3354):\n ${bundleHits.join('\n ')}`);
|
|
102
|
+
}
|
|
103
|
+
if (patchIssues.length) parts.push(`dsh.bundle.patch 路径异常:\n ${patchIssues.join('\n ')}`);
|
|
104
|
+
return fail(id, Severity.CRITICAL,
|
|
105
|
+
parts.join('\n') + (userHits.length ? `\n(用户自有 patch 另有 !!js ${userHits.length} 处,属用户自主配置)` : ''),
|
|
106
|
+
'要求该插件移除 !!js 标签(改用静态配置值);若确需动态配置,应由用户在自己的 profile patch 里写。'
|
|
107
|
+
+ '同时修正 dsh.bundle.patch 指向,使其存在于包内',
|
|
108
|
+
['#454', '#587', '#3354']
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const note = userHits.length ? `;用户自有 patch 含 ${userHits.length} 处 !!js(用户自主配置,仅提示)` : '';
|
|
113
|
+
return pass(id, Severity.CRITICAL,
|
|
114
|
+
`扫描 ${layers.length} 个 patch 层:第三方层无 !!js 标签;${patchIssues.length === 0 ? 'dsh.bundle.patch 路径均健全' : ''}${note}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export const sp12Check = {
|
|
118
|
+
id: 'SP12',
|
|
119
|
+
name: 'config-as-code-tag',
|
|
120
|
+
severity: Severity.CRITICAL,
|
|
121
|
+
phase: CheckPhase.POST_INSTALL,
|
|
122
|
+
description: '!!js 配置即代码(加载期执行 JS,实测宿主 RCE 面)+ dsh.bundle.patch 路径健全性(#454/#587/#3354)',
|
|
123
|
+
src: 'builtin',
|
|
124
|
+
runner: (profileDir) => run(profileDir),
|
|
125
|
+
};
|