@claude-flow/security 3.0.0-alpha.8 → 3.0.0-alpha.9
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/.claude-flow/data/pending-insights.jsonl +6 -0
- package/.claude-flow/neural/stats.json +6 -0
- package/__tests__/authorization-propagator.test.ts +155 -0
- package/__tests__/plugin-integrity-verifier.test.ts +199 -0
- package/__tests__/tool-output-guardrail.test.ts +256 -0
- package/dist/authorization/propagator.d.ts +148 -0
- package/dist/authorization/propagator.d.ts.map +1 -0
- package/dist/authorization/propagator.js +182 -0
- package/dist/authorization/propagator.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -1
- package/dist/plugins/integrity-verifier.d.ts +125 -0
- package/dist/plugins/integrity-verifier.d.ts.map +1 -0
- package/dist/plugins/integrity-verifier.js +186 -0
- package/dist/plugins/integrity-verifier.js.map +1 -0
- package/dist/tool-output-guardrail.d.ts +126 -0
- package/dist/tool-output-guardrail.d.ts.map +1 -0
- package/dist/tool-output-guardrail.js +257 -0
- package/dist/tool-output-guardrail.js.map +1 -0
- package/package.json +1 -1
- package/ruvector.db +0 -0
- package/src/authorization/propagator.ts +279 -0
- package/src/index.ts +43 -0
- package/src/plugins/integrity-verifier.ts +265 -0
- package/src/tool-output-guardrail.ts +360 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PluginIntegrityVerifier — install-layer security for plugin supply chain.
|
|
3
|
+
*
|
|
4
|
+
* Implements P1 of ADR-145 (ruvnet/ruflo#2254): Stage-1 Ed25519 signature
|
|
5
|
+
* verification at `plugins install`. Stage-2 (semantic-intent scan against
|
|
6
|
+
* SCH attacks) lands in P2; per-namespace write ACLs land in P3-P4.
|
|
7
|
+
*
|
|
8
|
+
* Threat model
|
|
9
|
+
* ------------
|
|
10
|
+
* Ruflo's plugin install path fetches manifests from IPFS via Pinata with
|
|
11
|
+
* NO signature verification and NO intent analysis. Two attacks land today:
|
|
12
|
+
*
|
|
13
|
+
* - DDIPE (arXiv:2604.03081): malicious logic embedded in plugin docs and
|
|
14
|
+
* config templates. 11.6-33.5% bypass rate across 4 frameworks/5 models;
|
|
15
|
+
* 2.5% evade both detection and alignment. Stage-1 signing blocks the
|
|
16
|
+
* static-payload variants.
|
|
17
|
+
*
|
|
18
|
+
* - SCH (arXiv:2605.14460): malicious intent wrapped as natural-language
|
|
19
|
+
* "compliance rules" in plugin descriptions. The agent generates the
|
|
20
|
+
* harmful code at runtime — no static payload exists. 77.67% breach
|
|
21
|
+
* success, 0.00% scanner detection. Stage-2 (P2) catches this; Stage-1
|
|
22
|
+
* does not.
|
|
23
|
+
*
|
|
24
|
+
* Scope (P1)
|
|
25
|
+
* ----------
|
|
26
|
+
* - Ed25519 signature verification of a detached signature over the manifest.
|
|
27
|
+
* - Trust-anchor allowlist: the publisher's signing key fingerprint must be
|
|
28
|
+
* in `trust-anchors.json`, gated on CODEOWNERS review.
|
|
29
|
+
* - Backwards compatible: default mode is warn-only. `CLAUDE_FLOW_STRICT_PLUGINS=true`
|
|
30
|
+
* makes verification a hard gate.
|
|
31
|
+
*
|
|
32
|
+
* Non-goals (P1)
|
|
33
|
+
* --------------
|
|
34
|
+
* - Semantic intent scan (SCH defence). Lands in P2 with a pattern-rule
|
|
35
|
+
* fallback for environments without LLM credentials.
|
|
36
|
+
* - Sandboxing the plugin runtime. Orthogonal blast-radius concern.
|
|
37
|
+
*
|
|
38
|
+
* Reference: ADR-145, arXiv:2605.14460 (SCH), arXiv:2604.03081 (DDIPE),
|
|
39
|
+
* arXiv:2604.16548 (Mnemonic Sovereignty survey).
|
|
40
|
+
*/
|
|
41
|
+
import { createHash } from 'node:crypto';
|
|
42
|
+
import * as nodeCrypto from 'node:crypto';
|
|
43
|
+
/**
|
|
44
|
+
* Canonical JSON serialisation for hashing. Deterministic key ordering at
|
|
45
|
+
* every nesting level. Pure; identical input → identical output bytes.
|
|
46
|
+
*/
|
|
47
|
+
export function canonicalize(value) {
|
|
48
|
+
if (value === null || typeof value !== 'object')
|
|
49
|
+
return JSON.stringify(value);
|
|
50
|
+
if (Array.isArray(value))
|
|
51
|
+
return '[' + value.map(canonicalize).join(',') + ']';
|
|
52
|
+
const obj = value;
|
|
53
|
+
const keys = Object.keys(obj).sort();
|
|
54
|
+
return '{' + keys.map(k => JSON.stringify(k) + ':' + canonicalize(obj[k])).join(',') + '}';
|
|
55
|
+
}
|
|
56
|
+
/** Hex-encoded SHA-256 of the canonical-JSON manifest. */
|
|
57
|
+
export function hashManifest(manifest) {
|
|
58
|
+
return createHash('sha256').update(canonicalize(manifest)).digest('hex');
|
|
59
|
+
}
|
|
60
|
+
/** Short fingerprint for telemetry — first 16 hex chars of the key. */
|
|
61
|
+
export function fingerprint(publicKey) {
|
|
62
|
+
return publicKey.slice(0, 16);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Find the trust anchor that vouches for a manifest. Returns the matched
|
|
66
|
+
* anchor (with anchor.publicKey === signer.publicKey) or null.
|
|
67
|
+
*
|
|
68
|
+
* Scope-matching uses a minimal glob: `*` at the end of the scope string
|
|
69
|
+
* matches any suffix. Empty/missing scope matches every plugin id.
|
|
70
|
+
*/
|
|
71
|
+
export function findAnchor(anchors, pluginId, signerPublicKey, now) {
|
|
72
|
+
for (const a of anchors) {
|
|
73
|
+
if (a.publicKey !== signerPublicKey)
|
|
74
|
+
continue;
|
|
75
|
+
if (a.expiresAt && new Date(a.expiresAt).getTime() <= now)
|
|
76
|
+
continue;
|
|
77
|
+
if (!a.scope || a.scope === '*' || a.scope === pluginId)
|
|
78
|
+
return a;
|
|
79
|
+
if (a.scope.endsWith('*') && pluginId.startsWith(a.scope.slice(0, -1)))
|
|
80
|
+
return a;
|
|
81
|
+
}
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* `PluginIntegrityVerifier` — Stage-1 verifier (signature + trust anchor).
|
|
86
|
+
*
|
|
87
|
+
* Pure-ish: holds the trust-anchor list and clock, no other state. Safe to
|
|
88
|
+
* construct per-invocation or share.
|
|
89
|
+
*/
|
|
90
|
+
export class PluginIntegrityVerifier {
|
|
91
|
+
config;
|
|
92
|
+
constructor(config) {
|
|
93
|
+
this.config = config;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Verify a signed manifest. Returns the verification status; the caller
|
|
97
|
+
* decides what to do with `signature-missing` / `manifest-hash-mismatch`
|
|
98
|
+
* etc. based on its strict-mode policy.
|
|
99
|
+
*/
|
|
100
|
+
async verify(signed) {
|
|
101
|
+
const now = (this.config.now ?? Date.now)();
|
|
102
|
+
const pluginId = signed.manifest?.id ?? '<unknown>';
|
|
103
|
+
if (!signed.signature || !signed.publicKey) {
|
|
104
|
+
return { status: 'signature-missing', pluginId, ts: now };
|
|
105
|
+
}
|
|
106
|
+
// Re-hash the manifest and compare to the signed hash.
|
|
107
|
+
const recomputed = hashManifest(signed.manifest);
|
|
108
|
+
if (recomputed !== signed.manifestHash) {
|
|
109
|
+
return {
|
|
110
|
+
status: 'manifest-hash-mismatch',
|
|
111
|
+
pluginId,
|
|
112
|
+
signerFingerprint: fingerprint(signed.publicKey),
|
|
113
|
+
ts: now,
|
|
114
|
+
detail: `expected ${signed.manifestHash}, got ${recomputed}`,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// Find a trust anchor that vouches for this signer + plugin.
|
|
118
|
+
const anchor = findAnchor(this.config.trustAnchors.anchors, pluginId, signed.publicKey, now);
|
|
119
|
+
if (!anchor) {
|
|
120
|
+
return {
|
|
121
|
+
status: 'unknown-signer',
|
|
122
|
+
pluginId,
|
|
123
|
+
signerFingerprint: fingerprint(signed.publicKey),
|
|
124
|
+
ts: now,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// Ed25519 verify. Probe @noble/ed25519 (already a workspace dep); fall
|
|
128
|
+
// back to the failure status if it isn't resolvable in this environment
|
|
129
|
+
// (mirrors verify.mjs's #1880 precondition pattern).
|
|
130
|
+
const ed = await loadEd25519();
|
|
131
|
+
if (!ed) {
|
|
132
|
+
return {
|
|
133
|
+
status: 'signature-invalid',
|
|
134
|
+
pluginId,
|
|
135
|
+
signerFingerprint: fingerprint(signed.publicKey),
|
|
136
|
+
ts: now,
|
|
137
|
+
detail: '@noble/ed25519 not available — cannot verify',
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// Required by @noble/ed25519 v2 for sync sha512.
|
|
141
|
+
if (!ed.etc.sha512Sync) {
|
|
142
|
+
ed.etc.sha512Sync = (...m) => {
|
|
143
|
+
const h = nodeCrypto.createHash('sha512');
|
|
144
|
+
for (const x of m)
|
|
145
|
+
h.update(x);
|
|
146
|
+
return h.digest();
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
let ok = false;
|
|
150
|
+
try {
|
|
151
|
+
const sigBytes = hexToBytes(signed.signature);
|
|
152
|
+
const msgBytes = hexToBytes(signed.manifestHash);
|
|
153
|
+
const pubBytes = hexToBytes(signed.publicKey);
|
|
154
|
+
ok = await ed.verify(sigBytes, msgBytes, pubBytes);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
ok = false;
|
|
158
|
+
}
|
|
159
|
+
if (!ok) {
|
|
160
|
+
return {
|
|
161
|
+
status: 'signature-invalid',
|
|
162
|
+
pluginId,
|
|
163
|
+
signerFingerprint: fingerprint(signed.publicKey),
|
|
164
|
+
ts: now,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
return { status: 'pass', pluginId, signerFingerprint: fingerprint(signed.publicKey), ts: now };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// ─── helpers ────────────────────────────────────────────────────────────
|
|
171
|
+
function hexToBytes(hex) {
|
|
172
|
+
const out = new Uint8Array(hex.length >>> 1);
|
|
173
|
+
for (let i = 0; i < out.length; i++)
|
|
174
|
+
out[i] = parseInt(hex.substr(i * 2, 2), 16);
|
|
175
|
+
return out;
|
|
176
|
+
}
|
|
177
|
+
async function loadEd25519() {
|
|
178
|
+
try {
|
|
179
|
+
const mod = await import('@noble/ed25519');
|
|
180
|
+
return mod;
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=integrity-verifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"integrity-verifier.js","sourceRoot":"","sources":["../../src/plugins/integrity-verifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolOutputGuardrail — semantic screening of content returned by MCP tool
|
|
3
|
+
* calls, memory reads, and external API responses before that content enters
|
|
4
|
+
* agent reasoning. Closes the OWASP ASI01 (Agent Goal Hijacking) gap
|
|
5
|
+
* identified in ruvnet/ruflo#2149 / ADR-131.
|
|
6
|
+
*
|
|
7
|
+
* Threat model
|
|
8
|
+
* ------------
|
|
9
|
+
* Attackers embed malicious instructions in content the agent retrieves
|
|
10
|
+
* autonomously (web page, MCP tool response, memory entry). An LLM cannot
|
|
11
|
+
* reliably distinguish instructions from data once both are in the prompt.
|
|
12
|
+
* System-level per-boundary guardrails are the only category with
|
|
13
|
+
* sub-millisecond latency and no model dependency (arXiv:2601.17548,
|
|
14
|
+
* Jan 2026 systematic review).
|
|
15
|
+
*
|
|
16
|
+
* Scope
|
|
17
|
+
* -----
|
|
18
|
+
* - Detection only — does NOT modify the running prompt; callers decide
|
|
19
|
+
* what action to take (allow, flag, redact, reject) via `scanAndEnforce`.
|
|
20
|
+
* - Synchronous pattern match — designed for <1ms p99 on typical tool
|
|
21
|
+
* responses (≤32KB). Large content is capped at `maxScanBytes` and the
|
|
22
|
+
* truncation itself is reported as a low-severity finding.
|
|
23
|
+
* - Pure-function shape — no I/O, no async, no model calls. Safe to invoke
|
|
24
|
+
* in hot paths (every MCP tool result, every memory read).
|
|
25
|
+
*
|
|
26
|
+
* Non-goals
|
|
27
|
+
* ---------
|
|
28
|
+
* - Not a replacement for input validation at HTTP/CLI boundaries
|
|
29
|
+
* (InputValidator handles that). This is the *content* boundary.
|
|
30
|
+
* - Not a model-based classifier. False-positive rate is bounded by
|
|
31
|
+
* pattern specificity; tune via `customPatterns` and `policy`.
|
|
32
|
+
*
|
|
33
|
+
* Reference: ADR-131, OpenAI Agents SDK ToolGuardrail API (March 2025).
|
|
34
|
+
*/
|
|
35
|
+
export type InjectionSeverity = 'low' | 'medium' | 'high' | 'critical';
|
|
36
|
+
export type InjectionCategory = 'instruction-override' | 'role-hijack' | 'exfiltration' | 'jailbreak' | 'hidden-unicode' | 'embedded-system' | 'tool-spoofing' | 'truncation';
|
|
37
|
+
export interface InjectionFinding {
|
|
38
|
+
/** Short label for the matched pattern (stable identifier for telemetry). */
|
|
39
|
+
pattern: string;
|
|
40
|
+
/** Risk weight assigned to this pattern. */
|
|
41
|
+
severity: InjectionSeverity;
|
|
42
|
+
/** Category for downstream classification + OWASP mapping. */
|
|
43
|
+
category: InjectionCategory;
|
|
44
|
+
/** Char offset of the first match in the scanned content. */
|
|
45
|
+
position: number;
|
|
46
|
+
/** Up to 80 chars of surrounding context for human triage (redacted in `sanitized`). */
|
|
47
|
+
context: string;
|
|
48
|
+
}
|
|
49
|
+
export interface GuardrailResult {
|
|
50
|
+
/** True iff no findings, or all findings under the `flag` policy threshold. */
|
|
51
|
+
safe: boolean;
|
|
52
|
+
/** Findings in the order they were detected. */
|
|
53
|
+
findings: InjectionFinding[];
|
|
54
|
+
/** Highest severity observed; `none` if findings is empty. */
|
|
55
|
+
highest: InjectionSeverity | 'none';
|
|
56
|
+
}
|
|
57
|
+
export type GuardrailAction = 'allow' | 'flag' | 'redact' | 'reject';
|
|
58
|
+
export interface GuardrailConfig {
|
|
59
|
+
/**
|
|
60
|
+
* Per-severity action.
|
|
61
|
+
* Defaults:
|
|
62
|
+
* low → allow
|
|
63
|
+
* medium → flag
|
|
64
|
+
* high → redact
|
|
65
|
+
* critical → reject
|
|
66
|
+
*
|
|
67
|
+
* `allow` — content passes through unchanged, no logging required.
|
|
68
|
+
* `flag` — content passes through; caller SHOULD log + monitor.
|
|
69
|
+
* `redact` — matched substrings replaced with `[REDACTED:<pattern>]`.
|
|
70
|
+
* `reject` — caller MUST drop the content and treat the tool call
|
|
71
|
+
* as failed (signal the agent that the tool returned an
|
|
72
|
+
* unsafe payload rather than letting the payload through).
|
|
73
|
+
*/
|
|
74
|
+
policy?: Partial<Record<InjectionSeverity, GuardrailAction>>;
|
|
75
|
+
/** Add domain-specific patterns without subclassing. */
|
|
76
|
+
customPatterns?: Array<{
|
|
77
|
+
label: string;
|
|
78
|
+
regex: RegExp;
|
|
79
|
+
severity: InjectionSeverity;
|
|
80
|
+
category: InjectionCategory;
|
|
81
|
+
}>;
|
|
82
|
+
/**
|
|
83
|
+
* Maximum bytes of content to scan. Beyond this, the tail is ignored and
|
|
84
|
+
* a `truncation` finding is added at `medium`. Default: 1 MiB.
|
|
85
|
+
* Set to 0 to disable truncation (scan everything; slower on huge blobs).
|
|
86
|
+
*/
|
|
87
|
+
maxScanBytes?: number;
|
|
88
|
+
}
|
|
89
|
+
export declare class ToolOutputGuardrail {
|
|
90
|
+
private readonly patterns;
|
|
91
|
+
private readonly policy;
|
|
92
|
+
private readonly maxScanBytes;
|
|
93
|
+
constructor(config?: GuardrailConfig);
|
|
94
|
+
/**
|
|
95
|
+
* Pure scan — no side effects, no content modification. Useful when the
|
|
96
|
+
* caller wants to log findings but cannot drop the content (e.g. read-only
|
|
97
|
+
* audit mode).
|
|
98
|
+
*/
|
|
99
|
+
scan(content: string): GuardrailResult;
|
|
100
|
+
/**
|
|
101
|
+
* Scan + enforce policy. Returns the content to pass forward (possibly
|
|
102
|
+
* redacted or empty) plus the scan result and the action that was taken.
|
|
103
|
+
* Callers should treat `reject` as "drop the tool result and signal an
|
|
104
|
+
* error" — do NOT silently substitute empty content.
|
|
105
|
+
*/
|
|
106
|
+
scanAndEnforce(content: string): {
|
|
107
|
+
content: string;
|
|
108
|
+
result: GuardrailResult;
|
|
109
|
+
action: GuardrailAction;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Replace each non-truncation finding's matched substring with
|
|
113
|
+
* `[REDACTED:<pattern>]`. Truncation findings have no substring to redact
|
|
114
|
+
* (their `position` is a length, not an offset into the content) and are
|
|
115
|
+
* skipped. Idempotent for already-redacted strings.
|
|
116
|
+
*/
|
|
117
|
+
private redact;
|
|
118
|
+
}
|
|
119
|
+
/** Convenience factory that returns a guardrail with the default policy. */
|
|
120
|
+
export declare function createToolOutputGuardrail(config?: GuardrailConfig): ToolOutputGuardrail;
|
|
121
|
+
/**
|
|
122
|
+
* One-shot helper for callers that just want a yes/no answer without
|
|
123
|
+
* constructing a guardrail. Uses the default policy.
|
|
124
|
+
*/
|
|
125
|
+
export declare function isToolOutputSafe(content: string): boolean;
|
|
126
|
+
//# sourceMappingURL=tool-output-guardrail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-output-guardrail.d.ts","sourceRoot":"","sources":["../src/tool-output-guardrail.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;AAEvE,MAAM,MAAM,iBAAiB,GACzB,sBAAsB,GACtB,aAAa,GACb,cAAc,GACd,WAAW,GACX,gBAAgB,GAChB,iBAAiB,GACjB,eAAe,GACf,YAAY,CAAC;AAEjB,MAAM,WAAW,gBAAgB;IAC/B,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;IAChB,4CAA4C;IAC5C,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,8DAA8D;IAC9D,QAAQ,EAAE,iBAAiB,CAAC;IAC5B,6DAA6D;IAC7D,QAAQ,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,IAAI,EAAE,OAAO,CAAC;IACd,gDAAgD;IAChD,QAAQ,EAAE,gBAAgB,EAAE,CAAC;IAC7B,8DAA8D;IAC9D,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAAC;CACrC;AAED,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAErE,MAAM,WAAW,eAAe;IAC9B;;;;;;;;;;;;;;OAcG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,iBAAiB,EAAE,eAAe,CAAC,CAAC,CAAC;IAE7D,wDAAwD;IACxD,cAAc,CAAC,EAAE,KAAK,CAAC;QACrB,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,iBAAiB,CAAC;QAC5B,QAAQ,EAAE,iBAAiB,CAAC;KAC7B,CAAC,CAAC;IAEH;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAiID,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAiD;IAC1E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmD;IAC1E,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;gBAE1B,MAAM,GAAE,eAAoB;IAMxC;;;;OAIG;IACH,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe;IA+CtC;;;;;OAKG;IACH,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG;QAC/B,OAAO,EAAE,MAAM,CAAC;QAChB,MAAM,EAAE,eAAe,CAAC;QACxB,MAAM,EAAE,eAAe,CAAC;KACzB;IAgBD;;;;;OAKG;IACH,OAAO,CAAC,MAAM;CAmBf;AAED,4EAA4E;AAC5E,wBAAgB,yBAAyB,CAAC,MAAM,CAAC,EAAE,eAAe,GAAG,mBAAmB,CAEvF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzD"}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolOutputGuardrail — semantic screening of content returned by MCP tool
|
|
3
|
+
* calls, memory reads, and external API responses before that content enters
|
|
4
|
+
* agent reasoning. Closes the OWASP ASI01 (Agent Goal Hijacking) gap
|
|
5
|
+
* identified in ruvnet/ruflo#2149 / ADR-131.
|
|
6
|
+
*
|
|
7
|
+
* Threat model
|
|
8
|
+
* ------------
|
|
9
|
+
* Attackers embed malicious instructions in content the agent retrieves
|
|
10
|
+
* autonomously (web page, MCP tool response, memory entry). An LLM cannot
|
|
11
|
+
* reliably distinguish instructions from data once both are in the prompt.
|
|
12
|
+
* System-level per-boundary guardrails are the only category with
|
|
13
|
+
* sub-millisecond latency and no model dependency (arXiv:2601.17548,
|
|
14
|
+
* Jan 2026 systematic review).
|
|
15
|
+
*
|
|
16
|
+
* Scope
|
|
17
|
+
* -----
|
|
18
|
+
* - Detection only — does NOT modify the running prompt; callers decide
|
|
19
|
+
* what action to take (allow, flag, redact, reject) via `scanAndEnforce`.
|
|
20
|
+
* - Synchronous pattern match — designed for <1ms p99 on typical tool
|
|
21
|
+
* responses (≤32KB). Large content is capped at `maxScanBytes` and the
|
|
22
|
+
* truncation itself is reported as a low-severity finding.
|
|
23
|
+
* - Pure-function shape — no I/O, no async, no model calls. Safe to invoke
|
|
24
|
+
* in hot paths (every MCP tool result, every memory read).
|
|
25
|
+
*
|
|
26
|
+
* Non-goals
|
|
27
|
+
* ---------
|
|
28
|
+
* - Not a replacement for input validation at HTTP/CLI boundaries
|
|
29
|
+
* (InputValidator handles that). This is the *content* boundary.
|
|
30
|
+
* - Not a model-based classifier. False-positive rate is bounded by
|
|
31
|
+
* pattern specificity; tune via `customPatterns` and `policy`.
|
|
32
|
+
*
|
|
33
|
+
* Reference: ADR-131, OpenAI Agents SDK ToolGuardrail API (March 2025).
|
|
34
|
+
*/
|
|
35
|
+
const DEFAULT_POLICY = {
|
|
36
|
+
low: 'allow',
|
|
37
|
+
medium: 'flag',
|
|
38
|
+
high: 'redact',
|
|
39
|
+
critical: 'reject',
|
|
40
|
+
};
|
|
41
|
+
const DEFAULT_MAX_SCAN_BYTES = 1024 * 1024;
|
|
42
|
+
/**
|
|
43
|
+
* Built-in pattern library. Ordered by severity (critical first) so the
|
|
44
|
+
* `highest` field reflects the worst category. Patterns are intentionally
|
|
45
|
+
* conservative — they target the explicit instruction-override / role-hijack
|
|
46
|
+
* shapes that show up in published indirect-injection corpora rather than
|
|
47
|
+
* general "suspicious looking" text.
|
|
48
|
+
*/
|
|
49
|
+
const BUILTIN_PATTERNS = [
|
|
50
|
+
// ── critical: instruction override + embedded system frames ──
|
|
51
|
+
{
|
|
52
|
+
label: 'ignore-previous-instructions',
|
|
53
|
+
// Allow optional filler words between the verb and the temporal keyword
|
|
54
|
+
// ("ignore the above", "disregard your earlier", "forget all prior").
|
|
55
|
+
regex: /\b(?:ignore|disregard|forget)\s+(?:all\s+|any\s+|the\s+|my\s+|your\s+|these\s+|those\s+)?(?:previous|prior|above|earlier|preceding|aforementioned)\s+(?:instructions?|prompts?|rules?|directives?)\b/gi,
|
|
56
|
+
severity: 'critical',
|
|
57
|
+
category: 'instruction-override',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
label: 'chatml-frame',
|
|
61
|
+
regex: /<\|(?:im_start|im_end|system|assistant|user|endoftext)\|>/gi,
|
|
62
|
+
severity: 'critical',
|
|
63
|
+
category: 'embedded-system',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
label: 'llama-inst-frame',
|
|
67
|
+
regex: /\[\/?INST\]/g,
|
|
68
|
+
severity: 'critical',
|
|
69
|
+
category: 'embedded-system',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
label: 'exfiltrate-secret',
|
|
73
|
+
regex: /\b(?:exfiltrate|leak|send|post|upload|transmit)\b[^.\n]{0,80}\b(?:secret|token|api[-_\s]?keys?|password|credential|env\s+vars?)\b/gi,
|
|
74
|
+
severity: 'critical',
|
|
75
|
+
category: 'exfiltration',
|
|
76
|
+
},
|
|
77
|
+
// ── high: role hijack + jailbreak + system tags + bidi unicode ──
|
|
78
|
+
{
|
|
79
|
+
label: 'system-tag-injection',
|
|
80
|
+
regex: /<\/?system>/gi,
|
|
81
|
+
severity: 'high',
|
|
82
|
+
category: 'embedded-system',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
label: 'role-hijack-you-are-now',
|
|
86
|
+
regex: /\byou\s+are\s+(?:now|actually|secretly)\s+(?:a|an|the)\s+/gi,
|
|
87
|
+
severity: 'high',
|
|
88
|
+
category: 'role-hijack',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
label: 'jailbreak-dan',
|
|
92
|
+
regex: /\b(?:DAN(?:\s+mode)?|developer\s+mode|jailbreak\s+mode|do\s+anything\s+now)\b/gi,
|
|
93
|
+
severity: 'high',
|
|
94
|
+
category: 'jailbreak',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
label: 'new-instructions-directive',
|
|
98
|
+
regex: /\b(?:new|updated|revised|additional)\s+(?:instructions?|task|directives?|objectives?)\s*[:.]/gi,
|
|
99
|
+
severity: 'high',
|
|
100
|
+
category: 'instruction-override',
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
label: 'bidi-override',
|
|
104
|
+
regex: /[--]/g,
|
|
105
|
+
severity: 'high',
|
|
106
|
+
category: 'hidden-unicode',
|
|
107
|
+
},
|
|
108
|
+
// ── medium: softer role manipulation + tool spoofing ──
|
|
109
|
+
{
|
|
110
|
+
label: 'role-hijack-act-as',
|
|
111
|
+
regex: /\b(?:act|behave|pretend|role[-\s]?play)\s+as\s+(?:if\s+)?(?:a|an|the)\s+/gi,
|
|
112
|
+
severity: 'medium',
|
|
113
|
+
category: 'role-hijack',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
label: 'tool-call-spoof',
|
|
117
|
+
regex: /\b(?:tool_call|function_call|mcp_tool)\s*[:=]\s*["'{]/gi,
|
|
118
|
+
severity: 'medium',
|
|
119
|
+
category: 'tool-spoofing',
|
|
120
|
+
},
|
|
121
|
+
// ── low: zero-width whitespace (common in copy-paste attacks) ──
|
|
122
|
+
{
|
|
123
|
+
label: 'zero-width-char',
|
|
124
|
+
regex: /[-]/g,
|
|
125
|
+
severity: 'low',
|
|
126
|
+
category: 'hidden-unicode',
|
|
127
|
+
},
|
|
128
|
+
];
|
|
129
|
+
const SEVERITY_ORDER = {
|
|
130
|
+
none: 0,
|
|
131
|
+
low: 1,
|
|
132
|
+
medium: 2,
|
|
133
|
+
high: 3,
|
|
134
|
+
critical: 4,
|
|
135
|
+
};
|
|
136
|
+
function snippet(text, idx, len) {
|
|
137
|
+
const start = Math.max(0, idx - 24);
|
|
138
|
+
const end = Math.min(text.length, idx + len + 24);
|
|
139
|
+
const head = start > 0 ? '…' : '';
|
|
140
|
+
const tail = end < text.length ? '…' : '';
|
|
141
|
+
return head + text.slice(start, end).replace(/\s+/g, ' ') + tail;
|
|
142
|
+
}
|
|
143
|
+
function maxSeverity(a, b) {
|
|
144
|
+
return SEVERITY_ORDER[a] >= SEVERITY_ORDER[b]
|
|
145
|
+
? (a === 'none' ? b : a)
|
|
146
|
+
: b;
|
|
147
|
+
}
|
|
148
|
+
export class ToolOutputGuardrail {
|
|
149
|
+
patterns;
|
|
150
|
+
policy;
|
|
151
|
+
maxScanBytes;
|
|
152
|
+
constructor(config = {}) {
|
|
153
|
+
this.patterns = [...BUILTIN_PATTERNS, ...(config.customPatterns ?? [])];
|
|
154
|
+
this.policy = { ...DEFAULT_POLICY, ...(config.policy ?? {}) };
|
|
155
|
+
this.maxScanBytes = config.maxScanBytes ?? DEFAULT_MAX_SCAN_BYTES;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Pure scan — no side effects, no content modification. Useful when the
|
|
159
|
+
* caller wants to log findings but cannot drop the content (e.g. read-only
|
|
160
|
+
* audit mode).
|
|
161
|
+
*/
|
|
162
|
+
scan(content) {
|
|
163
|
+
if (typeof content !== 'string') {
|
|
164
|
+
return { safe: true, findings: [], highest: 'none' };
|
|
165
|
+
}
|
|
166
|
+
let scanned = content;
|
|
167
|
+
const findings = [];
|
|
168
|
+
if (this.maxScanBytes > 0 && content.length > this.maxScanBytes) {
|
|
169
|
+
scanned = content.slice(0, this.maxScanBytes);
|
|
170
|
+
findings.push({
|
|
171
|
+
pattern: 'content-truncated',
|
|
172
|
+
severity: 'medium',
|
|
173
|
+
category: 'truncation',
|
|
174
|
+
position: this.maxScanBytes,
|
|
175
|
+
context: `(content truncated at ${this.maxScanBytes} bytes; ${content.length - this.maxScanBytes} bytes unscanned)`,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
for (const { label, regex, severity, category } of this.patterns) {
|
|
179
|
+
// Clone the RegExp so that the global-flag lastIndex state doesn't
|
|
180
|
+
// leak between calls. Built-in patterns are immutable; user-supplied
|
|
181
|
+
// patterns are also defensively cloned.
|
|
182
|
+
const re = new RegExp(regex.source, regex.flags.includes('g') ? regex.flags : regex.flags + 'g');
|
|
183
|
+
let match;
|
|
184
|
+
while ((match = re.exec(scanned)) !== null) {
|
|
185
|
+
findings.push({
|
|
186
|
+
pattern: label,
|
|
187
|
+
severity,
|
|
188
|
+
category,
|
|
189
|
+
position: match.index,
|
|
190
|
+
context: snippet(scanned, match.index, match[0].length),
|
|
191
|
+
});
|
|
192
|
+
// Defensive: zero-length match would loop forever
|
|
193
|
+
if (match[0].length === 0)
|
|
194
|
+
re.lastIndex++;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
let highest = 'none';
|
|
198
|
+
for (const f of findings)
|
|
199
|
+
highest = maxSeverity(highest, f.severity);
|
|
200
|
+
const action = highest === 'none' ? 'allow' : this.policy[highest];
|
|
201
|
+
const safe = action === 'allow' || action === 'flag';
|
|
202
|
+
return { safe, findings, highest };
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Scan + enforce policy. Returns the content to pass forward (possibly
|
|
206
|
+
* redacted or empty) plus the scan result and the action that was taken.
|
|
207
|
+
* Callers should treat `reject` as "drop the tool result and signal an
|
|
208
|
+
* error" — do NOT silently substitute empty content.
|
|
209
|
+
*/
|
|
210
|
+
scanAndEnforce(content) {
|
|
211
|
+
const result = this.scan(content);
|
|
212
|
+
const action = result.highest === 'none' ? 'allow' : this.policy[result.highest];
|
|
213
|
+
let outgoing = content;
|
|
214
|
+
if (action === 'redact') {
|
|
215
|
+
outgoing = this.redact(content, result.findings);
|
|
216
|
+
}
|
|
217
|
+
else if (action === 'reject') {
|
|
218
|
+
outgoing = '';
|
|
219
|
+
}
|
|
220
|
+
return { content: outgoing, result, action };
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Replace each non-truncation finding's matched substring with
|
|
224
|
+
* `[REDACTED:<pattern>]`. Truncation findings have no substring to redact
|
|
225
|
+
* (their `position` is a length, not an offset into the content) and are
|
|
226
|
+
* skipped. Idempotent for already-redacted strings.
|
|
227
|
+
*/
|
|
228
|
+
redact(content, findings) {
|
|
229
|
+
if (findings.length === 0)
|
|
230
|
+
return content;
|
|
231
|
+
// Build a single combined regex per pattern label to avoid replacing
|
|
232
|
+
// overlapping matches multiple times.
|
|
233
|
+
const labels = new Set(findings
|
|
234
|
+
.filter((f) => f.category !== 'truncation')
|
|
235
|
+
.map((f) => f.pattern));
|
|
236
|
+
let out = content;
|
|
237
|
+
for (const { label, regex } of this.patterns) {
|
|
238
|
+
if (!labels.has(label))
|
|
239
|
+
continue;
|
|
240
|
+
const re = new RegExp(regex.source, regex.flags.includes('g') ? regex.flags : regex.flags + 'g');
|
|
241
|
+
out = out.replace(re, `[REDACTED:${label}]`);
|
|
242
|
+
}
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/** Convenience factory that returns a guardrail with the default policy. */
|
|
247
|
+
export function createToolOutputGuardrail(config) {
|
|
248
|
+
return new ToolOutputGuardrail(config);
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* One-shot helper for callers that just want a yes/no answer without
|
|
252
|
+
* constructing a guardrail. Uses the default policy.
|
|
253
|
+
*/
|
|
254
|
+
export function isToolOutputSafe(content) {
|
|
255
|
+
return createToolOutputGuardrail().scan(content).safe;
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=tool-output-guardrail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-output-guardrail.js","sourceRoot":"","sources":["../src/tool-output-guardrail.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/ruvector.db
CHANGED
|
Binary file
|