theorum 0.1.15 → 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 +241 -98
- package/esm/mod.d.ts +57 -28
- package/esm/mod.js +43 -23
- package/esm/src/cli/commands/bench.js +18 -18
- package/esm/src/cli/commands/fuzz-canary.d.ts +13 -0
- package/esm/src/cli/commands/fuzz-canary.js +191 -0
- package/esm/src/cli/commands/fuzz-guardrails.d.ts +3 -5
- package/esm/src/cli/commands/fuzz-guardrails.js +4 -581
- 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 +35 -15
- package/esm/src/cli/commands/run.d.ts +3 -0
- package/esm/src/cli/commands/run.js +23 -32
- package/esm/src/cli/commands/test.d.ts +10 -1
- package/esm/src/cli/commands/test.js +34 -34
- package/esm/src/cli/event-log.d.ts +19 -0
- package/esm/src/cli/event-log.js +147 -0
- package/esm/src/cli/index.js +57 -11
- package/esm/src/cli/matrix/synthesizer.d.ts +10 -12
- package/esm/src/cli/matrix/synthesizer.js +45 -118
- package/esm/src/guardrails/canary-gate.d.ts +21 -0
- package/esm/src/guardrails/canary-gate.js +32 -0
- package/esm/src/guardrails/canary.d.ts +34 -0
- package/esm/src/guardrails/canary.js +150 -0
- package/esm/src/guardrails/corpus/canary-egress-attacks.d.ts +17 -0
- package/esm/src/guardrails/corpus/canary-egress-attacks.js +151 -0
- package/esm/src/guardrails/corpus/fuzz-inbound.d.ts +11 -0
- package/esm/src/guardrails/corpus/fuzz-inbound.js +213 -0
- package/esm/src/guardrails/corpus/inbound-payloads.d.ts +10 -0
- package/esm/src/guardrails/corpus/inbound-payloads.js +125 -0
- package/esm/src/guardrails/corpus/live-attacks.d.ts +20 -0
- package/esm/src/guardrails/corpus/live-attacks.js +231 -0
- package/esm/src/guardrails/corpus/mod.d.ts +14 -0
- package/esm/src/guardrails/corpus/mod.js +11 -0
- package/esm/src/guardrails/corpus/secrets.d.ts +17 -0
- package/esm/src/guardrails/corpus/secrets.js +17 -0
- package/esm/src/guardrails/corpus/strings.d.ts +28 -0
- package/esm/src/guardrails/corpus/strings.js +34 -0
- package/esm/src/guardrails/corpus/types.d.ts +38 -0
- package/esm/src/guardrails/corpus/types.js +6 -0
- package/esm/src/guardrails/egress.d.ts +32 -0
- package/esm/src/guardrails/egress.js +87 -0
- package/esm/src/guardrails/error.d.ts +14 -23
- package/esm/src/guardrails/error.js +87 -76
- 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/injection.js +28 -5
- 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 +41 -0
- package/esm/src/guardrails/live-outbound-gate.js +222 -0
- package/esm/src/guardrails/mod.d.ts +30 -6
- package/esm/src/guardrails/mod.js +20 -5
- 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 +45 -19
- package/esm/src/guardrails/sanitize.js +177 -94
- package/esm/src/guardrails/sensitive.js +2 -1
- package/esm/src/guardrails/serialize.d.ts +35 -0
- package/esm/src/guardrails/serialize.js +58 -0
- package/esm/src/guardrails/testing.d.ts +17 -0
- package/esm/src/guardrails/testing.js +13 -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.d.ts +19 -0
- package/esm/src/host/client-turn.js +36 -0
- package/esm/src/host/mint-trace.d.ts +1 -1
- package/esm/src/host/mod.d.ts +5 -3
- package/esm/src/host/mod.js +4 -3
- 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.d.ts +24 -2
- package/esm/src/kernel/engine/delta.js +478 -39
- package/esm/src/kernel/engine/live-inbound.d.ts +21 -0
- package/esm/src/kernel/engine/live-inbound.js +31 -0
- 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.d.ts +1 -1
- package/esm/src/kernel/engine/runner/gates.js +130 -43
- package/esm/src/kernel/engine/runner/mod.d.ts +6 -4
- package/esm/src/kernel/engine/runner/mod.js +192 -53
- 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 +31 -0
- package/esm/src/kernel/engine/runner/steps.d.ts +1 -1
- package/esm/src/kernel/engine/runner/steps.js +244 -43
- package/esm/src/kernel/engine/runner/stream.d.ts +9 -3
- package/esm/src/kernel/engine/runner/stream.js +140 -44
- package/esm/src/kernel/engine/session/mod.d.ts +25 -0
- package/esm/src/kernel/engine/session/mod.js +557 -0
- 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 +21 -10
- package/esm/src/kernel/mod.js +11 -8
- 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 +12 -10
- package/esm/src/kernel/registry/attachments.js +33 -27
- package/esm/src/kernel/registry/catalog.d.ts +25 -24
- package/esm/src/kernel/registry/catalog.js +60 -101
- package/esm/src/kernel/registry/ingress.d.ts +9 -4
- package/esm/src/kernel/registry/ingress.js +97 -75
- 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 +55 -12
- package/esm/src/kernel/registry/profiles.js +413 -73
- package/esm/src/kernel/registry/provider-request.js +13 -7
- package/esm/src/kernel/registry/resolve.d.ts +8 -8
- package/esm/src/kernel/registry/resolve.js +169 -154
- 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 +12 -7
- package/esm/src/kernel/registry/vault.js +32 -10
- package/esm/src/kernel/schema.d.ts +231 -0
- package/esm/src/kernel/schema.js +607 -0
- package/esm/src/kernel/stages.d.ts +175 -0
- package/esm/src/kernel/stages.js +476 -0
- package/esm/src/kernel/stop.d.ts +78 -19
- package/esm/src/kernel/stop.js +51 -16
- 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 +84 -0
- package/esm/src/kernel/tools/execute.js +614 -0
- package/esm/src/kernel/tools/harness.d.ts +8 -0
- package/esm/src/kernel/tools/harness.js +46 -0
- package/esm/src/kernel/tools/invoke.d.ts +10 -0
- package/esm/src/kernel/tools/invoke.js +101 -0
- package/esm/src/kernel/tools/mod.d.ts +13 -0
- package/esm/src/kernel/tools/mod.js +11 -0
- 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.d.ts +12 -0
- package/esm/src/kernel/tools/project.js +36 -0
- package/esm/src/kernel/tools/registry.d.ts +23 -0
- package/esm/src/kernel/tools/registry.js +81 -0
- 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 +39 -0
- package/esm/src/kernel/tools/resolve.js +283 -0
- package/esm/src/kernel/tools/schema.d.ts +15 -0
- package/esm/src/kernel/tools/schema.js +176 -0
- 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 +394 -0
- package/esm/src/kernel/tools/types.js +9 -0
- package/esm/src/kernel/types.d.ts +540 -256
- 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 +8 -4
- package/esm/src/observability/trace-attach.js +50 -29
- package/esm/src/observability/trace-record.d.ts +23 -13
- package/esm/src/observability/trace-record.js +96 -39
- package/esm/src/observability/trace-sink.d.ts +19 -0
- package/esm/src/observability/trace-sink.js +10 -0
- package/esm/src/observability/trace-usage.d.ts +10 -3
- package/esm/src/observability/trace-usage.js +70 -17
- package/esm/src/observability/trace.d.ts +18 -7
- package/esm/src/observability/trace.js +34 -17
- package/esm/src/observability/types.d.ts +113 -0
- package/esm/src/observability/types.js +11 -0
- package/esm/src/presets/google/speech-voices.d.ts +11 -0
- package/esm/src/presets/google/speech-voices.js +41 -0
- package/esm/src/presets/google.d.ts +36 -24
- package/esm/src/presets/google.js +50 -63
- package/esm/src/presets/mod.d.ts +2 -2
- package/esm/src/presets/mod.js +1 -1
- package/esm/src/providers/create-provider.d.ts +20 -17
- package/esm/src/providers/create-provider.js +72 -26
- package/esm/src/providers/google/interactions/framing.d.ts +23 -0
- package/esm/src/providers/google/interactions/framing.js +269 -0
- package/esm/src/providers/google/interactions/mod.d.ts +7 -0
- package/esm/src/providers/google/interactions/mod.js +7 -0
- package/esm/src/providers/google/interactions/stream.d.ts +83 -0
- package/esm/src/providers/google/interactions/stream.js +588 -0
- package/esm/src/providers/google/keys.d.ts +26 -0
- package/esm/src/providers/{keys.js → google/keys.js} +19 -31
- package/esm/src/providers/google/live/framing.d.ts +49 -0
- package/esm/src/providers/google/live/framing.js +552 -0
- package/esm/src/providers/google/live/openapi-schema.d.ts +6 -0
- package/esm/src/providers/google/live/openapi-schema.js +46 -0
- package/esm/src/providers/google/live/session.d.ts +25 -0
- package/esm/src/providers/google/live/session.js +134 -0
- package/esm/src/providers/google/live/stream.d.ts +45 -0
- package/esm/src/providers/google/live/stream.js +214 -0
- package/esm/src/providers/google/urls.d.ts +6 -0
- package/esm/src/providers/google/urls.js +6 -0
- package/esm/src/providers/local/local.d.ts +30 -0
- package/esm/src/providers/{local.js → local/local.js} +66 -126
- package/esm/src/providers/local/mod.d.ts +9 -0
- package/esm/src/providers/local/mod.js +9 -0
- package/esm/src/providers/mod.d.ts +6 -3
- package/esm/src/providers/mod.js +3 -1
- 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 +107 -0
- package/esm/src/providers/{openrouter.js → openrouter/chat.js} +117 -231
- package/esm/src/providers/openrouter/image.d.ts +34 -0
- package/esm/src/providers/openrouter/image.js +275 -0
- package/esm/src/providers/openrouter/openai/chat-payload.d.ts +24 -0
- package/esm/src/providers/openrouter/openai/chat-payload.js +82 -0
- package/esm/src/providers/openrouter/openai/compat.d.ts +53 -0
- package/esm/src/providers/openrouter/openai/compat.js +213 -0
- package/esm/src/providers/openrouter/openai/image-payload.d.ts +18 -0
- package/esm/src/providers/openrouter/openai/image-payload.js +90 -0
- package/esm/src/providers/openrouter/openai/sdk-messages.d.ts +22 -0
- package/esm/src/providers/openrouter/openai/sdk-messages.js +122 -0
- package/esm/src/providers/openrouter/resolve-api-key.d.ts +9 -0
- package/esm/src/providers/openrouter/resolve-api-key.js +24 -0
- package/esm/src/providers/openrouter/speech.d.ts +23 -0
- package/esm/src/providers/{speech.js → openrouter/speech.js} +32 -55
- package/esm/src/providers/probe.d.ts +1 -0
- package/esm/src/providers/probe.js +22 -0
- package/esm/src/providers/shared/pcm.d.ts +12 -0
- package/esm/src/providers/{pcm.js → shared/pcm.js} +16 -3
- package/esm/src/providers/shared/sse.d.ts +18 -0
- package/esm/src/providers/shared/sse.js +87 -0
- package/esm/src/providers/shared/tool-args.d.ts +17 -0
- package/esm/src/providers/shared/tool-args.js +45 -0
- package/esm/src/providers/shared/upstream-tap.d.ts +5 -0
- package/esm/src/providers/{google-tap.js → shared/upstream-tap.js} +4 -7
- package/esm/src/providers/shared/upstream-tape.d.ts +6 -0
- package/esm/src/providers/{gemini-tape.js → shared/upstream-tape.js} +12 -22
- package/esm/src/providers/types.d.ts +27 -0
- package/esm/src/providers/types.js +1 -0
- package/package.json +11 -7
- package/docs/cli.md +0 -97
- package/docs/guardrails.md +0 -178
- package/docs/host.md +0 -97
- package/docs/kernel.md +0 -404
- package/docs/observability.md +0 -105
- package/docs/openrouter.md +0 -125
- package/docs/presets-google.md +0 -91
- package/docs/presets.md +0 -88
- package/docs/providers.md +0 -202
- package/docs/streaming.md +0 -96
- package/esm/src/kernel/engine/boundary.d.ts +0 -10
- package/esm/src/kernel/engine/boundary.js +0 -55
- package/esm/src/kernel/engine/runner/tools.d.ts +0 -13
- package/esm/src/kernel/engine/runner/tools.js +0 -198
- package/esm/src/kernel/registry/tools.d.ts +0 -12
- package/esm/src/kernel/registry/tools.js +0 -36
- package/esm/src/providers/expose-for-tests.d.ts +0 -1
- package/esm/src/providers/expose-for-tests.js +0 -25
- package/esm/src/providers/gemini-tape.d.ts +0 -2
- package/esm/src/providers/google-tap.d.ts +0 -3
- package/esm/src/providers/interactions.d.ts +0 -5
- package/esm/src/providers/interactions.js +0 -169
- package/esm/src/providers/keys.d.ts +0 -19
- package/esm/src/providers/local.d.ts +0 -29
- package/esm/src/providers/openrouter-mod.d.ts +0 -13
- package/esm/src/providers/openrouter-mod.js +0 -12
- package/esm/src/providers/openrouter-payload.d.ts +0 -39
- package/esm/src/providers/openrouter-payload.js +0 -195
- package/esm/src/providers/openrouter.d.ts +0 -15
- package/esm/src/providers/pcm.d.ts +0 -7
- package/esm/src/providers/provider.d.ts +0 -15
- package/esm/src/providers/provider.js +0 -202
- package/esm/src/providers/speech.d.ts +0 -23
- package/esm/src/providers/sse.d.ts +0 -7
- package/esm/src/providers/sse.js +0 -55
- package/esm/src/streaming/mod.d.ts +0 -9
- package/esm/src/streaming/mod.js +0 -8
- /package/esm/src/{streaming → host}/readStreamingJsonStringField.d.ts +0 -0
- /package/esm/src/{streaming → host}/readStreamingJsonStringField.js +0 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
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 const TRUST_LEVELS = ['trusted', 'assembled', 'untrusted'];
|
|
21
|
+
/** Boundary a guardrail check runs at. */
|
|
22
|
+
export const GUARDRAIL_STAGES = [
|
|
23
|
+
'input',
|
|
24
|
+
'history',
|
|
25
|
+
'system',
|
|
26
|
+
'attachment',
|
|
27
|
+
'tool_call',
|
|
28
|
+
'tool_result',
|
|
29
|
+
'output_delta',
|
|
30
|
+
'output_final',
|
|
31
|
+
'network',
|
|
32
|
+
'live_inbound',
|
|
33
|
+
'live_outbound',
|
|
34
|
+
'trace',
|
|
35
|
+
];
|
|
36
|
+
/** How serious a hit is. Does not decide what happens next — that is `onBlock`. */
|
|
37
|
+
export const SEVERITIES = ['info', 'low', 'medium', 'high'];
|
|
38
|
+
/** Egress block handling. */
|
|
39
|
+
export const EGRESS_ON_BLOCK = ['reject_to_agent', 'refuse_to_user'];
|
|
40
|
+
/**
|
|
41
|
+
* Where a tool result came from.
|
|
42
|
+
*
|
|
43
|
+
* `local` is host TypeScript the profile registered; `http` and `mcp` are remote
|
|
44
|
+
* services whose bytes the host does not control. `delegated` is another agent
|
|
45
|
+
* answering through the tool boundary — its output is model-generated prose that
|
|
46
|
+
* reads as authoritative, which is why depth is tracked separately.
|
|
47
|
+
*/
|
|
48
|
+
export const TOOL_ORIGINS = ['local', 'builtin', 'http', 'mcp', 'delegated'];
|
|
49
|
+
/**
|
|
50
|
+
* What a turn may still do after it has ingested untrusted remote content.
|
|
51
|
+
*
|
|
52
|
+
* Enforcement is opt-in. Tracking and reporting are on by default — every
|
|
53
|
+
* remote read is observable — but refusing tool calls changes what working agents
|
|
54
|
+
* are allowed to do, so a host declares which access levels to gate rather than
|
|
55
|
+
* having the kernel guess.
|
|
56
|
+
*/
|
|
57
|
+
/**
|
|
58
|
+
* How strongly the tool-ingress signals fired, derived from the hits themselves.
|
|
59
|
+
*
|
|
60
|
+
* Not a probability: there is no calibrated model behind it. `elevated` means one
|
|
61
|
+
* directive signal alongside an external destination; `high` means the content
|
|
62
|
+
* named a tool the model can call, or several signals agreed.
|
|
63
|
+
*/
|
|
64
|
+
export const ADVISORY_LEVELS = ['none', 'elevated', 'high'];
|
|
65
|
+
export const TAINT_GATES = ['off', 'destructive', 'write'];
|
|
66
|
+
/** The guardrail field names a `host` profile may set. */
|
|
67
|
+
export const HOST_GUARDRAIL_FIELDS = [
|
|
68
|
+
'sanitizeInput',
|
|
69
|
+
'redactSensitive',
|
|
70
|
+
'network',
|
|
71
|
+
'taint',
|
|
72
|
+
];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip host-only diagnostics from turn events before client-facing transports.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import type { TurnEvent } from '../kernel/types.js';
|
|
7
|
+
/** Options for {@link forClient} / {@link forClientEvents}. */
|
|
8
|
+
export interface ClientTurnOptions {
|
|
9
|
+
/**
|
|
10
|
+
* Keep provider-native step payloads on `evidence` events.
|
|
11
|
+
* Default `false` — parsed fields (`kind`, `code`, `result`, citations) remain.
|
|
12
|
+
*/
|
|
13
|
+
includeEvidenceRaw?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Return a copy of one turn event safe to forward to browsers or end-user SSE. */
|
|
16
|
+
declare function forClient(event: TurnEvent, options?: ClientTurnOptions): TurnEvent;
|
|
17
|
+
/** Map {@link forClient} over a batch (e.g. Live relay or HTTP stream flush). */
|
|
18
|
+
declare function forClientEvents(events: TurnEvent[], options?: ClientTurnOptions): TurnEvent[];
|
|
19
|
+
export { forClient, forClientEvents };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strip host-only diagnostics from turn events before client-facing transports.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
*/
|
|
6
|
+
import { projectGuardrailTurnEvent } from '../guardrails/events.js';
|
|
7
|
+
function stripErrorInternal(event) {
|
|
8
|
+
if (event.type !== 'error' || !event.errorInternal) {
|
|
9
|
+
return event;
|
|
10
|
+
}
|
|
11
|
+
const { errorInternal: _internal, ...rest } = event;
|
|
12
|
+
return rest;
|
|
13
|
+
}
|
|
14
|
+
function stripEvidenceRaw(event) {
|
|
15
|
+
if (event.type !== 'evidence' || !event.evidence?.raw) {
|
|
16
|
+
return event;
|
|
17
|
+
}
|
|
18
|
+
const { raw: _raw, ...evidence } = event.evidence;
|
|
19
|
+
return { ...event, evidence };
|
|
20
|
+
}
|
|
21
|
+
/** Return a copy of one turn event safe to forward to browsers or end-user SSE. */
|
|
22
|
+
function forClient(event, options) {
|
|
23
|
+
let out = stripErrorInternal(event);
|
|
24
|
+
if (!options?.includeEvidenceRaw) {
|
|
25
|
+
out = stripEvidenceRaw(out);
|
|
26
|
+
}
|
|
27
|
+
// Clients never receive matched substrings — even if the host opted into
|
|
28
|
+
// guardrailMatchPreview for server logs / JSONL.
|
|
29
|
+
out = projectGuardrailTurnEvent(out, false);
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
/** Map {@link forClient} over a batch (e.g. Live relay or HTTP stream flush). */
|
|
33
|
+
function forClientEvents(events, options) {
|
|
34
|
+
return events.map((event) => forClient(event, options));
|
|
35
|
+
}
|
|
36
|
+
export { forClient, forClientEvents };
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
-
import { type TraceSink } from '../observability/trace.js';
|
|
10
9
|
import type { TraceRecord } from '../observability/trace-record.js';
|
|
10
|
+
import type { TraceSink } from '../observability/trace-sink.js';
|
|
11
11
|
interface CutoutTape {
|
|
12
12
|
ok: boolean;
|
|
13
13
|
ms: number;
|
package/esm/src/host/mod.d.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Optional host-application helpers.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* implementing the same glue in every app route.
|
|
4
|
+
* Not part of the turn kernel. Shared glue for Deno HTTP hosts (reply status,
|
|
5
|
+
* cutout-trace flush) and live structured-output preview while tokens stream.
|
|
7
6
|
*
|
|
8
7
|
* @module
|
|
9
8
|
*/
|
|
10
9
|
import "../../_dnt.polyfills.js";
|
|
10
|
+
export type { ClientTurnOptions } from './client-turn.js';
|
|
11
|
+
export { forClient, forClientEvents } from './client-turn.js';
|
|
11
12
|
export type { CutoutTape } from './mint-trace.js';
|
|
12
13
|
export { flushMintTrace } from './mint-trace.js';
|
|
14
|
+
export { readStreamingJsonStringField } from './readStreamingJsonStringField.js';
|
|
13
15
|
export { caughtStatus, HTTP_BUSY, HTTP_METHOD, HTTP_NOT_FOUND, HTTP_OK, json, } from './reply.js';
|
package/esm/src/host/mod.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Optional host-application helpers.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* implementing the same glue in every app route.
|
|
4
|
+
* Not part of the turn kernel. Shared glue for Deno HTTP hosts (reply status,
|
|
5
|
+
* cutout-trace flush) and live structured-output preview while tokens stream.
|
|
7
6
|
*
|
|
8
7
|
* @module
|
|
9
8
|
*/
|
|
10
9
|
import "../../_dnt.polyfills.js";
|
|
10
|
+
export { forClient, forClientEvents } from './client-turn.js';
|
|
11
11
|
export { flushMintTrace } from './mint-trace.js';
|
|
12
|
+
export { readStreamingJsonStringField } from './readStreamingJsonStringField.js';
|
|
12
13
|
export { caughtStatus, HTTP_BUSY, HTTP_METHOD, HTTP_NOT_FOUND, HTTP_OK, json, } from './reply.js';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Crypto utilities for OAuth 2.1 PKCE and stateless sealed state envelopes.
|
|
3
|
+
*
|
|
4
|
+
* All operations use standard `crypto.subtle` and `crypto.getRandomValues`,
|
|
5
|
+
* ensuring 100% portability across Node, Deno, Bun, Cloudflare Workers, and browsers.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
/** Encode Uint8Array to RFC 4648 base64url string without padding. */
|
|
10
|
+
export declare function toBase64Url(bytes: Uint8Array): string;
|
|
11
|
+
/** Decode RFC 4648 base64url string to Uint8Array. */
|
|
12
|
+
export declare function fromBase64Url(base64url: string): Uint8Array;
|
|
13
|
+
/**
|
|
14
|
+
* Generate a cryptographically secure PKCE code verifier (RFC 7636 Section 4.1).
|
|
15
|
+
* Length must be between 43 and 128 characters without modulo bias.
|
|
16
|
+
*/
|
|
17
|
+
export declare function generateCodeVerifier(length?: number): string;
|
|
18
|
+
/**
|
|
19
|
+
* Compute the PKCE code challenge using S256 (RFC 7636 Section 4.2):
|
|
20
|
+
* `BASE64URL(SHA256(ASCII(code_verifier)))`
|
|
21
|
+
*/
|
|
22
|
+
export declare function computeCodeChallenge(verifier: string): Promise<string>;
|
|
23
|
+
export interface SealedStatePayload {
|
|
24
|
+
codeVerifier: string;
|
|
25
|
+
expectedIssuer: string;
|
|
26
|
+
resource?: string;
|
|
27
|
+
redirectUri: string;
|
|
28
|
+
expiresAt: number;
|
|
29
|
+
clientId: string;
|
|
30
|
+
extra?: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a stateless HMAC-SHA256 signed envelope for OAuth `state`.
|
|
34
|
+
* This allows a stateless backend to recover the code_verifier and expected issuer
|
|
35
|
+
* upon receiving the OAuth callback, without any database or session cache.
|
|
36
|
+
*/
|
|
37
|
+
export declare function sealStatePayload(payload: SealedStatePayload, secret: string): Promise<string>;
|
|
38
|
+
/**
|
|
39
|
+
* Unpack and verify an HMAC-SHA256 signed `state` envelope.
|
|
40
|
+
* Validates cryptographic signature and expiration timestamp.
|
|
41
|
+
*/
|
|
42
|
+
export declare function unsealStatePayload(sealed: string, secret: string): Promise<SealedStatePayload>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web Crypto utilities for OAuth 2.1 PKCE and stateless sealed state envelopes.
|
|
3
|
+
*
|
|
4
|
+
* All operations use standard `crypto.subtle` and `crypto.getRandomValues`,
|
|
5
|
+
* ensuring 100% portability across Node, Deno, Bun, Cloudflare Workers, and browsers.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
/** Encode Uint8Array to RFC 4648 base64url string without padding. */
|
|
10
|
+
export function toBase64Url(bytes) {
|
|
11
|
+
let binary = '';
|
|
12
|
+
for (let i = 0; i < bytes.byteLength; i++) {
|
|
13
|
+
binary += String.fromCharCode(bytes[i]);
|
|
14
|
+
}
|
|
15
|
+
return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
16
|
+
}
|
|
17
|
+
/** Decode RFC 4648 base64url string to Uint8Array. */
|
|
18
|
+
export function fromBase64Url(base64url) {
|
|
19
|
+
let base64 = base64url.replace(/-/g, '+').replace(/_/g, '/');
|
|
20
|
+
while (base64.length % 4 !== 0) {
|
|
21
|
+
base64 += '=';
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const binary = atob(base64);
|
|
25
|
+
const bytes = new Uint8Array(binary.length);
|
|
26
|
+
for (let i = 0; i < binary.length; i++) {
|
|
27
|
+
bytes[i] = binary.charCodeAt(i);
|
|
28
|
+
}
|
|
29
|
+
return bytes;
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
throw new Error(`Invalid base64url encoding: ${err instanceof Error ? err.message : String(err)}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Generate a cryptographically secure PKCE code verifier (RFC 7636 Section 4.1).
|
|
37
|
+
* Length must be between 43 and 128 characters without modulo bias.
|
|
38
|
+
*/
|
|
39
|
+
export function generateCodeVerifier(length = 64) {
|
|
40
|
+
if (length < 43 || length > 128) {
|
|
41
|
+
throw new RangeError(`Invalid PKCE code_verifier length: ${length}. RFC 7636 Section 4.1 requires length between 43 and 128 characters.`);
|
|
42
|
+
}
|
|
43
|
+
const validChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
|
|
44
|
+
const maxValid = 256 - (256 % validChars.length); // 198 (66 * 3) eliminates modulo bias
|
|
45
|
+
let verifier = '';
|
|
46
|
+
const buffer = new Uint8Array(length * 2);
|
|
47
|
+
while (verifier.length < length) {
|
|
48
|
+
crypto.getRandomValues(buffer);
|
|
49
|
+
for (let i = 0; i < buffer.length && verifier.length < length; i++) {
|
|
50
|
+
const val = buffer[i];
|
|
51
|
+
if (val !== undefined && val < maxValid) {
|
|
52
|
+
verifier += validChars[val % validChars.length];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return verifier;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Compute the PKCE code challenge using S256 (RFC 7636 Section 4.2):
|
|
60
|
+
* `BASE64URL(SHA256(ASCII(code_verifier)))`
|
|
61
|
+
*/
|
|
62
|
+
export async function computeCodeChallenge(verifier) {
|
|
63
|
+
const encoder = new TextEncoder();
|
|
64
|
+
const data = encoder.encode(verifier);
|
|
65
|
+
const digest = await crypto.subtle.digest('SHA-256', data);
|
|
66
|
+
return toBase64Url(new Uint8Array(digest));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Create a stateless HMAC-SHA256 signed envelope for OAuth `state`.
|
|
70
|
+
* This allows a stateless backend to recover the code_verifier and expected issuer
|
|
71
|
+
* upon receiving the OAuth callback, without any database or session cache.
|
|
72
|
+
*/
|
|
73
|
+
export async function sealStatePayload(payload, secret) {
|
|
74
|
+
const encoder = new TextEncoder();
|
|
75
|
+
const jsonStr = JSON.stringify(payload);
|
|
76
|
+
const payloadBytes = encoder.encode(jsonStr);
|
|
77
|
+
const payloadB64 = toBase64Url(payloadBytes);
|
|
78
|
+
const key = await crypto.subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
79
|
+
const signature = await crypto.subtle.sign('HMAC', key, encoder.encode(payloadB64));
|
|
80
|
+
const signatureB64 = toBase64Url(new Uint8Array(signature));
|
|
81
|
+
return `${payloadB64}.${signatureB64}`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Unpack and verify an HMAC-SHA256 signed `state` envelope.
|
|
85
|
+
* Validates cryptographic signature and expiration timestamp.
|
|
86
|
+
*/
|
|
87
|
+
export async function unsealStatePayload(sealed, secret) {
|
|
88
|
+
const parts = sealed.split('.');
|
|
89
|
+
if (parts.length !== 2) {
|
|
90
|
+
throw new Error('Invalid sealed state format'); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
91
|
+
}
|
|
92
|
+
const [payloadB64, signatureB64] = parts;
|
|
93
|
+
const encoder = new TextEncoder();
|
|
94
|
+
const key = await crypto.subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
|
|
95
|
+
const signatureBytes = fromBase64Url(signatureB64);
|
|
96
|
+
const isValid = await crypto.subtle.verify('HMAC', key, signatureBytes, encoder.encode(payloadB64));
|
|
97
|
+
if (!isValid) {
|
|
98
|
+
throw new Error('OAuth state HMAC signature verification failed: state has been tampered with or corrupted');
|
|
99
|
+
}
|
|
100
|
+
const payloadJson = new TextDecoder().decode(fromBase64Url(payloadB64));
|
|
101
|
+
const payload = JSON.parse(payloadJson);
|
|
102
|
+
if (Date.now() > payload.expiresAt) {
|
|
103
|
+
throw new Error('OAuth state has expired'); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
104
|
+
}
|
|
105
|
+
return payload;
|
|
106
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication and authorization primitives for Theorum.
|
|
3
|
+
*
|
|
4
|
+
* Implements stateless OAuth 2.1 PKCE, RFC 9728 discovery, RFC 8414 AS metadata,
|
|
5
|
+
* RFC 9207 issuer validation, and RFC 8707 resource indicators.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
export * from './crypto.js';
|
|
10
|
+
export * from './oauth.js';
|
|
11
|
+
export * from './types.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication and authorization primitives for Theorum.
|
|
3
|
+
*
|
|
4
|
+
* Implements stateless OAuth 2.1 PKCE, RFC 9728 discovery, RFC 8414 AS metadata,
|
|
5
|
+
* RFC 9207 issuer validation, and RFC 8707 resource indicators.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
export * from './crypto.js';
|
|
10
|
+
export * from './oauth.js';
|
|
11
|
+
export * from './types.js';
|