@dzhechkov/harness-core 0.3.23 → 0.3.24
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/dist/benchmark.d.ts +6 -0
- package/dist/benchmark.d.ts.map +1 -1
- package/dist/benchmark.js +50 -3
- package/dist/benchmark.js.map +1 -1
- package/dist/capability-vocab.d.ts +90 -0
- package/dist/capability-vocab.d.ts.map +1 -0
- package/dist/capability-vocab.js +286 -0
- package/dist/capability-vocab.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp-scan.d.ts +75 -0
- package/dist/mcp-scan.d.ts.map +1 -0
- package/dist/mcp-scan.js +383 -0
- package/dist/mcp-scan.js.map +1 -0
- package/dist/reconcile.d.ts +79 -0
- package/dist/reconcile.d.ts.map +1 -0
- package/dist/reconcile.js +134 -0
- package/dist/reconcile.js.map +1 -0
- package/package.json +1 -1
- package/src/benchmark.ts +55 -3
- package/src/capability-vocab.ts +307 -0
- package/src/index.ts +25 -0
- package/src/mcp-scan.ts +489 -0
- package/src/reconcile.ts +207 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dz mcp-scan` — "npm audit for agent tools".
|
|
3
|
+
*
|
|
4
|
+
* A deterministic, static (no-execution) security scan of a project's agent
|
|
5
|
+
* permission surface. It reads Claude-Code-style `.claude/settings*.json`
|
|
6
|
+
* permission grants and MCP server declarations (`.mcp.json`, `.vscode/mcp.json`)
|
|
7
|
+
* and emits a three-tier verdict (`clean` / `medium` / `high`) with findings.
|
|
8
|
+
*
|
|
9
|
+
* The rule set is adapted from the MetaHarness `threat-model` skill
|
|
10
|
+
* (ruvnet/agent-harness-generator) and mapped onto the Claude Code permission
|
|
11
|
+
* grammar. See `docs/research/metaharness-analysis.md` §1.
|
|
12
|
+
*
|
|
13
|
+
* Semantics (verified against the MetaHarness rules + Claude Code merge model):
|
|
14
|
+
* - settings.json and settings.local.json are evaluated as a MERGED surface
|
|
15
|
+
* (union of allow, union of deny); deny wins over allow.
|
|
16
|
+
* - findings are reported per CAPABILITY (one shell / network / write finding
|
|
17
|
+
* with a count + examples), not per individual grant.
|
|
18
|
+
* - secrets-reachability requires MCP to be active (per MetaHarness).
|
|
19
|
+
* - `low`-severity findings are informational and do NOT change the verdict.
|
|
20
|
+
*
|
|
21
|
+
* Verdict (highest non-low severity wins):
|
|
22
|
+
* - `high` (exit 2): shell granted · default-deny off · secrets reachable ·
|
|
23
|
+
* hardcoded secret in MCP env · all-MCP-servers enabled ·
|
|
24
|
+
* MCP server runs an interpreter / package-runner
|
|
25
|
+
* - `medium` (exit 1): network granted · file-write granted · remote MCP server
|
|
26
|
+
* - `clean` (exit 0): no high/medium findings (low/info may still be present)
|
|
27
|
+
*
|
|
28
|
+
* @packageDocumentation
|
|
29
|
+
*/
|
|
30
|
+
import { type CapabilityClass } from './capability-vocab.js';
|
|
31
|
+
/** Re-exported for back-compat (was historically exported from this module). */
|
|
32
|
+
export { parseGrant } from './capability-vocab.js';
|
|
33
|
+
/** Severity of a single finding. `low` is informational (verdict-neutral). */
|
|
34
|
+
export type McpSeverity = 'high' | 'medium' | 'low';
|
|
35
|
+
/** The capability class a finding concerns. */
|
|
36
|
+
export type McpCapability = CapabilityClass;
|
|
37
|
+
/** A single static-scan finding. */
|
|
38
|
+
export interface McpFinding {
|
|
39
|
+
/** Stable rule id, e.g. `MS-SHELL-GRANT`. */
|
|
40
|
+
readonly id: string;
|
|
41
|
+
readonly severity: McpSeverity;
|
|
42
|
+
readonly capability: McpCapability;
|
|
43
|
+
/** Relative path / source the finding came from. */
|
|
44
|
+
readonly source: string;
|
|
45
|
+
/** Human-readable explanation. */
|
|
46
|
+
readonly detail: string;
|
|
47
|
+
/** The grant / value (or aggregated count + examples) that triggered the rule. */
|
|
48
|
+
readonly evidence: string;
|
|
49
|
+
}
|
|
50
|
+
/** The overall verdict. `clean` when there are zero high/medium findings. */
|
|
51
|
+
export type McpVerdict = 'clean' | 'medium' | 'high';
|
|
52
|
+
/** Result of {@link scanMcp}. */
|
|
53
|
+
export interface McpScanReport {
|
|
54
|
+
readonly verdict: McpVerdict;
|
|
55
|
+
/** Process exit code: clean=0, medium=1, high=2. */
|
|
56
|
+
readonly exitCode: 0 | 1 | 2;
|
|
57
|
+
readonly findings: readonly McpFinding[];
|
|
58
|
+
/** Relative paths actually read during the scan. */
|
|
59
|
+
readonly scanned: readonly string[];
|
|
60
|
+
/** Derived capability flags (for badges / `--json`). */
|
|
61
|
+
readonly capabilities: {
|
|
62
|
+
readonly shell: boolean;
|
|
63
|
+
readonly network: boolean;
|
|
64
|
+
readonly fileWrite: boolean;
|
|
65
|
+
readonly secretsReachable: boolean;
|
|
66
|
+
/** Scoped to the `.claude/settings*.json` surface. */
|
|
67
|
+
readonly defaultDeny: boolean;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Statically scan a project/pack root for an unsafe agent permission surface.
|
|
72
|
+
* Deterministic and read-only — never executes anything it finds.
|
|
73
|
+
*/
|
|
74
|
+
export declare function scanMcp(rootDir: string): McpScanReport;
|
|
75
|
+
//# sourceMappingURL=mcp-scan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-scan.d.ts","sourceRoot":"","sources":["../src/mcp-scan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAKH,OAAO,EAKL,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AAE/B,gFAAgF;AAChF,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAEnD,8EAA8E;AAC9E,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEpD,+CAA+C;AAC/C,MAAM,MAAM,aAAa,GAAG,eAAe,CAAC;AAE5C,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC;IACnC,oDAAoD;IACpD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kCAAkC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,kFAAkF;IAClF,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B;AAED,6EAA6E;AAC7E,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;AAErD,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,oDAAoD;IACpD,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,QAAQ,CAAC,QAAQ,EAAE,SAAS,UAAU,EAAE,CAAC;IACzC,oDAAoD;IACpD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,wDAAwD;IACxD,QAAQ,CAAC,YAAY,EAAE;QACrB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;QACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;QAC1B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;QAC5B,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;QACnC,sDAAsD;QACtD,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;KAC/B,CAAC;CACH;AAqPD;;;GAGG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CA2JtD"}
|
package/dist/mcp-scan.js
ADDED
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dz mcp-scan` — "npm audit for agent tools".
|
|
3
|
+
*
|
|
4
|
+
* A deterministic, static (no-execution) security scan of a project's agent
|
|
5
|
+
* permission surface. It reads Claude-Code-style `.claude/settings*.json`
|
|
6
|
+
* permission grants and MCP server declarations (`.mcp.json`, `.vscode/mcp.json`)
|
|
7
|
+
* and emits a three-tier verdict (`clean` / `medium` / `high`) with findings.
|
|
8
|
+
*
|
|
9
|
+
* The rule set is adapted from the MetaHarness `threat-model` skill
|
|
10
|
+
* (ruvnet/agent-harness-generator) and mapped onto the Claude Code permission
|
|
11
|
+
* grammar. See `docs/research/metaharness-analysis.md` §1.
|
|
12
|
+
*
|
|
13
|
+
* Semantics (verified against the MetaHarness rules + Claude Code merge model):
|
|
14
|
+
* - settings.json and settings.local.json are evaluated as a MERGED surface
|
|
15
|
+
* (union of allow, union of deny); deny wins over allow.
|
|
16
|
+
* - findings are reported per CAPABILITY (one shell / network / write finding
|
|
17
|
+
* with a count + examples), not per individual grant.
|
|
18
|
+
* - secrets-reachability requires MCP to be active (per MetaHarness).
|
|
19
|
+
* - `low`-severity findings are informational and do NOT change the verdict.
|
|
20
|
+
*
|
|
21
|
+
* Verdict (highest non-low severity wins):
|
|
22
|
+
* - `high` (exit 2): shell granted · default-deny off · secrets reachable ·
|
|
23
|
+
* hardcoded secret in MCP env · all-MCP-servers enabled ·
|
|
24
|
+
* MCP server runs an interpreter / package-runner
|
|
25
|
+
* - `medium` (exit 1): network granted · file-write granted · remote MCP server
|
|
26
|
+
* - `clean` (exit 0): no high/medium findings (low/info may still be present)
|
|
27
|
+
*
|
|
28
|
+
* @packageDocumentation
|
|
29
|
+
*/
|
|
30
|
+
import { existsSync, lstatSync, readFileSync } from 'node:fs';
|
|
31
|
+
import { basename, join } from 'node:path';
|
|
32
|
+
import { SHELL_TOOLS, INTERPRETER_RE, PACKAGE_RUNNERS, INLINE_CODE_ARGS, SHELL_NET_RE, SHELL_WRITE_RE, SECRET_FILE_RE, MAX_FILE_BYTES, parseGrant, isWildcard, toolKind, } from './capability-vocab.js';
|
|
33
|
+
/** Re-exported for back-compat (was historically exported from this module). */
|
|
34
|
+
export { parseGrant } from './capability-vocab.js';
|
|
35
|
+
/** Compile a Claude arg glob (`*` wildcard) to a RegExp. */
|
|
36
|
+
function globToRe(arg) {
|
|
37
|
+
const esc = arg.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*');
|
|
38
|
+
return new RegExp(`^${esc}$`);
|
|
39
|
+
}
|
|
40
|
+
/** True when a deny rule covers a grant (deny wins). Case-insensitive on tool. */
|
|
41
|
+
function denyCovers(grant, denyList) {
|
|
42
|
+
const g = parseGrant(grant);
|
|
43
|
+
return denyList.some((d) => {
|
|
44
|
+
const dd = parseGrant(d);
|
|
45
|
+
if (dd.tool === '*')
|
|
46
|
+
return true;
|
|
47
|
+
if (dd.tool.toLowerCase() !== g.tool.toLowerCase())
|
|
48
|
+
return false;
|
|
49
|
+
if (dd.arg === null)
|
|
50
|
+
return true; // bare-tool deny covers every arg
|
|
51
|
+
if (g.arg === null)
|
|
52
|
+
return false;
|
|
53
|
+
try {
|
|
54
|
+
return globToRe(dd.arg).test(g.arg);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return dd.arg === g.arg;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/** True when the deny list actually protects secret FILE locations. */
|
|
62
|
+
function denyProtectsSecrets(denyList) {
|
|
63
|
+
return denyList.some((d) => {
|
|
64
|
+
const { tool, arg } = parseGrant(d);
|
|
65
|
+
const t = tool.toLowerCase();
|
|
66
|
+
if (t !== 'read' && t !== 'glob' && t !== '*' && t !== 'bash')
|
|
67
|
+
return false;
|
|
68
|
+
return arg !== null && SECRET_FILE_RE.test(arg);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/** Heuristic: does an MCP env value look like a hardcoded secret (not a `${VAR}` ref)? */
|
|
72
|
+
function looksLikeHardcodedSecret(key, value) {
|
|
73
|
+
if (typeof value !== 'string')
|
|
74
|
+
return false;
|
|
75
|
+
if (!/key|token|secret|password|api|credential|auth/i.test(key))
|
|
76
|
+
return false;
|
|
77
|
+
const v = value.trim();
|
|
78
|
+
if (v === '')
|
|
79
|
+
return false;
|
|
80
|
+
if (/^\$\{?\w+\}?$/.test(v))
|
|
81
|
+
return false; // pure ${VAR} / $VAR reference
|
|
82
|
+
if (/^(true|false|production|development|test|none|null|\d+)$/i.test(v))
|
|
83
|
+
return false;
|
|
84
|
+
return v.length >= 8;
|
|
85
|
+
}
|
|
86
|
+
function readJson(path) {
|
|
87
|
+
try {
|
|
88
|
+
const st = lstatSync(path);
|
|
89
|
+
if (st.isSymbolicLink() || !st.isFile() || st.size > MAX_FILE_BYTES)
|
|
90
|
+
return null;
|
|
91
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function asStringArray(value) {
|
|
98
|
+
return Array.isArray(value) ? value.filter((x) => typeof x === 'string') : [];
|
|
99
|
+
}
|
|
100
|
+
function isPlainObject(value) {
|
|
101
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
102
|
+
}
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// scanners
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
function scanSettings(root, rel, acc) {
|
|
107
|
+
const abs = join(root, rel);
|
|
108
|
+
if (!existsSync(abs))
|
|
109
|
+
return;
|
|
110
|
+
const data = readJson(abs);
|
|
111
|
+
if (!isPlainObject(data))
|
|
112
|
+
return;
|
|
113
|
+
acc.scanned.push(rel);
|
|
114
|
+
acc.hasSettings = true;
|
|
115
|
+
const perms = isPlainObject(data['permissions']) ? data['permissions'] : {};
|
|
116
|
+
const allow = asStringArray(perms['allow']);
|
|
117
|
+
const deny = asStringArray(perms['deny']);
|
|
118
|
+
acc.denyRules.push(...deny);
|
|
119
|
+
if (deny.length > 0)
|
|
120
|
+
acc.hasDeny = true;
|
|
121
|
+
for (const grant of allow) {
|
|
122
|
+
const { tool, arg } = parseGrant(grant);
|
|
123
|
+
if (grant.trim() === '*' || (SHELL_TOOLS.has(tool.toLowerCase()) && arg === null)) {
|
|
124
|
+
acc.wildcardAll = true;
|
|
125
|
+
}
|
|
126
|
+
const kind = toolKind(tool);
|
|
127
|
+
switch (kind) {
|
|
128
|
+
case 'shell':
|
|
129
|
+
acc.shellGrants.push(grant);
|
|
130
|
+
if (arg !== null && SHELL_NET_RE.test(arg))
|
|
131
|
+
acc.shellArgNetwork.push(grant);
|
|
132
|
+
if (arg !== null && SHELL_WRITE_RE.test(arg))
|
|
133
|
+
acc.shellArgWrite.push(grant);
|
|
134
|
+
break;
|
|
135
|
+
case 'network':
|
|
136
|
+
acc.networkGrants.push(grant);
|
|
137
|
+
break;
|
|
138
|
+
case 'file-write':
|
|
139
|
+
acc.writeGrants.push(grant);
|
|
140
|
+
break;
|
|
141
|
+
case 'read':
|
|
142
|
+
acc.hasReadGrant = true;
|
|
143
|
+
break;
|
|
144
|
+
case 'mcp':
|
|
145
|
+
acc.mcpToolGrants.push(grant);
|
|
146
|
+
acc.mcpActive = true;
|
|
147
|
+
break;
|
|
148
|
+
case 'safe':
|
|
149
|
+
break;
|
|
150
|
+
default:
|
|
151
|
+
acc.unknownTools.push(grant);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (data['enableAllProjectMcpServers'] === true) {
|
|
155
|
+
acc.enableAllMcp = true;
|
|
156
|
+
acc.enableAllMcpSource = rel;
|
|
157
|
+
acc.mcpActive = true;
|
|
158
|
+
}
|
|
159
|
+
if (Array.isArray(data['enabledMcpjsonServers']) && data['enabledMcpjsonServers'].length > 0) {
|
|
160
|
+
acc.mcpActive = true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function scanMcpServers(root, rel, acc) {
|
|
164
|
+
const abs = join(root, rel);
|
|
165
|
+
if (!existsSync(abs))
|
|
166
|
+
return;
|
|
167
|
+
const data = readJson(abs);
|
|
168
|
+
if (!isPlainObject(data))
|
|
169
|
+
return;
|
|
170
|
+
acc.scanned.push(rel);
|
|
171
|
+
const merged = {};
|
|
172
|
+
if (isPlainObject(data['mcpServers']))
|
|
173
|
+
Object.assign(merged, data['mcpServers']);
|
|
174
|
+
if (isPlainObject(data['servers']))
|
|
175
|
+
Object.assign(merged, data['servers']);
|
|
176
|
+
if (Object.keys(merged).length > 0)
|
|
177
|
+
acc.mcpActive = true;
|
|
178
|
+
for (const [name, raw] of Object.entries(merged)) {
|
|
179
|
+
if (!isPlainObject(raw))
|
|
180
|
+
continue;
|
|
181
|
+
const type = typeof raw['type'] === 'string' ? raw['type'] : '';
|
|
182
|
+
const url = typeof raw['url'] === 'string' ? raw['url'] : '';
|
|
183
|
+
const command = typeof raw['command'] === 'string' ? raw['command'] : '';
|
|
184
|
+
const args = asStringArray(raw['args']);
|
|
185
|
+
const env = isPlainObject(raw['env']) ? raw['env'] : {};
|
|
186
|
+
if (type === 'sse' || type === 'http' || url !== '') {
|
|
187
|
+
acc.serverNetwork = true;
|
|
188
|
+
acc.findings.push({
|
|
189
|
+
id: 'MS-MCP-REMOTE',
|
|
190
|
+
severity: 'medium',
|
|
191
|
+
capability: 'network',
|
|
192
|
+
source: rel,
|
|
193
|
+
detail: `MCP server "${name}" is remote (${type || 'url'}) — sends data to an external endpoint.`,
|
|
194
|
+
evidence: url || type,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
const cmdBase = basename(command).toLowerCase();
|
|
198
|
+
const isInterpreter = INTERPRETER_RE.test(cmdBase);
|
|
199
|
+
const isRunner = PACKAGE_RUNNERS.has(cmdBase);
|
|
200
|
+
const hasInlineCode = args.some((a) => INLINE_CODE_ARGS.has(a));
|
|
201
|
+
if (command !== '' && (isInterpreter || isRunner || hasInlineCode)) {
|
|
202
|
+
acc.serverShell = true;
|
|
203
|
+
const why = isRunner
|
|
204
|
+
? `package runner "${cmdBase}" fetches and executes remote code`
|
|
205
|
+
: hasInlineCode
|
|
206
|
+
? `inline-code argument`
|
|
207
|
+
: `interpreter "${cmdBase}" can run arbitrary code`;
|
|
208
|
+
acc.findings.push({
|
|
209
|
+
id: 'MS-MCP-SHELL-CMD',
|
|
210
|
+
severity: 'high',
|
|
211
|
+
capability: 'shell',
|
|
212
|
+
source: rel,
|
|
213
|
+
detail: `MCP server "${name}" launches via ${why}.`,
|
|
214
|
+
evidence: [command, ...args].join(' ').slice(0, 120),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
for (const [k, v] of Object.entries(env)) {
|
|
218
|
+
if (looksLikeHardcodedSecret(k, v)) {
|
|
219
|
+
acc.serverSecret = true;
|
|
220
|
+
acc.findings.push({
|
|
221
|
+
id: 'MS-SECRET-HARDCODED',
|
|
222
|
+
severity: 'high',
|
|
223
|
+
capability: 'secrets',
|
|
224
|
+
source: rel,
|
|
225
|
+
detail: `MCP server "${name}" env "${k}" appears to contain a hardcoded secret — use \${ENV_VAR} indirection instead.`,
|
|
226
|
+
evidence: `${k}=${String(v).slice(0, 4)}…`,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
// entry point
|
|
234
|
+
// ---------------------------------------------------------------------------
|
|
235
|
+
function aggregate(grants, deny, id, severity, capability, label) {
|
|
236
|
+
const survivors = grants.filter((g) => !denyCovers(g, deny));
|
|
237
|
+
if (survivors.length === 0)
|
|
238
|
+
return null;
|
|
239
|
+
const examples = survivors.slice(0, 3).join(', ');
|
|
240
|
+
return {
|
|
241
|
+
id,
|
|
242
|
+
severity,
|
|
243
|
+
capability,
|
|
244
|
+
source: '.claude/settings*.json',
|
|
245
|
+
detail: `${label} via ${survivors.length} allow rule(s) not covered by a deny rule.`,
|
|
246
|
+
evidence: survivors.length > 3 ? `e.g. ${examples}, … (+${survivors.length - 3} more)` : examples,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Statically scan a project/pack root for an unsafe agent permission surface.
|
|
251
|
+
* Deterministic and read-only — never executes anything it finds.
|
|
252
|
+
*/
|
|
253
|
+
export function scanMcp(rootDir) {
|
|
254
|
+
const acc = {
|
|
255
|
+
findings: [],
|
|
256
|
+
scanned: [],
|
|
257
|
+
shellGrants: [],
|
|
258
|
+
networkGrants: [],
|
|
259
|
+
writeGrants: [],
|
|
260
|
+
shellArgNetwork: [],
|
|
261
|
+
shellArgWrite: [],
|
|
262
|
+
mcpToolGrants: [],
|
|
263
|
+
unknownTools: [],
|
|
264
|
+
denyRules: [],
|
|
265
|
+
hasReadGrant: false,
|
|
266
|
+
hasSettings: false,
|
|
267
|
+
hasDeny: false,
|
|
268
|
+
wildcardAll: false,
|
|
269
|
+
mcpActive: false,
|
|
270
|
+
enableAllMcp: false,
|
|
271
|
+
enableAllMcpSource: '.claude/settings*.json',
|
|
272
|
+
serverShell: false,
|
|
273
|
+
serverNetwork: false,
|
|
274
|
+
serverSecret: false,
|
|
275
|
+
};
|
|
276
|
+
// order is irrelevant to the result: all signals are merged before evaluation.
|
|
277
|
+
scanSettings(rootDir, join('.claude', 'settings.json'), acc);
|
|
278
|
+
scanSettings(rootDir, join('.claude', 'settings.local.json'), acc);
|
|
279
|
+
scanMcpServers(rootDir, '.mcp.json', acc);
|
|
280
|
+
scanMcpServers(rootDir, join('.vscode', 'mcp.json'), acc);
|
|
281
|
+
const deny = acc.denyRules;
|
|
282
|
+
// --- aggregated capability findings (merged surface, deny-suppressed) ---
|
|
283
|
+
const shellSurvivors = acc.shellGrants.filter((g) => !denyCovers(g, deny));
|
|
284
|
+
if (shellSurvivors.length > 0) {
|
|
285
|
+
const anyWildcard = shellSurvivors.some((g) => isWildcard(parseGrant(g).arg));
|
|
286
|
+
const examples = shellSurvivors.slice(0, 3).join(', ');
|
|
287
|
+
acc.findings.push({
|
|
288
|
+
id: anyWildcard ? 'MS-SHELL-WILDCARD' : 'MS-SHELL-GRANT',
|
|
289
|
+
severity: 'high',
|
|
290
|
+
capability: 'shell',
|
|
291
|
+
source: '.claude/settings*.json',
|
|
292
|
+
detail: anyWildcard
|
|
293
|
+
? `Wildcard shell grant — arbitrary command execution. ${shellSurvivors.length} shell allow rule(s).`
|
|
294
|
+
: `Shell execution granted via ${shellSurvivors.length} allow rule(s).`,
|
|
295
|
+
evidence: shellSurvivors.length > 3 ? `e.g. ${examples}, … (+${shellSurvivors.length - 3} more)` : examples,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
const netFinding = aggregate([...acc.networkGrants, ...acc.shellArgNetwork], deny, 'MS-NETWORK-GRANT', 'medium', 'network', 'Outbound network access granted');
|
|
299
|
+
if (netFinding)
|
|
300
|
+
acc.findings.push(netFinding);
|
|
301
|
+
const writeFinding = aggregate([...acc.writeGrants, ...acc.shellArgWrite], deny, 'MS-FILEWRITE-GRANT', 'medium', 'file-write', 'Filesystem write access granted');
|
|
302
|
+
if (writeFinding)
|
|
303
|
+
acc.findings.push(writeFinding);
|
|
304
|
+
// --- enable-all-mcp ---
|
|
305
|
+
if (acc.enableAllMcp) {
|
|
306
|
+
acc.findings.push({
|
|
307
|
+
id: 'MS-MCP-ALL-ENABLED',
|
|
308
|
+
severity: 'high',
|
|
309
|
+
capability: 'mcp',
|
|
310
|
+
source: acc.enableAllMcpSource,
|
|
311
|
+
detail: `enableAllProjectMcpServers: true — every project MCP server is trusted unconditionally (no per-server gate).`,
|
|
312
|
+
evidence: 'enableAllProjectMcpServers: true',
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
// --- secrets reachability (MetaHarness: requires MCP active) ---
|
|
316
|
+
const secretsReachable = acc.mcpActive && acc.hasReadGrant && !denyProtectsSecrets(deny);
|
|
317
|
+
if (secretsReachable) {
|
|
318
|
+
acc.findings.push({
|
|
319
|
+
id: 'MS-SECRETS-REACHABLE',
|
|
320
|
+
severity: 'high',
|
|
321
|
+
capability: 'secrets',
|
|
322
|
+
source: '.claude/settings*.json',
|
|
323
|
+
detail: `MCP is active and a Read grant exists without a deny rule protecting secret files (.env, SSH keys, cloud creds) — secrets are reachable by tools.`,
|
|
324
|
+
evidence: 'allow contains Read(...) ∧ MCP active ∧ deny lacks a secret-file guard',
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
// --- default-deny posture (scoped to the settings surface) ---
|
|
328
|
+
const defaultDeny = acc.hasSettings ? acc.hasDeny && !acc.wildcardAll : true;
|
|
329
|
+
if (acc.hasSettings && !defaultDeny) {
|
|
330
|
+
acc.findings.push({
|
|
331
|
+
id: 'MS-DEFAULT-DENY-OFF',
|
|
332
|
+
severity: 'high',
|
|
333
|
+
capability: 'policy',
|
|
334
|
+
source: '.claude/settings*.json',
|
|
335
|
+
detail: acc.wildcardAll
|
|
336
|
+
? `An allow rule grants a bare wildcard — everything is permitted (no default-deny).`
|
|
337
|
+
: `No deny rules declared — the permission surface has no guardrail (no default-deny baseline).`,
|
|
338
|
+
evidence: acc.wildcardAll ? 'allow contains "*" / "Bash(*)"' : 'permissions.deny is empty',
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
// --- informational (low) findings: verdict-neutral coverage signal ---
|
|
342
|
+
if (acc.mcpToolGrants.length > 0) {
|
|
343
|
+
acc.findings.push({
|
|
344
|
+
id: 'MS-MCP-TOOLS-ALLOWED',
|
|
345
|
+
severity: 'low',
|
|
346
|
+
capability: 'mcp',
|
|
347
|
+
source: '.claude/settings*.json',
|
|
348
|
+
detail: `${acc.mcpToolGrants.length} MCP tool(s) explicitly allowed (per-tool gate — the safe pattern).`,
|
|
349
|
+
evidence: acc.mcpToolGrants.slice(0, 3).join(', '),
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
if (acc.unknownTools.length > 0) {
|
|
353
|
+
acc.findings.push({
|
|
354
|
+
id: 'MS-UNKNOWN-TOOL',
|
|
355
|
+
severity: 'low',
|
|
356
|
+
capability: 'policy',
|
|
357
|
+
source: '.claude/settings*.json',
|
|
358
|
+
detail: `${acc.unknownTools.length} grant(s) for tools not in the recognised capability sets — coverage gap, review manually.`,
|
|
359
|
+
evidence: acc.unknownTools.slice(0, 3).join(', '),
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
// --- verdict (low is verdict-neutral) ---
|
|
363
|
+
const hasHigh = acc.findings.some((f) => f.severity === 'high');
|
|
364
|
+
const hasMedium = acc.findings.some((f) => f.severity === 'medium');
|
|
365
|
+
const verdict = hasHigh ? 'high' : hasMedium ? 'medium' : 'clean';
|
|
366
|
+
const exitCode = verdict === 'high' ? 2 : verdict === 'medium' ? 1 : 0;
|
|
367
|
+
const order = { high: 0, medium: 1, low: 2 };
|
|
368
|
+
const findings = [...acc.findings].sort((a, b) => order[a.severity] - order[b.severity] || a.id.localeCompare(b.id));
|
|
369
|
+
return {
|
|
370
|
+
verdict,
|
|
371
|
+
exitCode,
|
|
372
|
+
findings,
|
|
373
|
+
scanned: acc.scanned,
|
|
374
|
+
capabilities: {
|
|
375
|
+
shell: shellSurvivors.length > 0 || acc.serverShell,
|
|
376
|
+
network: Boolean(netFinding) || acc.serverNetwork,
|
|
377
|
+
fileWrite: Boolean(writeFinding),
|
|
378
|
+
secretsReachable: secretsReachable || acc.serverSecret,
|
|
379
|
+
defaultDeny,
|
|
380
|
+
},
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
//# sourceMappingURL=mcp-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-scan.js","sourceRoot":"","sources":["../src/mcp-scan.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dz mcp-scan --reconcile` — static capability reconciliation (Phase 3).
|
|
3
|
+
*
|
|
4
|
+
* Joins two things `dz` can read deterministically:
|
|
5
|
+
* - the project's GRANT surface (`scanMcp` of `.claude/settings*.json` + `.mcp.json`), and
|
|
6
|
+
* - the aggregate DECLARED capabilities of the installed skills (`.claude/skills/<id>/SKILL.md`),
|
|
7
|
+
* then REPORTS the gaps (under-grant / over-grant) and, optionally, EMITS a
|
|
8
|
+
* least-privilege advisory policy artifact for a host to consume.
|
|
9
|
+
*
|
|
10
|
+
* HONESTY: `dz` is build-time / static. It does NOT run agents and CANNOT block,
|
|
11
|
+
* time out, or rate-limit a tool call. The HOST (Claude Code enforcing
|
|
12
|
+
* settings.json allow/deny; or an MCP server consuming a policy.json) is the only
|
|
13
|
+
* thing that enforces at call time. Verbs here are REPORT / RECONCILE / EMIT /
|
|
14
|
+
* CANDIDATE — never BLOCK / DENIED / ENFORCED.
|
|
15
|
+
*
|
|
16
|
+
* @packageDocumentation
|
|
17
|
+
*/
|
|
18
|
+
import type { McpScanReport } from './mcp-scan.js';
|
|
19
|
+
/** The honesty banner — repeated in --help, the report header, and the artifact $comment. */
|
|
20
|
+
export declare const RECONCILE_BANNER: string;
|
|
21
|
+
/** A reconcilable capability axis (the axes BOTH the grant surface and the manifest expose). */
|
|
22
|
+
export type ReconcileAxis = 'shell' | 'network' | 'file-write';
|
|
23
|
+
/** Per-axis reconciliation state. */
|
|
24
|
+
export interface AxisState {
|
|
25
|
+
readonly axis: ReconcileAxis;
|
|
26
|
+
/** Project permits this capability (from scanMcp). */
|
|
27
|
+
readonly grant: boolean;
|
|
28
|
+
/** At least one installed skill declares it needs this (declared === true). */
|
|
29
|
+
readonly need: boolean;
|
|
30
|
+
/** Skills that declared need (true) — for attribution. */
|
|
31
|
+
readonly needSkills: readonly string[];
|
|
32
|
+
/** Installed skills that are SILENT on this axis (declared === undefined). */
|
|
33
|
+
readonly silentCount: number;
|
|
34
|
+
}
|
|
35
|
+
/** A single reconciliation finding. */
|
|
36
|
+
export interface ReconcileFinding {
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly kind: 'under-grant' | 'over-grant';
|
|
39
|
+
readonly severity: 'medium' | 'low' | 'info';
|
|
40
|
+
readonly axis: ReconcileAxis;
|
|
41
|
+
readonly detail: string;
|
|
42
|
+
readonly skills: readonly string[];
|
|
43
|
+
}
|
|
44
|
+
/** Aggregated declared limits across installed skills (tightest values; inert). */
|
|
45
|
+
export interface LimitsRollup {
|
|
46
|
+
readonly declaredBy: number;
|
|
47
|
+
readonly toolTimeoutMs?: number;
|
|
48
|
+
readonly maxToolCallsPerTurn?: number;
|
|
49
|
+
readonly requireApprovalForDangerous?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** The least-privilege advisory policy artifact (only written with --emit-policy). */
|
|
52
|
+
export interface PolicyArtifact {
|
|
53
|
+
readonly $comment: string;
|
|
54
|
+
readonly version: 1;
|
|
55
|
+
readonly defaultDeny: true;
|
|
56
|
+
/** axis → true ONLY where an installed skill declared need; absent otherwise. */
|
|
57
|
+
readonly allow: Partial<Record<ReconcileAxis, true>>;
|
|
58
|
+
readonly limits?: LimitsRollup;
|
|
59
|
+
readonly derivedFrom: {
|
|
60
|
+
readonly grants: Record<ReconcileAxis, boolean>;
|
|
61
|
+
readonly declaredNeed: Record<ReconcileAxis, boolean>;
|
|
62
|
+
readonly skillsByAxis: Partial<Record<ReconcileAxis, readonly string[]>>;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/** Result of {@link reconcileCapabilities}. */
|
|
66
|
+
export interface ReconcileReport {
|
|
67
|
+
readonly skillsDir: string;
|
|
68
|
+
readonly installedCount: number;
|
|
69
|
+
readonly axes: readonly AxisState[];
|
|
70
|
+
readonly findings: readonly ReconcileFinding[];
|
|
71
|
+
readonly limits: LimitsRollup | null;
|
|
72
|
+
readonly policy: PolicyArtifact;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Statically reconcile a project's GRANT surface against the DECLARED needs of
|
|
76
|
+
* its installed skills. Pure: same inputs → same report. No execution, no writes.
|
|
77
|
+
*/
|
|
78
|
+
export declare function reconcileCapabilities(report: McpScanReport, skillsDir: string): ReconcileReport;
|
|
79
|
+
//# sourceMappingURL=reconcile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../src/reconcile.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,6FAA6F;AAC7F,eAAO,MAAM,gBAAgB,QAGkD,CAAC;AAEhF,gGAAgG;AAChG,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,YAAY,CAAC;AAG/D,qCAAqC;AACrC,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,0DAA0D;IAC1D,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,8EAA8E;IAC9E,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,uCAAuC;AACvC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IAC7C,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;CACpC;AAED,mFAAmF;AACnF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IACtC,QAAQ,CAAC,2BAA2B,CAAC,EAAE,OAAO,CAAC;CAChD;AAED,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC;IAC3B,iFAAiF;IACjF,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;IACrD,QAAQ,CAAC,MAAM,CAAC,EAAE,YAAY,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE;QACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QAChD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QACtD,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;KAC1E,CAAC;CACH;AAED,+CAA+C;AAC/C,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,SAAS,SAAS,EAAE,CAAC;IACpC,QAAQ,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC/C,QAAQ,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;CACjC;AAqCD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,CA6E/F"}
|