@weave_protocol/mund 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -30
- package/dist/analyzers/index.d.ts.map +1 -1
- package/dist/analyzers/index.js +3 -1
- package/dist/analyzers/index.js.map +1 -1
- package/dist/canary.d.ts +48 -0
- package/dist/canary.d.ts.map +1 -0
- package/dist/canary.js +149 -0
- package/dist/canary.js.map +1 -0
- package/dist/cli/scan.d.ts +2 -1
- package/dist/cli/scan.d.ts.map +1 -1
- package/dist/cli/scan.js +10 -1
- package/dist/cli/scan.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/rugpull.d.ts +73 -0
- package/dist/rugpull.d.ts.map +1 -0
- package/dist/rugpull.js +177 -0
- package/dist/rugpull.js.map +1 -0
- package/dist/tools/canary-rugpull.d.ts +7 -0
- package/dist/tools/canary-rugpull.d.ts.map +1 -0
- package/dist/tools/canary-rugpull.js +50 -0
- package/dist/tools/canary-rugpull.js.map +1 -0
- package/dist/tools/index.d.ts +1 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +4 -0
- package/dist/tools/index.js.map +1 -1
- package/package.json +1 -1
- package/src/analyzers/index.ts +3 -1
- package/src/canary.ts +165 -0
- package/src/cli/scan.ts +10 -1
- package/src/index.ts +2 -0
- package/src/rugpull.ts +233 -0
- package/src/tools/canary-rugpull.ts +75 -0
- package/src/tools/index.ts +5 -0
- package/tests/analyzers/engine.test.ts +1 -1
- package/tests/canary.test.ts +79 -0
- package/tests/cli/scan.test.ts +2 -2
- package/tests/fixtures/canary/base64.json +9 -0
- package/tests/fixtures/canary/clean-output.json +8 -0
- package/tests/fixtures/canary/exact-leak.json +9 -0
- package/tests/fixtures/canary/lowercased.json +9 -0
- package/tests/fixtures/canary/other-token.json +8 -0
- package/tests/fixtures/canary/prefix-only.json +8 -0
- package/tests/fixtures/canary/reversed.json +9 -0
- package/tests/fixtures/canary/spaced-out.json +9 -0
- package/tests/fixtures/canary/zero-width.json +9 -0
- package/tests/fixtures/rugpull/description-changed/baseline.json +36 -0
- package/tests/fixtures/rugpull/description-changed/current.json +36 -0
- package/tests/fixtures/rugpull/description-changed/expected.json +11 -0
- package/tests/fixtures/rugpull/gains-execution/baseline.json +36 -0
- package/tests/fixtures/rugpull/gains-execution/current.json +36 -0
- package/tests/fixtures/rugpull/gains-execution/expected.json +18 -0
- package/tests/fixtures/rugpull/schema-changed/baseline.json +36 -0
- package/tests/fixtures/rugpull/schema-changed/current.json +39 -0
- package/tests/fixtures/rugpull/schema-changed/expected.json +11 -0
- package/tests/fixtures/rugpull/schema-key-reorder/baseline.json +36 -0
- package/tests/fixtures/rugpull/schema-key-reorder/current.json +36 -0
- package/tests/fixtures/rugpull/schema-key-reorder/expected.json +5 -0
- package/tests/fixtures/rugpull/server-renamed/baseline.json +36 -0
- package/tests/fixtures/rugpull/server-renamed/current.json +36 -0
- package/tests/fixtures/rugpull/server-renamed/expected.json +11 -0
- package/tests/fixtures/rugpull/tool-added/baseline.json +36 -0
- package/tests/fixtures/rugpull/tool-added/current.json +44 -0
- package/tests/fixtures/rugpull/tool-added/expected.json +11 -0
- package/tests/fixtures/rugpull/tool-removed/baseline.json +36 -0
- package/tests/fixtures/rugpull/tool-removed/current.json +21 -0
- package/tests/fixtures/rugpull/tool-removed/expected.json +11 -0
- package/tests/fixtures/rugpull/unchanged/baseline.json +36 -0
- package/tests/fixtures/rugpull/unchanged/current.json +36 -0
- package/tests/fixtures/rugpull/unchanged/expected.json +5 -0
- package/tests/fixtures.test.ts +40 -0
- package/tests/rugpull.test.ts +80 -0
- package/tests/tools/canary-rugpull.test.ts +57 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mund - The Guardian Protocol
|
|
3
|
+
* MCP Rug-Pull Detection (OWASP ASI04)
|
|
4
|
+
*
|
|
5
|
+
* A rug pull is an MCP server that passes review at install time and
|
|
6
|
+
* then changes what its tools say or do. Static scanning can't see that,
|
|
7
|
+
* so Mund records a fingerprint of every tool when a server is first
|
|
8
|
+
* approved (the baseline) and diffs later manifests against it.
|
|
9
|
+
*
|
|
10
|
+
* tool description changed -> high (the classic rug pull)
|
|
11
|
+
* tool input schema changed -> medium
|
|
12
|
+
* tool added -> medium
|
|
13
|
+
* tool removed -> low
|
|
14
|
+
* server name changed -> high
|
|
15
|
+
*
|
|
16
|
+
* Any changed or added tool is re-scanned with the MCP analyzer; if it
|
|
17
|
+
* now trips a high/critical finding, or newly gains execution
|
|
18
|
+
* capability, the change is escalated to critical.
|
|
19
|
+
*/
|
|
20
|
+
import type { IAnalyzer, SecurityIssue, Severity } from './types.js';
|
|
21
|
+
import { type McpServerManifest } from './analyzers/mcp-server-analyzer.js';
|
|
22
|
+
export type ToolChangeKind = 'description_changed' | 'schema_changed' | 'tool_added' | 'tool_removed' | 'server_renamed';
|
|
23
|
+
export interface ToolFingerprint {
|
|
24
|
+
descriptionHash: string;
|
|
25
|
+
schemaHash: string;
|
|
26
|
+
/** Kept so a diff can show what the description used to say. */
|
|
27
|
+
description: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ManifestBaseline {
|
|
30
|
+
serverId: string;
|
|
31
|
+
serverName: string;
|
|
32
|
+
recordedAt: string;
|
|
33
|
+
tools: Record<string, ToolFingerprint>;
|
|
34
|
+
}
|
|
35
|
+
export interface ToolChange {
|
|
36
|
+
kind: ToolChangeKind;
|
|
37
|
+
tool: string;
|
|
38
|
+
severity: Severity;
|
|
39
|
+
detail: string;
|
|
40
|
+
/** Set when the changed tool now trips the MCP analyzer or gains execution capability. */
|
|
41
|
+
escalated?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface RugPullResult {
|
|
44
|
+
serverId: string;
|
|
45
|
+
status: 'baseline_created' | 'unchanged' | 'changed';
|
|
46
|
+
changes: ToolChange[];
|
|
47
|
+
baselineRecordedAt?: string;
|
|
48
|
+
}
|
|
49
|
+
export declare function defaultBaselinePath(): string;
|
|
50
|
+
export declare function fingerprintManifest(serverId: string, manifest: McpServerManifest): ManifestBaseline;
|
|
51
|
+
/** Compare a manifest against its baseline. Pure: no I/O. */
|
|
52
|
+
export declare function diffManifest(baseline: ManifestBaseline, manifest: McpServerManifest, analyzer?: IAnalyzer): Promise<ToolChange[]>;
|
|
53
|
+
/** Persisted baselines, keyed by server id. */
|
|
54
|
+
export declare class BaselineStore {
|
|
55
|
+
private path;
|
|
56
|
+
constructor(path?: string);
|
|
57
|
+
load(): Promise<Record<string, ManifestBaseline>>;
|
|
58
|
+
get(serverId: string): Promise<ManifestBaseline | undefined>;
|
|
59
|
+
put(baseline: ManifestBaseline): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Check a manifest against its stored baseline. The first check for a
|
|
63
|
+
* server records the baseline. Pass acceptChanges to re-baseline after
|
|
64
|
+
* a human has reviewed the changes.
|
|
65
|
+
*/
|
|
66
|
+
export declare function checkRugPull(serverId: string, manifest: McpServerManifest, opts?: {
|
|
67
|
+
store?: BaselineStore;
|
|
68
|
+
analyzer?: IAnalyzer;
|
|
69
|
+
acceptChanges?: boolean;
|
|
70
|
+
}): Promise<RugPullResult>;
|
|
71
|
+
/** Express rug-pull changes as standard SecurityIssues (for scoring / SARIF). */
|
|
72
|
+
export declare function changesToIssues(result: RugPullResult): SecurityIssue[];
|
|
73
|
+
//# sourceMappingURL=rugpull.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rugpull.d.ts","sourceRoot":"","sources":["../src/rugpull.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACrE,OAAO,EAAqB,KAAK,iBAAiB,EAA0B,MAAM,oCAAoC,CAAC;AAEvH,MAAM,MAAM,cAAc,GAAG,qBAAqB,GAAG,gBAAgB,GAAG,YAAY,GAAG,cAAc,GAAG,gBAAgB,CAAC;AAEzH,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,cAAc,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,0FAA0F;IAC1F,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,kBAAkB,GAAG,WAAW,GAAG,SAAS,CAAC;IACrD,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAID,wBAAgB,mBAAmB,IAAI,MAAM,CAI5C;AAcD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,GAAG,gBAAgB,CAUnG;AAED,6DAA6D;AAC7D,wBAAsB,YAAY,CAChC,QAAQ,EAAE,gBAAgB,EAC1B,QAAQ,EAAE,iBAAiB,EAC3B,QAAQ,GAAE,SAAmC,GAC5C,OAAO,CAAC,UAAU,EAAE,CAAC,CAgDvB;AAoBD,+CAA+C;AAC/C,qBAAa,aAAa;IACZ,OAAO,CAAC,IAAI;gBAAJ,IAAI,GAAE,MAA8B;IAElD,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;IASjD,GAAG,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC;IAI5D,GAAG,CAAC,QAAQ,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;CAMrD;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,iBAAiB,EAC3B,IAAI,GAAE;IAAE,KAAK,CAAC,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GAClF,OAAO,CAAC,aAAa,CAAC,CAmBxB;AAED,iFAAiF;AACjF,wBAAgB,eAAe,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,EAAE,CAYtE"}
|
package/dist/rugpull.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mund - The Guardian Protocol
|
|
3
|
+
* MCP Rug-Pull Detection (OWASP ASI04)
|
|
4
|
+
*
|
|
5
|
+
* A rug pull is an MCP server that passes review at install time and
|
|
6
|
+
* then changes what its tools say or do. Static scanning can't see that,
|
|
7
|
+
* so Mund records a fingerprint of every tool when a server is first
|
|
8
|
+
* approved (the baseline) and diffs later manifests against it.
|
|
9
|
+
*
|
|
10
|
+
* tool description changed -> high (the classic rug pull)
|
|
11
|
+
* tool input schema changed -> medium
|
|
12
|
+
* tool added -> medium
|
|
13
|
+
* tool removed -> low
|
|
14
|
+
* server name changed -> high
|
|
15
|
+
*
|
|
16
|
+
* Any changed or added tool is re-scanned with the MCP analyzer; if it
|
|
17
|
+
* now trips a high/critical finding, or newly gains execution
|
|
18
|
+
* capability, the change is escalated to critical.
|
|
19
|
+
*/
|
|
20
|
+
import { createHash } from 'crypto';
|
|
21
|
+
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
22
|
+
import { homedir } from 'os';
|
|
23
|
+
import { dirname, join } from 'path';
|
|
24
|
+
import { McpServerAnalyzer } from './analyzers/mcp-server-analyzer.js';
|
|
25
|
+
const SEVERITY_RANK = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
26
|
+
export function defaultBaselinePath() {
|
|
27
|
+
if (process.env.MUND_BASELINE_PATH)
|
|
28
|
+
return process.env.MUND_BASELINE_PATH;
|
|
29
|
+
const base = process.env.XDG_CONFIG_HOME || join(homedir(), '.config');
|
|
30
|
+
return join(base, 'mund', 'mcp-baselines.json');
|
|
31
|
+
}
|
|
32
|
+
/** Stable JSON: object keys sorted so equivalent schemas hash the same. */
|
|
33
|
+
function canonical(value) {
|
|
34
|
+
if (Array.isArray(value))
|
|
35
|
+
return `[${value.map(canonical).join(',')}]`;
|
|
36
|
+
if (value && typeof value === 'object') {
|
|
37
|
+
const entries = Object.entries(value).sort(([a], [b]) => a.localeCompare(b));
|
|
38
|
+
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${canonical(v)}`).join(',')}}`;
|
|
39
|
+
}
|
|
40
|
+
return JSON.stringify(value ?? null);
|
|
41
|
+
}
|
|
42
|
+
const sha256 = (s) => createHash('sha256').update(s).digest('hex');
|
|
43
|
+
export function fingerprintManifest(serverId, manifest) {
|
|
44
|
+
const tools = {};
|
|
45
|
+
for (const tool of manifest.tools ?? []) {
|
|
46
|
+
tools[tool.name] = {
|
|
47
|
+
descriptionHash: sha256(tool.description ?? ''),
|
|
48
|
+
schemaHash: sha256(canonical(tool.inputSchema ?? {})),
|
|
49
|
+
description: (tool.description ?? '').slice(0, 2000),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return { serverId, serverName: manifest.name ?? serverId, recordedAt: new Date().toISOString(), tools };
|
|
53
|
+
}
|
|
54
|
+
/** Compare a manifest against its baseline. Pure: no I/O. */
|
|
55
|
+
export async function diffManifest(baseline, manifest, analyzer = new McpServerAnalyzer()) {
|
|
56
|
+
const current = fingerprintManifest(baseline.serverId, manifest);
|
|
57
|
+
const byName = new Map((manifest.tools ?? []).map(t => [t.name, t]));
|
|
58
|
+
const changes = [];
|
|
59
|
+
if (current.serverName !== baseline.serverName) {
|
|
60
|
+
changes.push({
|
|
61
|
+
kind: 'server_renamed', tool: '*', severity: 'high',
|
|
62
|
+
detail: `Server name changed from "${baseline.serverName}" to "${current.serverName}"`,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
for (const [name, now] of Object.entries(current.tools)) {
|
|
66
|
+
const before = baseline.tools[name];
|
|
67
|
+
if (!before) {
|
|
68
|
+
changes.push({ kind: 'tool_added', tool: name, severity: 'medium', detail: `New tool "${name}" not present at baseline` });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (now.descriptionHash !== before.descriptionHash) {
|
|
72
|
+
changes.push({
|
|
73
|
+
kind: 'description_changed', tool: name, severity: 'high',
|
|
74
|
+
detail: `Description of "${name}" changed after approval`,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (now.schemaHash !== before.schemaHash) {
|
|
78
|
+
changes.push({ kind: 'schema_changed', tool: name, severity: 'medium', detail: `Input schema of "${name}" changed after approval` });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const name of Object.keys(baseline.tools)) {
|
|
82
|
+
if (!current.tools[name]) {
|
|
83
|
+
changes.push({ kind: 'tool_removed', tool: name, severity: 'low', detail: `Tool "${name}" removed since baseline` });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Escalate changed/added tools that now look dangerous.
|
|
87
|
+
for (const change of changes) {
|
|
88
|
+
if (change.kind === 'tool_removed' || change.kind === 'server_renamed')
|
|
89
|
+
continue;
|
|
90
|
+
const tool = byName.get(change.tool);
|
|
91
|
+
if (!tool)
|
|
92
|
+
continue;
|
|
93
|
+
const reason = await escalationReason(tool, baseline.tools[change.tool], analyzer);
|
|
94
|
+
if (reason) {
|
|
95
|
+
change.severity = 'critical';
|
|
96
|
+
change.escalated = reason;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return changes.sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity]);
|
|
100
|
+
}
|
|
101
|
+
async function escalationReason(tool, before, analyzer) {
|
|
102
|
+
const single = JSON.stringify({ name: 'rugpull-check', tools: [tool] });
|
|
103
|
+
const findings = await analyzer.analyze(single, []);
|
|
104
|
+
const serious = findings.find(f => f.severity === 'critical' || f.severity === 'high');
|
|
105
|
+
if (serious)
|
|
106
|
+
return `Changed tool now triggers "${serious.rule_name}"`;
|
|
107
|
+
const nowExec = McpServerAnalyzer.analyzeCapabilities({ name: 'x', version: '0', tools: [tool] }).execution;
|
|
108
|
+
const wasExec = before
|
|
109
|
+
? McpServerAnalyzer.analyzeCapabilities({ name: 'x', version: '0', tools: [{ name: tool.name, description: before.description }] }).execution
|
|
110
|
+
: false;
|
|
111
|
+
if (nowExec && !wasExec)
|
|
112
|
+
return 'Tool newly requests command-execution capability';
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
/** Persisted baselines, keyed by server id. */
|
|
116
|
+
export class BaselineStore {
|
|
117
|
+
path;
|
|
118
|
+
constructor(path = defaultBaselinePath()) {
|
|
119
|
+
this.path = path;
|
|
120
|
+
}
|
|
121
|
+
async load() {
|
|
122
|
+
try {
|
|
123
|
+
const data = JSON.parse(await readFile(this.path, 'utf8'));
|
|
124
|
+
return data && typeof data === 'object' && data.version === 1 ? data.baselines : {};
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return {};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async get(serverId) {
|
|
131
|
+
return (await this.load())[serverId];
|
|
132
|
+
}
|
|
133
|
+
async put(baseline) {
|
|
134
|
+
const all = await this.load();
|
|
135
|
+
all[baseline.serverId] = baseline;
|
|
136
|
+
await mkdir(dirname(this.path), { recursive: true });
|
|
137
|
+
await writeFile(this.path, JSON.stringify({ version: 1, baselines: all }, null, 2));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Check a manifest against its stored baseline. The first check for a
|
|
142
|
+
* server records the baseline. Pass acceptChanges to re-baseline after
|
|
143
|
+
* a human has reviewed the changes.
|
|
144
|
+
*/
|
|
145
|
+
export async function checkRugPull(serverId, manifest, opts = {}) {
|
|
146
|
+
const store = opts.store ?? new BaselineStore();
|
|
147
|
+
const baseline = await store.get(serverId);
|
|
148
|
+
if (!baseline) {
|
|
149
|
+
await store.put(fingerprintManifest(serverId, manifest));
|
|
150
|
+
return { serverId, status: 'baseline_created', changes: [] };
|
|
151
|
+
}
|
|
152
|
+
const changes = await diffManifest(baseline, manifest, opts.analyzer);
|
|
153
|
+
if (opts.acceptChanges && changes.length > 0) {
|
|
154
|
+
await store.put(fingerprintManifest(serverId, manifest));
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
serverId,
|
|
158
|
+
status: changes.length > 0 ? 'changed' : 'unchanged',
|
|
159
|
+
changes,
|
|
160
|
+
baselineRecordedAt: baseline.recordedAt,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/** Express rug-pull changes as standard SecurityIssues (for scoring / SARIF). */
|
|
164
|
+
export function changesToIssues(result) {
|
|
165
|
+
return result.changes.map(c => ({
|
|
166
|
+
rule_id: `mcp_rugpull_${c.kind}`,
|
|
167
|
+
rule_name: `MCP rug pull: ${c.detail}`,
|
|
168
|
+
type: 'mcp_rugpull',
|
|
169
|
+
severity: c.severity,
|
|
170
|
+
action: c.severity === 'critical' || c.severity === 'high' ? 'block' : 'alert',
|
|
171
|
+
match: `${result.serverId}:${c.tool}`,
|
|
172
|
+
suggestion: c.escalated
|
|
173
|
+
? `${c.escalated}. Do not use this server until the change is reviewed.`
|
|
174
|
+
: 'Review the change before continuing to use this server; accept it to update the baseline.',
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=rugpull.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rugpull.js","sourceRoot":"","sources":["../src/rugpull.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mund - The Guardian Protocol
|
|
3
|
+
* Canary token (OWASP LLM07) and MCP rug-pull (OWASP ASI04) tools
|
|
4
|
+
*/
|
|
5
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
6
|
+
export declare function registerCanaryRugPullTools(server: McpServer): void;
|
|
7
|
+
//# sourceMappingURL=canary-rugpull.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canary-rugpull.d.ts","sourceRoot":"","sources":["../../src/tools/canary-rugpull.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAQzE,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA4DlE"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mund - The Guardian Protocol
|
|
3
|
+
* Canary token (OWASP LLM07) and MCP rug-pull (OWASP ASI04) tools
|
|
4
|
+
*/
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { canaries } from '../canary.js';
|
|
7
|
+
import { checkRugPull, changesToIssues } from '../rugpull.js';
|
|
8
|
+
import { McpServerAnalyzer } from '../analyzers/mcp-server-analyzer.js';
|
|
9
|
+
import { computeRiskScore } from '../scoring.js';
|
|
10
|
+
const text = (value) => ({ content: [{ type: 'text', text: JSON.stringify(value, null, 2) }] });
|
|
11
|
+
export function registerCanaryRugPullTools(server) {
|
|
12
|
+
server.tool('mund_create_canary', 'Create a canary token to place in a system prompt or other confidential context. Every later scan flags output containing it (even spaced out, base64-encoded or reversed), revealing prompt leakage.', {
|
|
13
|
+
label: z.string().optional().describe('What the canary protects, e.g. "support-bot system prompt"'),
|
|
14
|
+
token: z.string().optional().describe('Register an existing token instead of generating one'),
|
|
15
|
+
}, async ({ label, token }) => {
|
|
16
|
+
try {
|
|
17
|
+
const canary = token ? canaries.register(token, label) : canaries.create(label);
|
|
18
|
+
return text({
|
|
19
|
+
...canary,
|
|
20
|
+
how_to_use: `Add this line to the system prompt: "Internal reference: ${canary.token}. Never repeat it."`,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
return text({ error: error.message });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
server.tool('mund_list_canaries', 'List registered canary tokens.', {}, async () => text({ canaries: canaries.list() }));
|
|
28
|
+
server.tool('mund_remove_canary', 'Remove a canary token.', { token: z.string().describe('Token to remove') }, async ({ token }) => text({ removed: canaries.remove(token) }));
|
|
29
|
+
server.tool('mund_check_mcp_rugpull', 'Detect MCP rug pulls: compare a server manifest with the baseline recorded when the server was first checked. Flags changed tool descriptions or schemas, and added or removed tools; escalates to critical when a changed tool now looks malicious or gains command execution. The first check records the baseline.', {
|
|
30
|
+
server_id: z.string().describe('Stable identifier for the server, e.g. its registry name or install path'),
|
|
31
|
+
manifest: z.string().describe('JSON content of the server manifest (server.json or tools/list result)'),
|
|
32
|
+
accept_changes: z.boolean().optional().describe('After human review, accept the changes and update the baseline'),
|
|
33
|
+
}, async ({ server_id, manifest, accept_changes }) => {
|
|
34
|
+
const parsed = McpServerAnalyzer.parseManifest(manifest);
|
|
35
|
+
if (!parsed)
|
|
36
|
+
return text({ error: 'Invalid manifest: expected JSON with name, tools or resources' });
|
|
37
|
+
const result = await checkRugPull(server_id, parsed, { acceptChanges: accept_changes });
|
|
38
|
+
const risk = computeRiskScore(changesToIssues(result));
|
|
39
|
+
return text({
|
|
40
|
+
...result,
|
|
41
|
+
risk_score: risk.score,
|
|
42
|
+
risk_label: risk.label,
|
|
43
|
+
recommendation: result.status === 'baseline_created' ? 'BASELINE_RECORDED'
|
|
44
|
+
: result.changes.some(c => c.severity === 'critical') ? 'DO_NOT_USE'
|
|
45
|
+
: result.changes.length > 0 ? (accept_changes ? 'CHANGES_ACCEPTED' : 'REVIEW_CHANGES')
|
|
46
|
+
: 'UNCHANGED',
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=canary-rugpull.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canary-rugpull.js","sourceRoot":"","sources":["../../src/tools/canary-rugpull.ts"],"names":[],"mappings":"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"}
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
export { registerMonitoringTools } from './monitoring.js';
|
|
6
6
|
export { registerConfigurationTools } from './configuration.js';
|
|
7
7
|
export { registerMcpScanningTools } from './mcp-scanning.js';
|
|
8
|
+
export { registerCanaryRugPullTools } from './canary-rugpull.js';
|
|
8
9
|
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
9
10
|
import type { IStorage, DetectionRule } from '../types.js';
|
|
10
11
|
import { AnalyzerEngine } from '../analyzers/index.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAMjE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAE5D;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,QAAQ,EACjB,KAAK,EAAE,aAAa,EAAE,EACtB,QAAQ,EAAE,cAAc,EACxB,eAAe,EAAE,eAAe,EAChC,SAAS,EAAE,OAAO,GACjB,IAAI,CAYN"}
|
package/dist/tools/index.js
CHANGED
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
export { registerMonitoringTools } from './monitoring.js';
|
|
6
6
|
export { registerConfigurationTools } from './configuration.js';
|
|
7
7
|
export { registerMcpScanningTools } from './mcp-scanning.js';
|
|
8
|
+
export { registerCanaryRugPullTools } from './canary-rugpull.js';
|
|
8
9
|
import { registerMonitoringTools } from './monitoring.js';
|
|
9
10
|
import { registerConfigurationTools } from './configuration.js';
|
|
10
11
|
import { registerMcpScanningTools } from './mcp-scanning.js';
|
|
12
|
+
import { registerCanaryRugPullTools } from './canary-rugpull.js';
|
|
11
13
|
/**
|
|
12
14
|
* Register all Mund MCP tools
|
|
13
15
|
*/
|
|
@@ -18,5 +20,7 @@ export function registerAllTools(server, storage, rules, analyzer, notificationH
|
|
|
18
20
|
registerConfigurationTools(server, storage, rules);
|
|
19
21
|
// MCP server scanning tools (new)
|
|
20
22
|
registerMcpScanningTools(server);
|
|
23
|
+
// Canary tokens (LLM07) and MCP rug-pull detection (ASI04)
|
|
24
|
+
registerCanaryRugPullTools(server);
|
|
21
25
|
}
|
|
22
26
|
//# sourceMappingURL=index.js.map
|
package/dist/tools/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAEjE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,0BAA0B,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AAMjE;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAC9B,MAAiB,EACjB,OAAiB,EACjB,KAAsB,EACtB,QAAwB,EACxB,eAAgC,EAChC,SAAkB;IAElB,wBAAwB;IACxB,uBAAuB,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,SAAS,CAAC,CAAC;IAEtF,iCAAiC;IACjC,0BAA0B,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IAEnD,kCAAkC;IAClC,wBAAwB,CAAC,MAAM,CAAC,CAAC;IAEjC,2DAA2D;IAC3D,0BAA0B,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC"}
|
package/package.json
CHANGED
package/src/analyzers/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { CodeAnalyzer } from './code-analyzer.js';
|
|
|
16
16
|
import { InjectionDetector } from './injection-detector.js';
|
|
17
17
|
import { ExfiltrationDetector } from './exfiltration-detector.js';
|
|
18
18
|
import { McpServerAnalyzer } from './mcp-server-analyzer.js';
|
|
19
|
+
import { CanaryDetector } from '../canary.js';
|
|
19
20
|
import type { IAnalyzer } from '../types.js';
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -28,7 +29,8 @@ export function getAnalyzers(): IAnalyzer[] {
|
|
|
28
29
|
new CodeAnalyzer(),
|
|
29
30
|
new InjectionDetector(),
|
|
30
31
|
new ExfiltrationDetector(),
|
|
31
|
-
new McpServerAnalyzer()
|
|
32
|
+
new McpServerAnalyzer(),
|
|
33
|
+
new CanaryDetector()
|
|
32
34
|
];
|
|
33
35
|
}
|
|
34
36
|
|
package/src/canary.ts
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mund - The Guardian Protocol
|
|
3
|
+
* Canary Tokens - detect system prompt leakage (OWASP LLM07)
|
|
4
|
+
*
|
|
5
|
+
* Generate a unique marker, place it in your system prompt (or any
|
|
6
|
+
* confidential context), and Mund flags any scanned output that
|
|
7
|
+
* contains it. A model that has been talked into revealing its
|
|
8
|
+
* instructions reveals the canary with them.
|
|
9
|
+
*
|
|
10
|
+
* Matching tolerates the cheap evasions a leaking model or an attacker
|
|
11
|
+
* might apply: case changes, inserted whitespace / zero-width
|
|
12
|
+
* characters / punctuation, base64 encoding, and reversal.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { randomBytes } from 'crypto';
|
|
16
|
+
import { readFileSync } from 'fs';
|
|
17
|
+
import type { DetectionRule, IAnalyzer, SecurityIssue } from './types.js';
|
|
18
|
+
|
|
19
|
+
export interface Canary {
|
|
20
|
+
token: string;
|
|
21
|
+
label: string;
|
|
22
|
+
createdAt: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type CanaryMatchKind = 'exact' | 'obfuscated' | 'base64' | 'reversed';
|
|
26
|
+
|
|
27
|
+
const TOKEN_PREFIX = 'MUND-CANARY-';
|
|
28
|
+
const MIN_TOKEN_LENGTH = 12;
|
|
29
|
+
const ZERO_WIDTH = /[-]/g;
|
|
30
|
+
|
|
31
|
+
/** Lowercase and keep only letters and digits. */
|
|
32
|
+
function squash(text: string): string {
|
|
33
|
+
return text.replace(ZERO_WIDTH, '').toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class CanaryRegistry {
|
|
37
|
+
private canaries = new Map<string, Canary>();
|
|
38
|
+
|
|
39
|
+
/** Create a new random canary token. */
|
|
40
|
+
create(label = 'system-prompt'): Canary {
|
|
41
|
+
const canary: Canary = {
|
|
42
|
+
token: TOKEN_PREFIX + randomBytes(8).toString('hex').toUpperCase(),
|
|
43
|
+
label,
|
|
44
|
+
createdAt: new Date().toISOString(),
|
|
45
|
+
};
|
|
46
|
+
this.canaries.set(canary.token, canary);
|
|
47
|
+
return canary;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Register an existing token (e.g. one already deployed in a prompt). */
|
|
51
|
+
register(token: string, label = 'system-prompt'): Canary {
|
|
52
|
+
if (squash(token).length < MIN_TOKEN_LENGTH) {
|
|
53
|
+
throw new Error(`Canary token must contain at least ${MIN_TOKEN_LENGTH} letters/digits to avoid false positives`);
|
|
54
|
+
}
|
|
55
|
+
const canary: Canary = { token, label, createdAt: new Date().toISOString() };
|
|
56
|
+
this.canaries.set(token, canary);
|
|
57
|
+
return canary;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
remove(token: string): boolean {
|
|
61
|
+
return this.canaries.delete(token);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
list(): Canary[] {
|
|
65
|
+
return [...this.canaries.values()];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get size(): number {
|
|
69
|
+
return this.canaries.size;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Find every registered canary that appears in the content. */
|
|
73
|
+
find(content: string): Array<{ canary: Canary; kind: CanaryMatchKind; index: number }> {
|
|
74
|
+
const hits: Array<{ canary: Canary; kind: CanaryMatchKind; index: number }> = [];
|
|
75
|
+
if (this.canaries.size === 0 || !content) return hits;
|
|
76
|
+
|
|
77
|
+
const lower = content.toLowerCase();
|
|
78
|
+
const squashed = squash(content);
|
|
79
|
+
const decoded = decodeBase64Runs(content).map(squash);
|
|
80
|
+
|
|
81
|
+
for (const canary of this.canaries.values()) {
|
|
82
|
+
const needle = squash(canary.token);
|
|
83
|
+
const exactAt = lower.indexOf(canary.token.toLowerCase());
|
|
84
|
+
|
|
85
|
+
if (exactAt !== -1) {
|
|
86
|
+
hits.push({ canary, kind: 'exact', index: exactAt });
|
|
87
|
+
} else if (squashed.includes(needle)) {
|
|
88
|
+
hits.push({ canary, kind: 'obfuscated', index: -1 });
|
|
89
|
+
} else if (decoded.some(d => d.includes(needle))) {
|
|
90
|
+
hits.push({ canary, kind: 'base64', index: -1 });
|
|
91
|
+
} else if (squashed.includes([...needle].reverse().join(''))) {
|
|
92
|
+
hits.push({ canary, kind: 'reversed', index: -1 });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return hits;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Decode every plausible base64 run in the text (best effort). */
|
|
100
|
+
function decodeBase64Runs(content: string): string[] {
|
|
101
|
+
const runs = content.match(/[A-Za-z0-9+/]{16,}={0,2}/g) ?? [];
|
|
102
|
+
const out: string[] = [];
|
|
103
|
+
for (const run of runs) {
|
|
104
|
+
try {
|
|
105
|
+
const text = Buffer.from(run, 'base64').toString('utf8');
|
|
106
|
+
if (/^[\x20-\x7E\s]+$/.test(text)) out.push(text);
|
|
107
|
+
} catch {
|
|
108
|
+
// not base64
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Shared registry used by the MCP tools and the default analyzer set. */
|
|
115
|
+
export const canaries = new CanaryRegistry();
|
|
116
|
+
|
|
117
|
+
/** Load canaries from MUND_CANARIES (comma-separated) or MUND_CANARY_FILE (one per line). */
|
|
118
|
+
export function loadCanariesFromEnv(registry: CanaryRegistry = canaries): number {
|
|
119
|
+
const tokens: string[] = [];
|
|
120
|
+
if (process.env.MUND_CANARIES) tokens.push(...process.env.MUND_CANARIES.split(','));
|
|
121
|
+
if (process.env.MUND_CANARY_FILE) {
|
|
122
|
+
try {
|
|
123
|
+
tokens.push(...readFileSync(process.env.MUND_CANARY_FILE, 'utf8').split('\n'));
|
|
124
|
+
} catch {
|
|
125
|
+
// missing file: nothing to load
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
let loaded = 0;
|
|
129
|
+
for (const t of tokens.map(t => t.trim()).filter(Boolean)) {
|
|
130
|
+
try {
|
|
131
|
+
registry.register(t, 'env');
|
|
132
|
+
loaded++;
|
|
133
|
+
} catch {
|
|
134
|
+
// too short; skip
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return loaded;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export class CanaryDetector implements IAnalyzer {
|
|
141
|
+
name = 'CanaryDetector';
|
|
142
|
+
type = 'exfiltration';
|
|
143
|
+
|
|
144
|
+
constructor(private registry: CanaryRegistry = canaries) {}
|
|
145
|
+
|
|
146
|
+
async analyze(content: string, _rules: DetectionRule[]): Promise<SecurityIssue[]> {
|
|
147
|
+
return this.registry.find(content).map(({ canary, kind, index }) => {
|
|
148
|
+
const before = index >= 0 ? content.slice(0, index).split('\n') : null;
|
|
149
|
+
return {
|
|
150
|
+
rule_id: 'canary_leak',
|
|
151
|
+
rule_name: `Canary token leaked (${kind})`,
|
|
152
|
+
type: 'exfiltration',
|
|
153
|
+
severity: 'critical',
|
|
154
|
+
action: 'block',
|
|
155
|
+
match: `${canary.token.slice(0, TOKEN_PREFIX.length + 4)}…`,
|
|
156
|
+
...(before
|
|
157
|
+
? { location: { start: index, end: index + canary.token.length, line: before.length, column: before[before.length - 1].length + 1 } }
|
|
158
|
+
: {}),
|
|
159
|
+
suggestion:
|
|
160
|
+
`Content contains the "${canary.label}" canary, so confidential instructions or context are leaking. ` +
|
|
161
|
+
'Block this output and review the prompt that produced it.',
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
package/src/cli/scan.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { computeRiskScore, type RiskScore } from '../scoring.js';
|
|
|
16
16
|
import { toSarif, type SarifTarget } from '../sarif.js';
|
|
17
17
|
import { OsvClient, parseNpmLockfile, advisoriesToIssues, type OsvSource } from '../osv.js';
|
|
18
18
|
import { getVersion } from '../version.js';
|
|
19
|
+
import { canaries, loadCanariesFromEnv } from '../canary.js';
|
|
19
20
|
|
|
20
21
|
export type OutputFormat = 'text' | 'json' | 'sarif';
|
|
21
22
|
type FailOn = Severity | 'none';
|
|
@@ -27,6 +28,7 @@ export interface ScanCliOptions {
|
|
|
27
28
|
failOn: FailOn;
|
|
28
29
|
osv: boolean;
|
|
29
30
|
offline: boolean;
|
|
31
|
+
canaries: string[];
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
export interface FileReport extends SarifTarget {
|
|
@@ -58,6 +60,8 @@ Options:
|
|
|
58
60
|
(critical, high [default], medium, low, none)
|
|
59
61
|
--no-osv Skip dependency vulnerability lookups
|
|
60
62
|
--offline Use only the local OSV cache (also: MUND_OFFLINE=1)
|
|
63
|
+
--canary=TOKEN Flag any file containing this canary token (repeatable;
|
|
64
|
+
also MUND_CANARIES / MUND_CANARY_FILE)
|
|
61
65
|
|
|
62
66
|
Examples:
|
|
63
67
|
mund-mcp scan .
|
|
@@ -65,7 +69,7 @@ Examples:
|
|
|
65
69
|
`;
|
|
66
70
|
|
|
67
71
|
export function parseScanArgs(args: string[]): ScanCliOptions {
|
|
68
|
-
const opts: ScanCliOptions = { paths: [], format: 'text', failOn: 'high', osv: true, offline: false };
|
|
72
|
+
const opts: ScanCliOptions = { paths: [], format: 'text', failOn: 'high', osv: true, offline: false, canaries: [] };
|
|
69
73
|
|
|
70
74
|
for (let i = 0; i < args.length; i++) {
|
|
71
75
|
const arg = args[i];
|
|
@@ -99,6 +103,9 @@ export function parseScanArgs(args: string[]): ScanCliOptions {
|
|
|
99
103
|
case '--offline':
|
|
100
104
|
opts.offline = true;
|
|
101
105
|
break;
|
|
106
|
+
case '--canary':
|
|
107
|
+
opts.canaries.push(value());
|
|
108
|
+
break;
|
|
102
109
|
default:
|
|
103
110
|
if (flag.startsWith('-')) throw new Error(`Unknown option ${flag}`);
|
|
104
111
|
opts.paths.push(arg);
|
|
@@ -111,6 +118,8 @@ export function parseScanArgs(args: string[]): ScanCliOptions {
|
|
|
111
118
|
|
|
112
119
|
export async function runScan(opts: ScanCliOptions, osvClient?: OsvClient): Promise<ScanReport> {
|
|
113
120
|
const cwd = process.cwd();
|
|
121
|
+
loadCanariesFromEnv();
|
|
122
|
+
for (const token of opts.canaries) canaries.register(token, 'cli');
|
|
114
123
|
const engine = new AnalyzerEngine();
|
|
115
124
|
const files: FileReport[] = [];
|
|
116
125
|
const lockfiles: string[] = [];
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { NotificationHub } from './notifications/index.js';
|
|
|
17
17
|
import { registerAllTools } from './tools/index.js';
|
|
18
18
|
import { runScanCli } from './cli/scan.js';
|
|
19
19
|
import { getVersion } from './version.js';
|
|
20
|
+
import { loadCanariesFromEnv } from './canary.js';
|
|
20
21
|
|
|
21
22
|
// Threat Intelligence imports
|
|
22
23
|
import { threatIntel } from './threat-intel-manager.js';
|
|
@@ -180,6 +181,7 @@ async function createMundServer(config: MundConfig): Promise<McpServer> {
|
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
// Register all core tools
|
|
184
|
+
loadCanariesFromEnv();
|
|
183
185
|
registerAllTools(server, storage, rules, analyzer, notificationHub, config.block_mode);
|
|
184
186
|
|
|
185
187
|
// Register threat intelligence tools
|