theorum 1.0.0 → 1.1.3
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 +81 -25
- package/esm/mod.d.ts +48 -29
- package/esm/mod.js +37 -24
- package/esm/src/cli/commands/bench.js +13 -16
- package/esm/src/cli/commands/fuzz-canary.js +25 -29
- package/esm/src/cli/commands/guardrails-eval.d.ts +14 -0
- package/esm/src/cli/commands/guardrails-eval.js +15 -0
- package/esm/src/cli/commands/profile.js +12 -3
- package/esm/src/cli/commands/run.js +8 -9
- package/esm/src/cli/commands/test.js +8 -8
- package/esm/src/cli/event-log.d.ts +1 -1
- package/esm/src/cli/index.js +12 -0
- package/esm/src/cli/matrix/synthesizer.d.ts +6 -6
- package/esm/src/cli/matrix/synthesizer.js +23 -22
- package/esm/src/guardrails/canary-gate.d.ts +2 -1
- package/esm/src/guardrails/canary-gate.js +2 -1
- package/esm/src/guardrails/canary.d.ts +9 -1
- package/esm/src/guardrails/canary.js +21 -10
- package/esm/src/guardrails/corpus/canary-egress-attacks.d.ts +1 -0
- package/esm/src/guardrails/corpus/fuzz-inbound.js +12 -15
- package/esm/src/guardrails/corpus/inbound-payloads.js +1 -0
- package/esm/src/guardrails/corpus/live-attacks.js +0 -1
- package/esm/src/guardrails/corpus/mod.d.ts +1 -0
- package/esm/src/guardrails/corpus/secrets.d.ts +1 -0
- package/esm/src/guardrails/corpus/secrets.js +1 -0
- package/esm/src/guardrails/corpus/strings.d.ts +1 -0
- package/esm/src/guardrails/corpus/strings.js +1 -0
- package/esm/src/guardrails/corpus/types.d.ts +1 -0
- package/esm/src/guardrails/egress.d.ts +26 -3
- package/esm/src/guardrails/egress.js +69 -18
- package/esm/src/guardrails/error.d.ts +14 -23
- package/esm/src/guardrails/error.js +86 -77
- package/esm/src/guardrails/eval/corpus.d.ts +108 -0
- package/esm/src/guardrails/eval/corpus.js +978 -0
- package/esm/src/guardrails/eval/mod.d.ts +51 -0
- package/esm/src/guardrails/eval/mod.js +133 -0
- package/esm/src/guardrails/eval/score.d.ts +66 -0
- package/esm/src/guardrails/eval/score.js +114 -0
- package/esm/src/guardrails/events.d.ts +25 -0
- package/esm/src/guardrails/events.js +56 -0
- package/esm/src/guardrails/hits.d.ts +24 -0
- package/esm/src/guardrails/hits.js +45 -0
- package/esm/src/guardrails/lexicon.d.ts +39 -0
- package/esm/src/guardrails/lexicon.js +200 -0
- package/esm/src/guardrails/live-outbound-gate.d.ts +15 -13
- package/esm/src/guardrails/live-outbound-gate.js +131 -89
- package/esm/src/guardrails/mod.d.ts +19 -4
- package/esm/src/guardrails/mod.js +12 -3
- package/esm/src/guardrails/network.d.ts +19 -0
- package/esm/src/guardrails/network.js +234 -0
- package/esm/src/guardrails/policy.d.ts +35 -0
- package/esm/src/guardrails/policy.js +50 -0
- package/esm/src/guardrails/progressive-yield.d.ts +51 -0
- package/esm/src/guardrails/progressive-yield.js +98 -0
- package/esm/src/guardrails/quota.d.ts +17 -3
- package/esm/src/guardrails/quota.js +18 -4
- package/esm/src/guardrails/sanitize.d.ts +36 -6
- package/esm/src/guardrails/sanitize.js +152 -60
- package/esm/src/guardrails/serialize.d.ts +35 -0
- package/esm/src/guardrails/serialize.js +58 -0
- package/esm/src/guardrails/testing.d.ts +7 -0
- package/esm/src/guardrails/testing.js +4 -0
- package/esm/src/guardrails/theorum-error.d.ts +12 -0
- package/esm/src/guardrails/theorum-error.js +15 -0
- package/esm/src/guardrails/tool-directives.d.ts +48 -0
- package/esm/src/guardrails/tool-directives.js +124 -0
- package/esm/src/guardrails/tool-result.d.ts +93 -0
- package/esm/src/guardrails/tool-result.js +276 -0
- package/esm/src/guardrails/types.d.ts +291 -0
- package/esm/src/guardrails/types.js +72 -0
- package/esm/src/host/client-turn.js +4 -0
- package/esm/src/host/mint-trace.d.ts +1 -1
- package/esm/src/kernel/auth/crypto.d.ts +42 -0
- package/esm/src/kernel/auth/crypto.js +106 -0
- package/esm/src/kernel/auth/mod.d.ts +11 -0
- package/esm/src/kernel/auth/mod.js +11 -0
- package/esm/src/kernel/auth/oauth.d.ts +47 -0
- package/esm/src/kernel/auth/oauth.js +278 -0
- package/esm/src/kernel/auth/types.d.ts +133 -0
- package/esm/src/kernel/auth/types.js +13 -0
- package/esm/src/kernel/engine/delta.js +43 -2
- package/esm/src/kernel/engine/live-inbound.d.ts +15 -3
- package/esm/src/kernel/engine/live-inbound.js +22 -8
- package/esm/src/kernel/engine/live-ingress.d.ts +19 -0
- package/esm/src/kernel/engine/live-ingress.js +47 -0
- package/esm/src/kernel/engine/repair.js +13 -12
- package/esm/src/kernel/engine/runner/gates.js +127 -40
- package/esm/src/kernel/engine/runner/mod.d.ts +6 -4
- package/esm/src/kernel/engine/runner/mod.js +182 -43
- package/esm/src/kernel/engine/runner/schema-validation.js +3 -3
- package/esm/src/kernel/engine/runner/stages.d.ts +39 -0
- package/esm/src/kernel/engine/runner/stages.js +89 -0
- package/esm/src/kernel/engine/runner/state.d.ts +24 -0
- package/esm/src/kernel/engine/runner/steps.js +128 -61
- package/esm/src/kernel/engine/runner/stream.d.ts +9 -2
- package/esm/src/kernel/engine/runner/stream.js +114 -55
- package/esm/src/kernel/engine/session/mod.d.ts +3 -1
- package/esm/src/kernel/engine/session/mod.js +386 -34
- package/esm/src/kernel/interaction-parts.d.ts +14 -0
- package/esm/src/kernel/interaction-parts.js +23 -0
- package/esm/src/kernel/mod.d.ts +19 -7
- package/esm/src/kernel/mod.js +10 -5
- package/esm/src/kernel/profile-graph.d.ts +159 -0
- package/esm/src/kernel/profile-graph.js +156 -0
- package/esm/src/kernel/registry/attachments.d.ts +10 -10
- package/esm/src/kernel/registry/attachments.js +29 -26
- package/esm/src/kernel/registry/catalog.d.ts +25 -11
- package/esm/src/kernel/registry/catalog.js +59 -24
- package/esm/src/kernel/registry/ingress.js +34 -22
- package/esm/src/kernel/registry/profile-outputs.d.ts +4 -0
- package/esm/src/kernel/registry/profile-outputs.js +8 -0
- package/esm/src/kernel/registry/profiles.d.ts +34 -14
- package/esm/src/kernel/registry/profiles.js +306 -59
- package/esm/src/kernel/registry/provider-request.js +2 -0
- package/esm/src/kernel/registry/resolve.d.ts +7 -6
- package/esm/src/kernel/registry/resolve.js +102 -117
- package/esm/src/kernel/registry/schemas.js +1 -1
- package/esm/src/kernel/registry/sole-model.d.ts +8 -0
- package/esm/src/kernel/registry/sole-model.js +10 -0
- package/esm/src/kernel/registry/system-prompt.d.ts +10 -0
- package/esm/src/kernel/registry/system-prompt.js +40 -0
- package/esm/src/kernel/registry/system-role.d.ts +8 -0
- package/esm/src/kernel/registry/system-role.js +14 -0
- package/esm/src/kernel/registry/vault.d.ts +2 -2
- package/esm/src/kernel/registry/vault.js +4 -4
- package/esm/src/kernel/schema.d.ts +97 -11
- package/esm/src/kernel/schema.js +256 -111
- package/esm/src/kernel/stages.d.ts +175 -0
- package/esm/src/kernel/stages.js +476 -0
- package/esm/src/kernel/stop.d.ts +65 -11
- package/esm/src/kernel/stop.js +46 -11
- package/esm/src/kernel/tools/events.d.ts +41 -0
- package/esm/src/kernel/tools/events.js +71 -0
- package/esm/src/kernel/tools/execute.d.ts +63 -24
- package/esm/src/kernel/tools/execute.js +475 -224
- package/esm/src/kernel/tools/harness.js +16 -14
- package/esm/src/kernel/tools/invoke.js +26 -8
- package/esm/src/kernel/tools/mod.d.ts +3 -1
- package/esm/src/kernel/tools/mod.js +2 -1
- package/esm/src/kernel/tools/permission.d.ts +15 -0
- package/esm/src/kernel/tools/permission.js +47 -0
- package/esm/src/kernel/tools/project.js +5 -3
- package/esm/src/kernel/tools/registry.js +20 -10
- package/esm/src/kernel/tools/remote.d.ts +94 -0
- package/esm/src/kernel/tools/remote.js +577 -0
- package/esm/src/kernel/tools/resolve.d.ts +17 -7
- package/esm/src/kernel/tools/resolve.js +38 -17
- package/esm/src/kernel/tools/schema.d.ts +2 -0
- package/esm/src/kernel/tools/schema.js +23 -6
- package/esm/src/kernel/tools/stage-run.d.ts +105 -0
- package/esm/src/kernel/tools/stage-run.js +155 -0
- package/esm/src/kernel/tools/types.d.ts +198 -20
- package/esm/src/kernel/types.d.ts +253 -114
- package/esm/src/kernel/util/find-last.d.ts +2 -0
- package/esm/src/kernel/util/find-last.js +10 -0
- package/esm/src/observability/destinations.d.ts +31 -0
- package/esm/src/observability/destinations.js +67 -0
- package/esm/src/observability/mod.d.ts +10 -3
- package/esm/src/observability/mod.js +6 -2
- package/esm/src/observability/policy.d.ts +27 -0
- package/esm/src/observability/policy.js +80 -0
- package/esm/src/observability/resolve-policy.d.ts +16 -0
- package/esm/src/observability/resolve-policy.js +64 -0
- package/esm/src/observability/trace-attach.d.ts +3 -1
- package/esm/src/observability/trace-attach.js +17 -10
- package/esm/src/observability/trace-record.d.ts +12 -4
- package/esm/src/observability/trace-record.js +63 -19
- package/esm/src/observability/trace-sink.d.ts +19 -0
- package/esm/src/observability/trace-sink.js +10 -0
- package/esm/src/observability/trace.d.ts +14 -11
- package/esm/src/observability/trace.js +23 -14
- package/esm/src/observability/types.d.ts +113 -0
- package/esm/src/observability/types.js +11 -0
- package/esm/src/providers/create-provider.d.ts +7 -4
- package/esm/src/providers/create-provider.js +21 -7
- package/esm/src/providers/google/interactions/framing.d.ts +8 -0
- package/esm/src/providers/google/interactions/framing.js +74 -13
- package/esm/src/providers/google/live/framing.d.ts +5 -2
- package/esm/src/providers/google/live/framing.js +49 -12
- package/esm/src/providers/google/live/openapi-schema.js +3 -2
- package/esm/src/providers/google/live/stream.d.ts +9 -0
- package/esm/src/providers/google/live/stream.js +16 -3
- package/esm/src/providers/openrouter/cache-control.d.ts +24 -0
- package/esm/src/providers/openrouter/cache-control.js +23 -0
- package/esm/src/providers/openrouter/chat.d.ts +21 -1
- package/esm/src/providers/openrouter/chat.js +51 -6
- package/esm/src/providers/openrouter/image.js +8 -19
- package/esm/src/providers/openrouter/openai/chat-payload.js +23 -1
- package/esm/src/providers/openrouter/openai/compat.d.ts +7 -1
- package/esm/src/providers/openrouter/openai/compat.js +23 -4
- package/esm/src/providers/openrouter/openai/image-payload.js +8 -2
- package/esm/src/providers/openrouter/openai/sdk-messages.d.ts +1 -1
- package/esm/src/providers/openrouter/openai/sdk-messages.js +32 -5
- package/package.json +1 -1
- package/esm/src/interface/blocks.d.ts +0 -20
- package/esm/src/interface/blocks.js +0 -180
- package/esm/src/interface/from-profile.d.ts +0 -14
- package/esm/src/interface/from-profile.js +0 -104
- package/esm/src/interface/inputs.d.ts +0 -8
- package/esm/src/interface/inputs.js +0 -127
- package/esm/src/interface/mod.d.ts +0 -10
- package/esm/src/interface/mod.js +0 -9
- package/esm/src/interface/types.d.ts +0 -153
- package/esm/src/interface/types.js +0 -13
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool boundary guardrails — the surface where untrusted bytes re-enter the
|
|
3
|
+
* model's context carrying the model's own authority.
|
|
4
|
+
*
|
|
5
|
+
* A tool result is not user text: the model asked for it, so it arrives looking
|
|
6
|
+
* like something the turn already trusts. Remote HTTP and MCP servers control
|
|
7
|
+
* their own response bodies (including their error strings), and a delegated
|
|
8
|
+
* agent answers in prose that reads as authoritative. Everything crossing this
|
|
9
|
+
* boundary is therefore fenced, detected, and labelled with where it came from.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import type { AdvisoryLevel, GuardrailEvent, GuardrailHit, Provenance, ResolvedGuardrailPolicy, ToolOrigin, TurnTaint, Verdict } from './types.js';
|
|
14
|
+
declare const TOOL_CLOSE = "</tool_data>";
|
|
15
|
+
/** True when a result's bytes came from outside the host's own code. */
|
|
16
|
+
declare function isRemoteOrigin(origin: ToolOrigin): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Wrap tool output so the model reads it as data and can see where it came from.
|
|
19
|
+
*
|
|
20
|
+
* The origin is on the tag rather than in prose so a result cannot claim a
|
|
21
|
+
* friendlier provenance than it has by writing one into its own body.
|
|
22
|
+
*/
|
|
23
|
+
declare function wrapToolData(text: string, provenance: Provenance, advisory?: AdvisoryLevel, guidance?: string): string;
|
|
24
|
+
export interface GuardedToolText {
|
|
25
|
+
/** Text to hand the model, fenced and redacted. */
|
|
26
|
+
text: string;
|
|
27
|
+
/** Emitted when the guard did anything worth recording. */
|
|
28
|
+
event?: GuardrailEvent;
|
|
29
|
+
/**
|
|
30
|
+
* Directive signals found in the content.
|
|
31
|
+
*
|
|
32
|
+
* Reported, never redacted: legitimate tool output is frequently
|
|
33
|
+
* instruction-shaped, so rewriting on this signal would corrupt real data.
|
|
34
|
+
* These raise the turn's taint instead.
|
|
35
|
+
*/
|
|
36
|
+
suspicious?: GuardrailHit[];
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Compose the model-facing text for a tool result.
|
|
40
|
+
*
|
|
41
|
+
* `finding` is the tool's summary and `data` its structured payload; both reach
|
|
42
|
+
* the model, so both are guarded together rather than only the prose half.
|
|
43
|
+
*/
|
|
44
|
+
declare function composeToolText(finding: string, data: unknown): string;
|
|
45
|
+
/**
|
|
46
|
+
* Guard one tool result on its way into the model's context.
|
|
47
|
+
*
|
|
48
|
+
* Local host tools are still detected — a host tool reading a database returns
|
|
49
|
+
* data the host did not write — but only remote origins are fenced, because
|
|
50
|
+
* fencing a local tool's output would change prompts hosts have already tuned.
|
|
51
|
+
*/
|
|
52
|
+
declare function guardToolResult(finding: string, data: unknown, provenance: Provenance, policy: ResolvedGuardrailPolicy, callableTools?: readonly string[]): GuardedToolText;
|
|
53
|
+
/**
|
|
54
|
+
* Guard a tool failure message.
|
|
55
|
+
*
|
|
56
|
+
* A remote server authors its own error strings, so an unguarded failure message
|
|
57
|
+
* is the cleanest injection path across this boundary: it reaches the model
|
|
58
|
+
* verbatim and is framed by the kernel as a system report.
|
|
59
|
+
*/
|
|
60
|
+
declare function guardToolFailureText(message: string, provenance: Provenance, policy: ResolvedGuardrailPolicy): GuardedToolText;
|
|
61
|
+
/**
|
|
62
|
+
* Inspect model-supplied tool arguments before the call runs.
|
|
63
|
+
*
|
|
64
|
+
* Arguments are model-authored, so the risk is not instruction smuggling but
|
|
65
|
+
* exfiltration: a credential lifted from context and posted outward as a
|
|
66
|
+
* parameter. Detection reports rather than rewrites — silently altering a tool
|
|
67
|
+
* argument would make the call succeed against something the model did not ask
|
|
68
|
+
* for.
|
|
69
|
+
*/
|
|
70
|
+
declare function inspectToolArguments(args: unknown, policy: ResolvedGuardrailPolicy): Verdict;
|
|
71
|
+
/** Guardrail event for a flagged tool call, for the runner to emit. */
|
|
72
|
+
declare function toolCallEvent(verdict: Verdict, provenance: Provenance): GuardrailEvent | undefined;
|
|
73
|
+
/**
|
|
74
|
+
* Record a tool result against the turn's taint.
|
|
75
|
+
*
|
|
76
|
+
* Only remote origins taint: a local host tool returns bytes the host's own code
|
|
77
|
+
* produced, and treating those as attacker-influenceable would make the gate
|
|
78
|
+
* useless in practice.
|
|
79
|
+
*/
|
|
80
|
+
declare function recordTaint(taint: TurnTaint | undefined, provenance: Provenance, suspicious?: GuardrailHit[]): TurnTaint;
|
|
81
|
+
/** True when the turn has already read attacker-influenceable content. */
|
|
82
|
+
declare function isTainted(taint: TurnTaint | undefined): boolean;
|
|
83
|
+
/** True when remote content this turn read looked like it was steering the agent. */
|
|
84
|
+
declare function isSuspicious(taint: TurnTaint | undefined): boolean;
|
|
85
|
+
/**
|
|
86
|
+
* Decide whether a tool call may proceed given what the turn has already read.
|
|
87
|
+
*
|
|
88
|
+
* A `flag` says the call is happening on a tainted turn and is worth recording; a
|
|
89
|
+
* `block` says the profile asked for it to be refused. Reporting happens whether
|
|
90
|
+
* or not enforcement is configured, so the risk is visible before a host opts in.
|
|
91
|
+
*/
|
|
92
|
+
declare function checkTaintGate(taint: TurnTaint | undefined, access: string, policy: ResolvedGuardrailPolicy): Verdict;
|
|
93
|
+
export { checkTaintGate, composeToolText, guardToolFailureText, guardToolResult, inspectToolArguments, isRemoteOrigin, isSuspicious, isTainted, recordTaint, TOOL_CLOSE, toolCallEvent, wrapToolData, };
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool boundary guardrails — the surface where untrusted bytes re-enter the
|
|
3
|
+
* model's context carrying the model's own authority.
|
|
4
|
+
*
|
|
5
|
+
* A tool result is not user text: the model asked for it, so it arrives looking
|
|
6
|
+
* like something the turn already trusts. Remote HTTP and MCP servers control
|
|
7
|
+
* their own response bodies (including their error strings), and a delegated
|
|
8
|
+
* agent answers in prose that reads as authoritative. Everything crossing this
|
|
9
|
+
* boundary is therefore fenced, detected, and labelled with where it came from.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import { lexiconText } from './lexicon.js';
|
|
14
|
+
import { detectionForTrust } from './policy.js';
|
|
15
|
+
import { sanitizeText } from './sanitize.js';
|
|
16
|
+
import { textForScan } from './serialize.js';
|
|
17
|
+
import { advisoryLevel, directiveHits } from './tool-directives.js';
|
|
18
|
+
const TOOL_CLOSE = '</tool_data>';
|
|
19
|
+
const TOOL_OPEN = '<tool_data';
|
|
20
|
+
/** Origins whose bytes the host does not author and cannot vouch for. */
|
|
21
|
+
const REMOTE_ORIGINS = new Set(['http', 'mcp', 'delegated']);
|
|
22
|
+
/** True when a result's bytes came from outside the host's own code. */
|
|
23
|
+
function isRemoteOrigin(origin) {
|
|
24
|
+
return REMOTE_ORIGINS.has(origin);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Strip fence markers a tool result tried to forge before wrapping it.
|
|
28
|
+
* Linear scan — avoids polynomial regex on forged `<tool_data…>` runs.
|
|
29
|
+
*/
|
|
30
|
+
function stripToolFences(text) {
|
|
31
|
+
const lower = text.toLowerCase();
|
|
32
|
+
let out = '';
|
|
33
|
+
let i = 0;
|
|
34
|
+
while (i < text.length) {
|
|
35
|
+
const openAt = lower.indexOf(TOOL_OPEN, i);
|
|
36
|
+
const closeAt = lower.indexOf(TOOL_CLOSE, i);
|
|
37
|
+
let next = -1;
|
|
38
|
+
let kind = null;
|
|
39
|
+
if (openAt >= 0 && (closeAt < 0 || openAt <= closeAt)) {
|
|
40
|
+
next = openAt;
|
|
41
|
+
kind = 'open';
|
|
42
|
+
}
|
|
43
|
+
else if (closeAt >= 0) {
|
|
44
|
+
next = closeAt;
|
|
45
|
+
kind = 'close';
|
|
46
|
+
}
|
|
47
|
+
if (next < 0 || kind === null) {
|
|
48
|
+
out += text.slice(i);
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
out += text.slice(i, next);
|
|
52
|
+
if (kind === 'close') {
|
|
53
|
+
i = next + TOOL_CLOSE.length;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const gt = text.indexOf('>', next + TOOL_OPEN.length);
|
|
57
|
+
if (gt < 0) {
|
|
58
|
+
out += text.slice(next);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
i = gt + 1;
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Wrap tool output so the model reads it as data and can see where it came from.
|
|
67
|
+
*
|
|
68
|
+
* The origin is on the tag rather than in prose so a result cannot claim a
|
|
69
|
+
* friendlier provenance than it has by writing one into its own body.
|
|
70
|
+
*/
|
|
71
|
+
function wrapToolData(text, provenance, advisory = 'none', guidance) {
|
|
72
|
+
const attrs = `tool="${provenance.tool}" origin="${provenance.origin}"` +
|
|
73
|
+
(advisory === 'none' ? '' : ` advisory="${advisory}"`);
|
|
74
|
+
const notice = advisory === 'none' ? '' : `${advisoryNotice(advisory)}${guidance ? ` ${guidance}` : ''}\n`;
|
|
75
|
+
return `<${'tool_data'} ${attrs}>\n${notice}${stripToolFences(text)}\n${TOOL_CLOSE}`;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The kernel's own statement of what it observed.
|
|
79
|
+
*
|
|
80
|
+
* Deliberately an observation, not an instruction: what the agent should do about
|
|
81
|
+
* it is product behaviour, supplied by the host as `advisoryGuidance`. Emitted
|
|
82
|
+
* only when signals fired, so it stays rare enough to carry weight — a warning on
|
|
83
|
+
* every fetch is one the model learns to skip.
|
|
84
|
+
*/
|
|
85
|
+
function advisoryNotice(advisory) {
|
|
86
|
+
return lexiconText(advisory === 'high' ? 'advisory.notice_high' : 'advisory.notice_elevated');
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Compose the model-facing text for a tool result.
|
|
90
|
+
*
|
|
91
|
+
* `finding` is the tool's summary and `data` its structured payload; both reach
|
|
92
|
+
* the model, so both are guarded together rather than only the prose half.
|
|
93
|
+
*/
|
|
94
|
+
function composeToolText(finding, data) {
|
|
95
|
+
if (data === undefined) {
|
|
96
|
+
return finding;
|
|
97
|
+
}
|
|
98
|
+
const rendered = textForScan(data);
|
|
99
|
+
if (rendered.unscannable) {
|
|
100
|
+
return finding;
|
|
101
|
+
}
|
|
102
|
+
return `${finding}\n${rendered.text}`;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Guard one tool result on its way into the model's context.
|
|
106
|
+
*
|
|
107
|
+
* Local host tools are still detected — a host tool reading a database returns
|
|
108
|
+
* data the host did not write — but only remote origins are fenced, because
|
|
109
|
+
* fencing a local tool's output would change prompts hosts have already tuned.
|
|
110
|
+
*/
|
|
111
|
+
function guardToolResult(finding, data, provenance, policy, callableTools = []) {
|
|
112
|
+
const composed = composeToolText(finding, data);
|
|
113
|
+
const options = detectionForTrust(policy, 'untrusted');
|
|
114
|
+
const redacted = sanitizeText(composed, options);
|
|
115
|
+
const changed = redacted !== composed;
|
|
116
|
+
// Directive detection runs on remote content only: a local tool's output is
|
|
117
|
+
// bytes the host's own code produced.
|
|
118
|
+
const remote = isRemoteOrigin(provenance.origin);
|
|
119
|
+
const suspicious = remote ? directiveHits(composed, callableTools) : [];
|
|
120
|
+
const advisory = advisoryLevel(suspicious);
|
|
121
|
+
const fenced = remote
|
|
122
|
+
? wrapToolData(redacted, provenance, advisory, policy.taint?.advisoryGuidance)
|
|
123
|
+
: redacted;
|
|
124
|
+
const hits = [
|
|
125
|
+
...(changed ? [{ rule: 'tool_result.redacted', severity: 'medium' }] : []),
|
|
126
|
+
...suspicious,
|
|
127
|
+
];
|
|
128
|
+
if (hits.length === 0) {
|
|
129
|
+
return { text: fenced };
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
text: fenced,
|
|
133
|
+
...(suspicious.length > 0 ? { suspicious } : {}),
|
|
134
|
+
event: {
|
|
135
|
+
stage: 'tool_result',
|
|
136
|
+
trust: 'untrusted',
|
|
137
|
+
// Redaction changed the text; directive signals only annotate it.
|
|
138
|
+
action: changed ? 'redact' : 'flag',
|
|
139
|
+
hits,
|
|
140
|
+
provenance,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Guard a tool failure message.
|
|
146
|
+
*
|
|
147
|
+
* A remote server authors its own error strings, so an unguarded failure message
|
|
148
|
+
* is the cleanest injection path across this boundary: it reaches the model
|
|
149
|
+
* verbatim and is framed by the kernel as a system report.
|
|
150
|
+
*/
|
|
151
|
+
function guardToolFailureText(message, provenance, policy) {
|
|
152
|
+
const options = detectionForTrust(policy, 'untrusted');
|
|
153
|
+
const redacted = sanitizeText(stripToolFences(message), options);
|
|
154
|
+
if (redacted === message) {
|
|
155
|
+
return { text: redacted };
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
text: redacted,
|
|
159
|
+
event: {
|
|
160
|
+
stage: 'tool_result',
|
|
161
|
+
trust: 'untrusted',
|
|
162
|
+
action: 'redact',
|
|
163
|
+
hits: [{ rule: 'tool_failure.redacted', severity: 'medium' }],
|
|
164
|
+
provenance,
|
|
165
|
+
},
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Inspect model-supplied tool arguments before the call runs.
|
|
170
|
+
*
|
|
171
|
+
* Arguments are model-authored, so the risk is not instruction smuggling but
|
|
172
|
+
* exfiltration: a credential lifted from context and posted outward as a
|
|
173
|
+
* parameter. Detection reports rather than rewrites — silently altering a tool
|
|
174
|
+
* argument would make the call succeed against something the model did not ask
|
|
175
|
+
* for.
|
|
176
|
+
*/
|
|
177
|
+
function inspectToolArguments(args, policy) {
|
|
178
|
+
if (!policy.redactSensitive) {
|
|
179
|
+
return { action: 'allow' };
|
|
180
|
+
}
|
|
181
|
+
const rendered = textForScan(args);
|
|
182
|
+
if (rendered.unscannable) {
|
|
183
|
+
return { action: 'allow' };
|
|
184
|
+
}
|
|
185
|
+
const options = { sanitizeInput: false, redactSensitive: true };
|
|
186
|
+
if (sanitizeText(rendered.text, options) === rendered.text) {
|
|
187
|
+
return { action: 'allow' };
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
action: 'flag',
|
|
191
|
+
hits: [{ rule: 'tool_call.sensitive-argument', severity: 'high' }],
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
/** Guardrail event for a flagged tool call, for the runner to emit. */
|
|
195
|
+
function toolCallEvent(verdict, provenance) {
|
|
196
|
+
if (verdict.action === 'allow') {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
stage: 'tool_call',
|
|
201
|
+
trust: 'untrusted',
|
|
202
|
+
action: verdict.action,
|
|
203
|
+
hits: verdict.hits,
|
|
204
|
+
provenance,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Record a tool result against the turn's taint.
|
|
209
|
+
*
|
|
210
|
+
* Only remote origins taint: a local host tool returns bytes the host's own code
|
|
211
|
+
* produced, and treating those as attacker-influenceable would make the gate
|
|
212
|
+
* useless in practice.
|
|
213
|
+
*/
|
|
214
|
+
function recordTaint(taint, provenance, suspicious = []) {
|
|
215
|
+
const sources = taint?.sources ?? [];
|
|
216
|
+
const prior = taint?.suspicious ?? [];
|
|
217
|
+
if (!isRemoteOrigin(provenance.origin)) {
|
|
218
|
+
return { sources, suspicious: prior };
|
|
219
|
+
}
|
|
220
|
+
return { sources: [...sources, provenance], suspicious: [...prior, ...suspicious] };
|
|
221
|
+
}
|
|
222
|
+
/** True when the turn has already read attacker-influenceable content. */
|
|
223
|
+
function isTainted(taint) {
|
|
224
|
+
return (taint?.sources.length ?? 0) > 0;
|
|
225
|
+
}
|
|
226
|
+
/** True when remote content this turn read looked like it was steering the agent. */
|
|
227
|
+
function isSuspicious(taint) {
|
|
228
|
+
return (taint?.suspicious.length ?? 0) > 0;
|
|
229
|
+
}
|
|
230
|
+
/** Capability rank, so a single threshold can express "this and anything worse". */
|
|
231
|
+
const CAPABILITY_RANK = {
|
|
232
|
+
'read-only': 0,
|
|
233
|
+
'read-write': 1,
|
|
234
|
+
destructive: 2,
|
|
235
|
+
};
|
|
236
|
+
const GATE_RANK = {
|
|
237
|
+
off: Number.POSITIVE_INFINITY,
|
|
238
|
+
destructive: 2,
|
|
239
|
+
write: 1,
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* Decide whether a tool call may proceed given what the turn has already read.
|
|
243
|
+
*
|
|
244
|
+
* A `flag` says the call is happening on a tainted turn and is worth recording; a
|
|
245
|
+
* `block` says the profile asked for it to be refused. Reporting happens whether
|
|
246
|
+
* or not enforcement is configured, so the risk is visible before a host opts in.
|
|
247
|
+
*/
|
|
248
|
+
function checkTaintGate(taint, access, policy) {
|
|
249
|
+
if (!isTainted(taint)) {
|
|
250
|
+
return { action: 'allow' };
|
|
251
|
+
}
|
|
252
|
+
const rank = CAPABILITY_RANK[access] ?? 0;
|
|
253
|
+
if (rank === 0) {
|
|
254
|
+
return { action: 'allow' };
|
|
255
|
+
}
|
|
256
|
+
const suspicious = isSuspicious(taint);
|
|
257
|
+
const hits = [
|
|
258
|
+
{
|
|
259
|
+
rule: suspicious ? 'tool_call.steered-turn' : 'tool_call.tainted-turn',
|
|
260
|
+
severity: 'high',
|
|
261
|
+
},
|
|
262
|
+
];
|
|
263
|
+
// Gated on origin alone. Whether the content looked directive changes the rule
|
|
264
|
+
// reported, never whether the call is refused.
|
|
265
|
+
if (rank < GATE_RANK[policy.taint?.afterRemoteRead ?? 'off']) {
|
|
266
|
+
return { action: 'flag', hits };
|
|
267
|
+
}
|
|
268
|
+
const read = taint?.sources.map((s) => s.tool).join(', ') ?? '';
|
|
269
|
+
const reason = lexiconText(suspicious ? 'taint.reason_steered' : 'taint.reason_tainted');
|
|
270
|
+
return {
|
|
271
|
+
action: 'block',
|
|
272
|
+
hits,
|
|
273
|
+
rejection: lexiconText('taint.blocked', { access, sources: read, reason }),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
export { checkTaintGate, composeToolText, guardToolFailureText, guardToolResult, inspectToolArguments, isRemoteOrigin, isSuspicious, isTainted, recordTaint, TOOL_CLOSE, toolCallEvent, wrapToolData, };
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Guardrail vocabulary — trust levels, stages, verdicts, and profile policy shape.
|
|
3
|
+
*
|
|
4
|
+
* This module is the single source of truth for guardrail types. It must not import
|
|
5
|
+
* from `src/kernel/`: the kernel type-imports `ProfileGuardrailsSpec` for
|
|
6
|
+
* `ProfileCommon.guardrails`, and that edge stays one-directional. Implementation
|
|
7
|
+
* modules under `src/guardrails/` may import kernel types freely.
|
|
8
|
+
*
|
|
9
|
+
* @module
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Origin trust for text entering the model's context.
|
|
13
|
+
*
|
|
14
|
+
* - `trusted` — author-time profile text (`identity.system`). Sensitive redaction
|
|
15
|
+
* only; injection redaction would mangle the host's own instructions.
|
|
16
|
+
* - `assembled` — host-built per turn (`req.system`). Interpolates retrieval and
|
|
17
|
+
* user data, so it is permeable and takes full detection.
|
|
18
|
+
* - `untrusted` — user input, tool results, attachments, delegated agents.
|
|
19
|
+
*/
|
|
20
|
+
export declare const TRUST_LEVELS: readonly ["trusted", "assembled", "untrusted"];
|
|
21
|
+
export type TrustLevel = (typeof TRUST_LEVELS)[number];
|
|
22
|
+
/** Boundary a guardrail check runs at. */
|
|
23
|
+
export declare const GUARDRAIL_STAGES: readonly ["input", "history", "system", "attachment", "tool_call", "tool_result", "output_delta", "output_final", "network", "live_inbound", "live_outbound", "trace"];
|
|
24
|
+
export type GuardrailStage = (typeof GUARDRAIL_STAGES)[number];
|
|
25
|
+
/** How serious a hit is. Does not decide what happens next — that is `onBlock`. */
|
|
26
|
+
export declare const SEVERITIES: readonly ["info", "low", "medium", "high"];
|
|
27
|
+
export type Severity = (typeof SEVERITIES)[number];
|
|
28
|
+
/** Egress block handling. */
|
|
29
|
+
export declare const EGRESS_ON_BLOCK: readonly ["reject_to_agent", "refuse_to_user"];
|
|
30
|
+
export type EgressOnBlock = (typeof EGRESS_ON_BLOCK)[number];
|
|
31
|
+
/** One detector match. */
|
|
32
|
+
export interface GuardrailHit {
|
|
33
|
+
/** Stable rule id, e.g. `injection.instruction-override`. */
|
|
34
|
+
rule: string;
|
|
35
|
+
severity: Severity;
|
|
36
|
+
/** Offsets into the inspected text; absent for whole-payload checks. */
|
|
37
|
+
span?: {
|
|
38
|
+
start: number;
|
|
39
|
+
end: number;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Exact matched substring (capped). Present when detectors had the source text.
|
|
43
|
+
* Stripped from host/trace unless `observability.include.guardrailMatchPreview`.
|
|
44
|
+
*/
|
|
45
|
+
match?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Outcome of one guardrail evaluation.
|
|
49
|
+
*
|
|
50
|
+
* A discriminated union so a new variant fails every unhandled `switch` at
|
|
51
|
+
* compile time rather than falling through at runtime.
|
|
52
|
+
*/
|
|
53
|
+
export type Verdict = {
|
|
54
|
+
action: 'allow';
|
|
55
|
+
} | {
|
|
56
|
+
action: 'redact';
|
|
57
|
+
text: string;
|
|
58
|
+
hits: GuardrailHit[];
|
|
59
|
+
} | {
|
|
60
|
+
action: 'flag';
|
|
61
|
+
hits: GuardrailHit[];
|
|
62
|
+
} | {
|
|
63
|
+
action: 'block';
|
|
64
|
+
hits: GuardrailHit[];
|
|
65
|
+
/** Sent to the model on a repair turn when `onBlock` is `reject_to_agent`. */
|
|
66
|
+
rejection: string;
|
|
67
|
+
/** Shown to the user when `onBlock` is `refuse_to_user`. Host-owned copy. */
|
|
68
|
+
refusal?: string;
|
|
69
|
+
};
|
|
70
|
+
export type GuardrailAction = Verdict['action'];
|
|
71
|
+
/**
|
|
72
|
+
* Where a tool result came from.
|
|
73
|
+
*
|
|
74
|
+
* `local` is host TypeScript the profile registered; `http` and `mcp` are remote
|
|
75
|
+
* services whose bytes the host does not control. `delegated` is another agent
|
|
76
|
+
* answering through the tool boundary — its output is model-generated prose that
|
|
77
|
+
* reads as authoritative, which is why depth is tracked separately.
|
|
78
|
+
*/
|
|
79
|
+
export declare const TOOL_ORIGINS: readonly ["local", "builtin", "http", "mcp", "delegated"];
|
|
80
|
+
export type ToolOrigin = (typeof TOOL_ORIGINS)[number];
|
|
81
|
+
/** Where a piece of content entered the turn from. */
|
|
82
|
+
export interface Provenance {
|
|
83
|
+
origin: ToolOrigin;
|
|
84
|
+
/** Registered tool name. */
|
|
85
|
+
tool: string;
|
|
86
|
+
/**
|
|
87
|
+
* Hops from the user's turn. A direct tool call is 1; a tool result produced by
|
|
88
|
+
* a delegated agent that itself called tools is deeper. Depth matters because a
|
|
89
|
+
* two-hop delegation can otherwise launder remote content into trusted-looking
|
|
90
|
+
* output.
|
|
91
|
+
*/
|
|
92
|
+
depth: number;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Untrusted content a turn has already taken into its context.
|
|
96
|
+
*
|
|
97
|
+
* Once a turn has read attacker-influenceable bytes, a later tool call is a
|
|
98
|
+
* confused-deputy risk: the content can ask the agent to act, and the agent has
|
|
99
|
+
* authority the content does not. Sources are kept in order so a policy can reason
|
|
100
|
+
* about depth as well as presence.
|
|
101
|
+
*/
|
|
102
|
+
export interface TurnTaint {
|
|
103
|
+
sources: Provenance[];
|
|
104
|
+
/**
|
|
105
|
+
* Directive hits found in remote content this turn read.
|
|
106
|
+
*
|
|
107
|
+
* Separates "read something remote" from "read something that tried to steer
|
|
108
|
+
* me". The second is far rarer, so a gate keyed on it refuses far less
|
|
109
|
+
* legitimate work.
|
|
110
|
+
*/
|
|
111
|
+
suspicious: GuardrailHit[];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* What a turn may still do after it has ingested untrusted remote content.
|
|
115
|
+
*
|
|
116
|
+
* Enforcement is opt-in. Tracking and reporting are on by default — every
|
|
117
|
+
* remote read is observable — but refusing tool calls changes what working agents
|
|
118
|
+
* are allowed to do, so a host declares which access levels to gate rather than
|
|
119
|
+
* having the kernel guess.
|
|
120
|
+
*/
|
|
121
|
+
/**
|
|
122
|
+
* How strongly the tool-ingress signals fired, derived from the hits themselves.
|
|
123
|
+
*
|
|
124
|
+
* Not a probability: there is no calibrated model behind it. `elevated` means one
|
|
125
|
+
* directive signal alongside an external destination; `high` means the content
|
|
126
|
+
* named a tool the model can call, or several signals agreed.
|
|
127
|
+
*/
|
|
128
|
+
export declare const ADVISORY_LEVELS: readonly ["none", "elevated", "high"];
|
|
129
|
+
export type AdvisoryLevel = (typeof ADVISORY_LEVELS)[number];
|
|
130
|
+
export declare const TAINT_GATES: readonly ["off", "destructive", "write"];
|
|
131
|
+
export type TaintGate = (typeof TAINT_GATES)[number];
|
|
132
|
+
/**
|
|
133
|
+
* Only structural facts gate tool calls.
|
|
134
|
+
*
|
|
135
|
+
* Content signals from `tool-directives.ts` deliberately have no gate here. They
|
|
136
|
+
* are pattern matches with no measured precision, and refusing a tool call on an
|
|
137
|
+
* unpredictable signal makes an agent unreliable rather than safe — the failure is
|
|
138
|
+
* invisible to the user and looks like the agent being stupid. Those signals
|
|
139
|
+
* annotate the fence and raise telemetry; the model still gets to decide, and the
|
|
140
|
+
* host still gets to see.
|
|
141
|
+
*/
|
|
142
|
+
export interface TaintGuardrailSpec {
|
|
143
|
+
/**
|
|
144
|
+
* Host copy appended to the fence when tool content looks directive.
|
|
145
|
+
*
|
|
146
|
+
* The kernel states what it observed; what the agent should *do* about it —
|
|
147
|
+
* ask the user, refuse, proceed carefully — is product behaviour and stays
|
|
148
|
+
* host-owned. Omitted means the observation is stated without guidance.
|
|
149
|
+
*/
|
|
150
|
+
advisoryGuidance?: string;
|
|
151
|
+
/**
|
|
152
|
+
* Least-severe tool capability refused once the turn has read remote content.
|
|
153
|
+
*
|
|
154
|
+
* - `off` (default) — report only.
|
|
155
|
+
* - `destructive` — refuse hard-to-undo calls.
|
|
156
|
+
* - `write` — refuse those and any state-changing call.
|
|
157
|
+
*
|
|
158
|
+
* Stated as a capability threshold rather than a list of tool `access` values so
|
|
159
|
+
* the guardrail vocabulary stays independent of the tool registry; the kernel
|
|
160
|
+
* maps a tool's declared access onto it.
|
|
161
|
+
*/
|
|
162
|
+
afterRemoteRead?: TaintGate;
|
|
163
|
+
}
|
|
164
|
+
/** Facts a check may read. Deliberately excludes the full profile. */
|
|
165
|
+
export interface GuardrailContext {
|
|
166
|
+
stage: GuardrailStage;
|
|
167
|
+
trust: TrustLevel;
|
|
168
|
+
profileId: string;
|
|
169
|
+
canary?: string;
|
|
170
|
+
role?: string;
|
|
171
|
+
slots?: Record<string, string>;
|
|
172
|
+
/** Set on tool-shaped stages; absent for user and system text. */
|
|
173
|
+
provenance?: Provenance;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* One guardrail decision, as it reaches the host and the trace.
|
|
177
|
+
*
|
|
178
|
+
* Carries rule identity and offsets, never the matched content, so a trace sink
|
|
179
|
+
* can count and locate hits without becoming a second copy of the secret.
|
|
180
|
+
*/
|
|
181
|
+
export interface GuardrailEvent {
|
|
182
|
+
stage: GuardrailStage;
|
|
183
|
+
trust: TrustLevel;
|
|
184
|
+
action: GuardrailAction;
|
|
185
|
+
hits: GuardrailHit[];
|
|
186
|
+
provenance?: Provenance;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* User-visible output projected out of the turn's events.
|
|
190
|
+
*
|
|
191
|
+
* Structured output travels alongside text so a profile with `outputs.structured`
|
|
192
|
+
* is not invisible to its own egress policy.
|
|
193
|
+
*/
|
|
194
|
+
export interface OutboundPayload {
|
|
195
|
+
/** Concatenated user-visible text for this attempt. */
|
|
196
|
+
text: string;
|
|
197
|
+
/** Structured output, when the profile emits it. */
|
|
198
|
+
structured?: unknown;
|
|
199
|
+
}
|
|
200
|
+
/** Evaluates candidate user-visible output before release. */
|
|
201
|
+
export type EgressEnforcer = (payload: OutboundPayload, context: GuardrailContext) => Verdict | Promise<Verdict>;
|
|
202
|
+
/** Profile egress policy for rejection, retry, or refusal behavior. */
|
|
203
|
+
export interface ProfileEgressSpec {
|
|
204
|
+
enforce: EgressEnforcer;
|
|
205
|
+
onBlock?: EgressOnBlock;
|
|
206
|
+
maxRetries?: number;
|
|
207
|
+
repairGuidance?: string;
|
|
208
|
+
}
|
|
209
|
+
/** SSRF and network access policy for HTTP and remote MCP tools. */
|
|
210
|
+
export interface NetworkGuardrailSpec {
|
|
211
|
+
/**
|
|
212
|
+
* When true, allows connections to localhost / loopback and private subnets
|
|
213
|
+
* (e.g. for local dev/testing). Default: false.
|
|
214
|
+
*/
|
|
215
|
+
allowPrivateNetworks?: boolean;
|
|
216
|
+
/** Hostnames or IP addresses permitted regardless of private subnet status. */
|
|
217
|
+
allowedHosts?: string[];
|
|
218
|
+
/**
|
|
219
|
+
* Allowed URL schemes. Defaults to `['https']`, or `['http', 'https']` when
|
|
220
|
+
* `allowPrivateNetworks` is set.
|
|
221
|
+
*/
|
|
222
|
+
allowedSchemes?: string[];
|
|
223
|
+
}
|
|
224
|
+
/** Optional daily turn quota consumed by host HTTP middleware. */
|
|
225
|
+
export interface QuotaGuardrailSpec {
|
|
226
|
+
perDay: number;
|
|
227
|
+
/**
|
|
228
|
+
* Host copy surfaced when the quota trips. The kernel never authors this:
|
|
229
|
+
* `quotaExhausted` returns structured data (`code`, `perDay`) and includes
|
|
230
|
+
* this string only when the host set it.
|
|
231
|
+
*/
|
|
232
|
+
message?: string;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Per-turn canary switches. `true` / `false` toggles minting with the
|
|
236
|
+
* registered default bind note; the object form supplies host copy.
|
|
237
|
+
*/
|
|
238
|
+
export interface CanaryGuardrailSpec {
|
|
239
|
+
/**
|
|
240
|
+
* Host template appended to the system prompt binding the canary. Must
|
|
241
|
+
* contain the `{canary}` placeholder; `bindCanary` refuses a note that lost
|
|
242
|
+
* the token. Omitted means the lexicon default (`canary.bind_note`).
|
|
243
|
+
*/
|
|
244
|
+
bindNote?: string;
|
|
245
|
+
}
|
|
246
|
+
/** Profile guardrail switches enforced by the kernel. */
|
|
247
|
+
export interface ProfileGuardrailsSpec {
|
|
248
|
+
/** Optional daily turn quota; omitted means quota enforcement is not configured. */
|
|
249
|
+
quota?: QuotaGuardrailSpec;
|
|
250
|
+
canary?: boolean | CanaryGuardrailSpec;
|
|
251
|
+
sanitizeInput?: boolean;
|
|
252
|
+
redactSensitive?: boolean;
|
|
253
|
+
egress?: ProfileEgressSpec;
|
|
254
|
+
/** SSRF and network access policies for HTTP and MCP tools. */
|
|
255
|
+
network?: NetworkGuardrailSpec;
|
|
256
|
+
/** What the turn may still do after reading untrusted remote content. */
|
|
257
|
+
taint?: TaintGuardrailSpec;
|
|
258
|
+
}
|
|
259
|
+
/** The guardrail field names a `host` profile may set. */
|
|
260
|
+
export declare const HOST_GUARDRAIL_FIELDS: readonly ["sanitizeInput", "redactSensitive", "network", "taint"];
|
|
261
|
+
/**
|
|
262
|
+
* The guardrail switches a `host` profile may set.
|
|
263
|
+
*
|
|
264
|
+
* A host profile runs no model, so only the guards that fire on the `invokeTool`
|
|
265
|
+
* path exist for it: the detectors applied to model-supplied arguments and to
|
|
266
|
+
* tool result and failure text (`sanitizeInput`, `redactSensitive`), SSRF
|
|
267
|
+
* clearance for declarative HTTP and MCP targets (`network`), and the
|
|
268
|
+
* confused-deputy gate on a tainted turn (`taint`). Everything else in
|
|
269
|
+
* {@link ProfileGuardrailsSpec} — quota, canary, egress — guards a model turn
|
|
270
|
+
* and is refused by `defineProfile` on `type: 'host'`.
|
|
271
|
+
*
|
|
272
|
+
* This is a view of the one guardrail vocabulary, not a second hierarchy.
|
|
273
|
+
*/
|
|
274
|
+
export type HostGuardrailsSpec = Pick<ProfileGuardrailsSpec, (typeof HOST_GUARDRAIL_FIELDS)[number]>;
|
|
275
|
+
/**
|
|
276
|
+
* A profile's guardrail switches with defaults applied.
|
|
277
|
+
*
|
|
278
|
+
* Every path resolves through `resolveGuardrailPolicy` so turn and Live ingress
|
|
279
|
+
* cannot drift apart on defaults.
|
|
280
|
+
*/
|
|
281
|
+
export interface ResolvedGuardrailPolicy {
|
|
282
|
+
sanitizeInput: boolean;
|
|
283
|
+
redactSensitive: boolean;
|
|
284
|
+
canary: boolean;
|
|
285
|
+
/** Host bind-note template from `guardrails.canary.bindNote`, when set. */
|
|
286
|
+
canaryBindNote?: string;
|
|
287
|
+
egress?: ProfileEgressSpec;
|
|
288
|
+
network?: NetworkGuardrailSpec;
|
|
289
|
+
quota?: QuotaGuardrailSpec;
|
|
290
|
+
taint?: TaintGuardrailSpec;
|
|
291
|
+
}
|