@theoremjs/agents 0.0.0-stage → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +545 -2
- package/esm/_dnt.polyfills.d.ts +66 -0
- package/esm/_dnt.polyfills.js +51 -0
- package/esm/mod.d.ts +79 -0
- package/esm/mod.js +49 -0
- package/esm/package.json +3 -0
- package/esm/src/cli/bin.d.ts +7 -0
- package/esm/src/cli/bin.js +9 -0
- package/esm/src/cli/commands/bench.d.ts +6 -0
- package/esm/src/cli/commands/bench.js +480 -0
- package/esm/src/cli/commands/detect-compile.d.ts +17 -0
- package/esm/src/cli/commands/detect-compile.js +22 -0
- package/esm/src/cli/commands/eval.d.ts +37 -0
- package/esm/src/cli/commands/eval.js +190 -0
- package/esm/src/cli/commands/fuzz-canary.d.ts +15 -0
- package/esm/src/cli/commands/fuzz-canary.js +241 -0
- package/esm/src/cli/commands/fuzz-guardrails.d.ts +2 -0
- package/esm/src/cli/commands/fuzz-guardrails.js +5 -0
- package/esm/src/cli/commands/profile.d.ts +2 -0
- package/esm/src/cli/commands/profile.js +80 -0
- package/esm/src/cli/commands/run.d.ts +13 -0
- package/esm/src/cli/commands/run.js +45 -0
- package/esm/src/cli/commands/test.d.ts +26 -0
- package/esm/src/cli/commands/test.js +143 -0
- package/esm/src/cli/event-log.d.ts +15 -0
- package/esm/src/cli/event-log.js +136 -0
- package/esm/src/cli/index.d.ts +10 -0
- package/esm/src/cli/index.js +267 -0
- package/esm/src/cli/matrix/fixtures.d.ts +10 -0
- package/esm/src/cli/matrix/fixtures.js +67 -0
- package/esm/src/cli/matrix/synthesizer.d.ts +18 -0
- package/esm/src/cli/matrix/synthesizer.js +103 -0
- package/esm/src/evals/attachments.d.ts +5 -0
- package/esm/src/evals/attachments.js +55 -0
- package/esm/src/evals/breakdown.d.ts +30 -0
- package/esm/src/evals/breakdown.js +69 -0
- package/esm/src/evals/graders/answer.d.ts +16 -0
- package/esm/src/evals/graders/answer.js +134 -0
- package/esm/src/evals/graders/code.d.ts +40 -0
- package/esm/src/evals/graders/code.js +184 -0
- package/esm/src/evals/graders/judge.d.ts +24 -0
- package/esm/src/evals/graders/judge.js +367 -0
- package/esm/src/evals/graders/latency.d.ts +6 -0
- package/esm/src/evals/graders/latency.js +82 -0
- package/esm/src/evals/graders/live.d.ts +14 -0
- package/esm/src/evals/graders/live.js +42 -0
- package/esm/src/evals/graders/media.d.ts +18 -0
- package/esm/src/evals/graders/media.js +91 -0
- package/esm/src/evals/graders/shared.d.ts +11 -0
- package/esm/src/evals/graders/shared.js +62 -0
- package/esm/src/evals/graders/transcript.d.ts +19 -0
- package/esm/src/evals/graders/transcript.js +202 -0
- package/esm/src/evals/mod.d.ts +34 -0
- package/esm/src/evals/mod.js +22 -0
- package/esm/src/evals/record.d.ts +41 -0
- package/esm/src/evals/record.js +107 -0
- package/esm/src/evals/rubrics/catalog.d.ts +148 -0
- package/esm/src/evals/rubrics/catalog.js +846 -0
- package/esm/src/evals/rubrics/mod.d.ts +24 -0
- package/esm/src/evals/rubrics/mod.js +55 -0
- package/esm/src/evals/rubrics/mustache.d.ts +23 -0
- package/esm/src/evals/rubrics/mustache.js +154 -0
- package/esm/src/evals/rubrics/types.d.ts +54 -0
- package/esm/src/evals/rubrics/types.js +74 -0
- package/esm/src/evals/run.d.ts +75 -0
- package/esm/src/evals/run.js +418 -0
- package/esm/src/evals/suite.d.ts +28 -0
- package/esm/src/evals/suite.js +97 -0
- package/esm/src/evals/summary.d.ts +35 -0
- package/esm/src/evals/summary.js +31 -0
- package/esm/src/evals/trial.d.ts +14 -0
- package/esm/src/evals/trial.js +135 -0
- package/esm/src/evals/types.d.ts +169 -0
- package/esm/src/evals/types.js +113 -0
- package/esm/src/evals/verdict.d.ts +23 -0
- package/esm/src/evals/verdict.js +48 -0
- package/esm/src/guardrails/boundaries.d.ts +41 -0
- package/esm/src/guardrails/boundaries.js +103 -0
- package/esm/src/guardrails/canary-gate.d.ts +22 -0
- package/esm/src/guardrails/canary-gate.js +40 -0
- package/esm/src/guardrails/canary.d.ts +146 -0
- package/esm/src/guardrails/canary.js +974 -0
- package/esm/src/guardrails/compile-egress.d.ts +12 -0
- package/esm/src/guardrails/compile-egress.js +12 -0
- package/esm/src/guardrails/corpus/canary-egress-attacks.d.ts +24 -0
- package/esm/src/guardrails/corpus/canary-egress-attacks.js +305 -0
- package/esm/src/guardrails/corpus/fuzz-inbound.d.ts +5 -0
- package/esm/src/guardrails/corpus/fuzz-inbound.js +199 -0
- package/esm/src/guardrails/corpus/inbound-payloads.d.ts +5 -0
- package/esm/src/guardrails/corpus/inbound-payloads.js +120 -0
- package/esm/src/guardrails/corpus/live-attacks.d.ts +15 -0
- package/esm/src/guardrails/corpus/live-attacks.js +229 -0
- package/esm/src/guardrails/corpus/mod.d.ts +7 -0
- package/esm/src/guardrails/corpus/mod.js +4 -0
- package/esm/src/guardrails/corpus/secrets.d.ts +12 -0
- package/esm/src/guardrails/corpus/secrets.js +16 -0
- package/esm/src/guardrails/corpus/strings.d.ts +23 -0
- package/esm/src/guardrails/corpus/strings.js +29 -0
- package/esm/src/guardrails/corpus/types.d.ts +36 -0
- package/esm/src/guardrails/corpus/types.js +1 -0
- package/esm/src/guardrails/credential-rules.d.ts +36 -0
- package/esm/src/guardrails/credential-rules.js +3001 -0
- package/esm/src/guardrails/credential-scan.d.ts +45 -0
- package/esm/src/guardrails/credential-scan.js +172 -0
- package/esm/src/guardrails/destinations.d.ts +45 -0
- package/esm/src/guardrails/destinations.js +234 -0
- package/esm/src/guardrails/detect-at.d.ts +107 -0
- package/esm/src/guardrails/detect-at.js +434 -0
- package/esm/src/guardrails/detect-reply.d.ts +61 -0
- package/esm/src/guardrails/detect-reply.js +95 -0
- package/esm/src/guardrails/detect-stream.d.ts +33 -0
- package/esm/src/guardrails/detect-stream.js +77 -0
- package/esm/src/guardrails/detectors.d.ts +216 -0
- package/esm/src/guardrails/detectors.js +503 -0
- package/esm/src/guardrails/egress-automata.d.ts +12 -0
- package/esm/src/guardrails/egress-automata.js +25582 -0
- package/esm/src/guardrails/egress-compiler.d.ts +55 -0
- package/esm/src/guardrails/egress-compiler.js +431 -0
- package/esm/src/guardrails/egress-patterns.d.ts +26 -0
- package/esm/src/guardrails/egress-patterns.js +36 -0
- package/esm/src/guardrails/egress-stream.d.ts +123 -0
- package/esm/src/guardrails/egress-stream.js +774 -0
- package/esm/src/guardrails/egress-urls.d.ts +86 -0
- package/esm/src/guardrails/egress-urls.js +909 -0
- package/esm/src/guardrails/egress.d.ts +46 -0
- package/esm/src/guardrails/egress.js +124 -0
- package/esm/src/guardrails/error.d.ts +31 -0
- package/esm/src/guardrails/error.js +96 -0
- package/esm/src/guardrails/event-schemas.d.ts +100 -0
- package/esm/src/guardrails/event-schemas.js +48 -0
- package/esm/src/guardrails/events.d.ts +13 -0
- package/esm/src/guardrails/events.js +45 -0
- package/esm/src/guardrails/hints.d.ts +14 -0
- package/esm/src/guardrails/hints.js +57 -0
- package/esm/src/guardrails/hits.d.ts +14 -0
- package/esm/src/guardrails/hits.js +30 -0
- package/esm/src/guardrails/host-patterns.d.ts +85 -0
- package/esm/src/guardrails/host-patterns.js +173 -0
- package/esm/src/guardrails/injection-patterns.d.ts +20 -0
- package/esm/src/guardrails/injection-patterns.js +126 -0
- package/esm/src/guardrails/injection-reversed.d.ts +9 -0
- package/esm/src/guardrails/injection-reversed.js +49 -0
- package/esm/src/guardrails/injection.d.ts +30 -0
- package/esm/src/guardrails/injection.js +460 -0
- package/esm/src/guardrails/labels.d.ts +40 -0
- package/esm/src/guardrails/labels.js +874 -0
- package/esm/src/guardrails/live-outbound-gate.d.ts +55 -0
- package/esm/src/guardrails/live-outbound-gate.js +298 -0
- package/esm/src/guardrails/mod.d.ts +54 -0
- package/esm/src/guardrails/mod.js +34 -0
- package/esm/src/guardrails/network.d.ts +46 -0
- package/esm/src/guardrails/network.js +330 -0
- package/esm/src/guardrails/normalize.d.ts +20 -0
- package/esm/src/guardrails/normalize.js +165 -0
- package/esm/src/guardrails/policy.d.ts +4 -0
- package/esm/src/guardrails/policy.js +21 -0
- package/esm/src/guardrails/progressive-yield.d.ts +110 -0
- package/esm/src/guardrails/progressive-yield.js +281 -0
- package/esm/src/guardrails/prompt-echo.d.ts +40 -0
- package/esm/src/guardrails/prompt-echo.js +306 -0
- package/esm/src/guardrails/quota.d.ts +21 -0
- package/esm/src/guardrails/quota.js +80 -0
- package/esm/src/guardrails/rules.d.ts +47 -0
- package/esm/src/guardrails/rules.js +47 -0
- package/esm/src/guardrails/sanitize.d.ts +40 -0
- package/esm/src/guardrails/sanitize.js +160 -0
- package/esm/src/guardrails/sensitive-groups.d.ts +21 -0
- package/esm/src/guardrails/sensitive-groups.js +12 -0
- package/esm/src/guardrails/sensitive.d.ts +22 -0
- package/esm/src/guardrails/sensitive.js +234 -0
- package/esm/src/guardrails/serialize.d.ts +15 -0
- package/esm/src/guardrails/serialize.js +37 -0
- package/esm/src/guardrails/testing.d.ts +11 -0
- package/esm/src/guardrails/testing.js +10 -0
- package/esm/src/guardrails/theorem-error.d.ts +21 -0
- package/esm/src/guardrails/theorem-error.js +54 -0
- package/esm/src/guardrails/thought-guard.d.ts +76 -0
- package/esm/src/guardrails/thought-guard.js +664 -0
- package/esm/src/guardrails/tool-directives.d.ts +38 -0
- package/esm/src/guardrails/tool-directives.js +195 -0
- package/esm/src/guardrails/tool-leak.d.ts +26 -0
- package/esm/src/guardrails/tool-leak.js +45 -0
- package/esm/src/guardrails/tool-result.d.ts +92 -0
- package/esm/src/guardrails/tool-result.js +272 -0
- package/esm/src/guardrails/types.d.ts +250 -0
- package/esm/src/guardrails/types.js +58 -0
- package/esm/src/host/client-turn.d.ts +36 -0
- package/esm/src/host/client-turn.js +80 -0
- package/esm/src/host/mint-trace.d.ts +29 -0
- package/esm/src/host/mint-trace.js +60 -0
- package/esm/src/host/mod.d.ts +13 -0
- package/esm/src/host/mod.js +11 -0
- package/esm/src/host/readStreamingJsonStringField.d.ts +2 -0
- package/esm/src/host/readStreamingJsonStringField.js +27 -0
- package/esm/src/host/reply.d.ts +13 -0
- package/esm/src/host/reply.js +41 -0
- package/esm/src/interface/blocks.d.ts +11 -0
- package/esm/src/interface/blocks.js +283 -0
- package/esm/src/interface/composer-actions.d.ts +24 -0
- package/esm/src/interface/composer-actions.js +33 -0
- package/esm/src/interface/draft.d.ts +20 -0
- package/esm/src/interface/draft.js +39 -0
- package/esm/src/interface/from-profile.d.ts +24 -0
- package/esm/src/interface/from-profile.js +179 -0
- package/esm/src/interface/history.d.ts +46 -0
- package/esm/src/interface/history.js +278 -0
- package/esm/src/interface/inputs.d.ts +11 -0
- package/esm/src/interface/inputs.js +67 -0
- package/esm/src/interface/mod.d.ts +33 -0
- package/esm/src/interface/mod.js +22 -0
- package/esm/src/interface/models.d.ts +26 -0
- package/esm/src/interface/models.js +50 -0
- package/esm/src/interface/pending.d.ts +57 -0
- package/esm/src/interface/pending.js +152 -0
- package/esm/src/interface/profile-interface.d.ts +4 -0
- package/esm/src/interface/profile-interface.js +237 -0
- package/esm/src/interface/session.d.ts +71 -0
- package/esm/src/interface/session.js +128 -0
- package/esm/src/interface/stop.d.ts +13 -0
- package/esm/src/interface/stop.js +28 -0
- package/esm/src/interface/tool-calls.d.ts +32 -0
- package/esm/src/interface/tool-calls.js +127 -0
- package/esm/src/interface/tool-invoke.d.ts +6 -0
- package/esm/src/interface/tool-invoke.js +22 -0
- package/esm/src/interface/tool-media.d.ts +11 -0
- package/esm/src/interface/tool-media.js +113 -0
- package/esm/src/interface/types.d.ts +278 -0
- package/esm/src/interface/types.js +5 -0
- package/esm/src/kernel/api-catalog.d.ts +85 -0
- package/esm/src/kernel/api-catalog.js +229 -0
- package/esm/src/kernel/auth/credential-source.d.ts +23 -0
- package/esm/src/kernel/auth/credential-source.js +11 -0
- package/esm/src/kernel/auth/crypto.d.ts +56 -0
- package/esm/src/kernel/auth/crypto.js +163 -0
- package/esm/src/kernel/auth/mod.d.ts +5 -0
- package/esm/src/kernel/auth/mod.js +5 -0
- package/esm/src/kernel/auth/oauth.d.ts +18 -0
- package/esm/src/kernel/auth/oauth.js +439 -0
- package/esm/src/kernel/auth/scope-refusal.d.ts +9 -0
- package/esm/src/kernel/auth/scope-refusal.js +8 -0
- package/esm/src/kernel/auth/typed-secret.d.ts +13 -0
- package/esm/src/kernel/auth/typed-secret.js +23 -0
- package/esm/src/kernel/auth/types.d.ts +152 -0
- package/esm/src/kernel/auth/types.js +1 -0
- package/esm/src/kernel/checkpoint.d.ts +43 -0
- package/esm/src/kernel/checkpoint.js +58 -0
- package/esm/src/kernel/default-scope.d.ts +72 -0
- package/esm/src/kernel/default-scope.js +121 -0
- package/esm/src/kernel/engine/call-trace.d.ts +19 -0
- package/esm/src/kernel/engine/call-trace.js +29 -0
- package/esm/src/kernel/engine/compaction.d.ts +71 -0
- package/esm/src/kernel/engine/compaction.js +203 -0
- package/esm/src/kernel/engine/decision.d.ts +43 -0
- package/esm/src/kernel/engine/decision.js +439 -0
- package/esm/src/kernel/engine/embedding.d.ts +8 -0
- package/esm/src/kernel/engine/embedding.js +118 -0
- package/esm/src/kernel/engine/hash.d.ts +6 -0
- package/esm/src/kernel/engine/hash.js +22 -0
- package/esm/src/kernel/engine/live-inbound.d.ts +19 -0
- package/esm/src/kernel/engine/live-inbound.js +44 -0
- package/esm/src/kernel/engine/live-ingress.d.ts +15 -0
- package/esm/src/kernel/engine/live-ingress.js +42 -0
- package/esm/src/kernel/engine/record.d.ts +3 -0
- package/esm/src/kernel/engine/record.js +11 -0
- package/esm/src/kernel/engine/repair.d.ts +7 -0
- package/esm/src/kernel/engine/repair.js +36 -0
- package/esm/src/kernel/engine/runner/gates.d.ts +5 -0
- package/esm/src/kernel/engine/runner/gates.js +508 -0
- package/esm/src/kernel/engine/runner/mod.d.ts +29 -0
- package/esm/src/kernel/engine/runner/mod.js +625 -0
- package/esm/src/kernel/engine/runner/nested.d.ts +32 -0
- package/esm/src/kernel/engine/runner/nested.js +134 -0
- package/esm/src/kernel/engine/runner/schema-validation.d.ts +35 -0
- package/esm/src/kernel/engine/runner/schema-validation.js +179 -0
- package/esm/src/kernel/engine/runner/stages.d.ts +28 -0
- package/esm/src/kernel/engine/runner/stages.js +41 -0
- package/esm/src/kernel/engine/runner/state.d.ts +97 -0
- package/esm/src/kernel/engine/runner/state.js +50 -0
- package/esm/src/kernel/engine/runner/step-order.d.ts +27 -0
- package/esm/src/kernel/engine/runner/step-order.js +165 -0
- package/esm/src/kernel/engine/runner/steps.d.ts +14 -0
- package/esm/src/kernel/engine/runner/steps.js +519 -0
- package/esm/src/kernel/engine/runner/stream.d.ts +71 -0
- package/esm/src/kernel/engine/runner/stream.js +376 -0
- package/esm/src/kernel/engine/runner/usage.d.ts +45 -0
- package/esm/src/kernel/engine/runner/usage.js +148 -0
- package/esm/src/kernel/engine/session/mod.d.ts +33 -0
- package/esm/src/kernel/engine/session/mod.js +1182 -0
- package/esm/src/kernel/engine/session/session-trace.d.ts +115 -0
- package/esm/src/kernel/engine/session/session-trace.js +500 -0
- package/esm/src/kernel/engine/token-estimate.d.ts +23 -0
- package/esm/src/kernel/engine/token-estimate.js +38 -0
- package/esm/src/kernel/engine/tool-trace.d.ts +58 -0
- package/esm/src/kernel/engine/tool-trace.js +132 -0
- package/esm/src/kernel/engine/tree.d.ts +2 -0
- package/esm/src/kernel/engine/tree.js +17 -0
- package/esm/src/kernel/engine/turn-trace.d.ts +119 -0
- package/esm/src/kernel/engine/turn-trace.js +855 -0
- package/esm/src/kernel/engine/usage.d.ts +36 -0
- package/esm/src/kernel/engine/usage.js +151 -0
- package/esm/src/kernel/interaction-parts.d.ts +13 -0
- package/esm/src/kernel/interaction-parts.js +40 -0
- package/esm/src/kernel/json.d.ts +10 -0
- package/esm/src/kernel/json.js +3 -0
- package/esm/src/kernel/mod.d.ts +74 -0
- package/esm/src/kernel/mod.js +45 -0
- package/esm/src/kernel/profile-graph.d.ts +145 -0
- package/esm/src/kernel/profile-graph.js +158 -0
- package/esm/src/kernel/profile-presence.d.ts +18 -0
- package/esm/src/kernel/profile-presence.js +158 -0
- package/esm/src/kernel/profile-scope.d.ts +40 -0
- package/esm/src/kernel/profile-scope.js +274 -0
- package/esm/src/kernel/protocols/binding.d.ts +65 -0
- package/esm/src/kernel/protocols/binding.js +125 -0
- package/esm/src/kernel/protocols/limits.d.ts +15 -0
- package/esm/src/kernel/protocols/limits.js +28 -0
- package/esm/src/kernel/protocols/mod.d.ts +5 -0
- package/esm/src/kernel/protocols/mod.js +5 -0
- package/esm/src/kernel/protocols/prices.d.ts +36 -0
- package/esm/src/kernel/protocols/prices.js +37 -0
- package/esm/src/kernel/protocols/protocol.d.ts +253 -0
- package/esm/src/kernel/protocols/protocol.js +253 -0
- package/esm/src/kernel/protocols/provider.d.ts +108 -0
- package/esm/src/kernel/protocols/provider.js +143 -0
- package/esm/src/kernel/protocols/runtime.d.ts +55 -0
- package/esm/src/kernel/protocols/runtime.js +534 -0
- package/esm/src/kernel/provider-budget.d.ts +32 -0
- package/esm/src/kernel/provider-budget.js +262 -0
- package/esm/src/kernel/provider-dispatch.d.ts +13 -0
- package/esm/src/kernel/provider-dispatch.js +36 -0
- package/esm/src/kernel/provider-live.d.ts +7 -0
- package/esm/src/kernel/provider-live.js +189 -0
- package/esm/src/kernel/provider-runtime.d.ts +57 -0
- package/esm/src/kernel/provider-runtime.js +524 -0
- package/esm/src/kernel/registry/attachments.d.ts +46 -0
- package/esm/src/kernel/registry/attachments.js +220 -0
- package/esm/src/kernel/registry/catalog.d.ts +25 -0
- package/esm/src/kernel/registry/catalog.js +85 -0
- package/esm/src/kernel/registry/embedding-rules.d.ts +28 -0
- package/esm/src/kernel/registry/embedding-rules.js +66 -0
- package/esm/src/kernel/registry/ingress.d.ts +12 -0
- package/esm/src/kernel/registry/ingress.js +179 -0
- package/esm/src/kernel/registry/kernel-registry.d.ts +15 -0
- package/esm/src/kernel/registry/kernel-registry.js +15 -0
- package/esm/src/kernel/registry/profile-outputs.d.ts +3 -0
- package/esm/src/kernel/registry/profile-outputs.js +10 -0
- package/esm/src/kernel/registry/profiles.d.ts +116 -0
- package/esm/src/kernel/registry/profiles.js +773 -0
- package/esm/src/kernel/registry/provider-request.d.ts +6 -0
- package/esm/src/kernel/registry/provider-request.js +34 -0
- package/esm/src/kernel/registry/resolve.d.ts +17 -0
- package/esm/src/kernel/registry/resolve.js +238 -0
- package/esm/src/kernel/registry/schemas.d.ts +12 -0
- package/esm/src/kernel/registry/schemas.js +27 -0
- package/esm/src/kernel/registry/slot-fill.d.ts +7 -0
- package/esm/src/kernel/registry/slot-fill.js +22 -0
- package/esm/src/kernel/registry/sole-model.d.ts +2 -0
- package/esm/src/kernel/registry/sole-model.js +4 -0
- package/esm/src/kernel/registry/system-prompt.d.ts +8 -0
- package/esm/src/kernel/registry/system-prompt.js +32 -0
- package/esm/src/kernel/registry/system-role.d.ts +3 -0
- package/esm/src/kernel/registry/system-role.js +9 -0
- package/esm/src/kernel/registry/vault.d.ts +14 -0
- package/esm/src/kernel/registry/vault.js +1 -0
- package/esm/src/kernel/schema.d.ts +226 -0
- package/esm/src/kernel/schema.js +895 -0
- package/esm/src/kernel/scope.d.ts +29 -0
- package/esm/src/kernel/scope.js +45 -0
- package/esm/src/kernel/stages.d.ts +176 -0
- package/esm/src/kernel/stages.js +431 -0
- package/esm/src/kernel/stop.d.ts +116 -0
- package/esm/src/kernel/stop.js +137 -0
- package/esm/src/kernel/system-parts.d.ts +26 -0
- package/esm/src/kernel/system-parts.js +98 -0
- package/esm/src/kernel/tools/activity-label.d.ts +22 -0
- package/esm/src/kernel/tools/activity-label.js +435 -0
- package/esm/src/kernel/tools/agent.d.ts +66 -0
- package/esm/src/kernel/tools/agent.js +212 -0
- package/esm/src/kernel/tools/coordinate.d.ts +4 -0
- package/esm/src/kernel/tools/coordinate.js +165 -0
- package/esm/src/kernel/tools/events.d.ts +66 -0
- package/esm/src/kernel/tools/events.js +184 -0
- package/esm/src/kernel/tools/execute.d.ts +100 -0
- package/esm/src/kernel/tools/execute.js +1173 -0
- package/esm/src/kernel/tools/gate-answer.d.ts +60 -0
- package/esm/src/kernel/tools/gate-answer.js +65 -0
- package/esm/src/kernel/tools/handler-events.d.ts +45 -0
- package/esm/src/kernel/tools/handler-events.js +106 -0
- package/esm/src/kernel/tools/harness.d.ts +17 -0
- package/esm/src/kernel/tools/harness.js +43 -0
- package/esm/src/kernel/tools/http-limit.d.ts +27 -0
- package/esm/src/kernel/tools/http-limit.js +85 -0
- package/esm/src/kernel/tools/invoke.d.ts +32 -0
- package/esm/src/kernel/tools/invoke.js +282 -0
- package/esm/src/kernel/tools/mcp-sessions.d.ts +23 -0
- package/esm/src/kernel/tools/mcp-sessions.js +69 -0
- package/esm/src/kernel/tools/mod.d.ts +10 -0
- package/esm/src/kernel/tools/mod.js +7 -0
- package/esm/src/kernel/tools/model-text.d.ts +16 -0
- package/esm/src/kernel/tools/model-text.js +31 -0
- package/esm/src/kernel/tools/operation.d.ts +76 -0
- package/esm/src/kernel/tools/operation.js +100 -0
- package/esm/src/kernel/tools/page-answer.d.ts +8 -0
- package/esm/src/kernel/tools/page-answer.js +19 -0
- package/esm/src/kernel/tools/permission.d.ts +15 -0
- package/esm/src/kernel/tools/permission.js +38 -0
- package/esm/src/kernel/tools/project.d.ts +11 -0
- package/esm/src/kernel/tools/project.js +55 -0
- package/esm/src/kernel/tools/registry.d.ts +27 -0
- package/esm/src/kernel/tools/registry.js +143 -0
- package/esm/src/kernel/tools/remote.d.ts +144 -0
- package/esm/src/kernel/tools/remote.js +1125 -0
- package/esm/src/kernel/tools/resolve.d.ts +31 -0
- package/esm/src/kernel/tools/resolve.js +267 -0
- package/esm/src/kernel/tools/run.d.ts +28 -0
- package/esm/src/kernel/tools/run.js +86 -0
- package/esm/src/kernel/tools/schema.d.ts +10 -0
- package/esm/src/kernel/tools/schema.js +95 -0
- package/esm/src/kernel/tools/signed-in-fetch.d.ts +19 -0
- package/esm/src/kernel/tools/signed-in-fetch.js +45 -0
- package/esm/src/kernel/tools/stage-run.d.ts +116 -0
- package/esm/src/kernel/tools/stage-run.js +198 -0
- package/esm/src/kernel/tools/types.d.ts +497 -0
- package/esm/src/kernel/tools/types.js +1 -0
- package/esm/src/kernel/tools/unchecked-output.d.ts +14 -0
- package/esm/src/kernel/tools/unchecked-output.js +18 -0
- package/esm/src/kernel/turn-events.d.ts +776 -0
- package/esm/src/kernel/turn-events.js +543 -0
- package/esm/src/kernel/types.d.ts +1025 -0
- package/esm/src/kernel/types.js +1 -0
- package/esm/src/kernel/util/base64.d.ts +4 -0
- package/esm/src/kernel/util/base64.js +15 -0
- package/esm/src/kernel/util/exact-type.d.ts +31 -0
- package/esm/src/kernel/util/exact-type.js +1 -0
- package/esm/src/kernel/util/find-last.d.ts +3 -0
- package/esm/src/kernel/util/find-last.js +9 -0
- package/esm/src/kernel/util/mime.d.ts +2 -0
- package/esm/src/kernel/util/mime.js +4 -0
- package/esm/src/kernel/util/partial-json.d.ts +23 -0
- package/esm/src/kernel/util/partial-json.js +128 -0
- package/esm/src/kernel/util/record.d.ts +2 -0
- package/esm/src/kernel/util/record.js +4 -0
- package/esm/src/observability/destinations.d.ts +14 -0
- package/esm/src/observability/destinations.js +43 -0
- package/esm/src/observability/jsonl.d.ts +18 -0
- package/esm/src/observability/jsonl.js +110 -0
- package/esm/src/observability/mod.d.ts +22 -0
- package/esm/src/observability/mod.js +16 -0
- package/esm/src/observability/openinference.d.ts +11 -0
- package/esm/src/observability/openinference.js +205 -0
- package/esm/src/observability/otlp.d.ts +71 -0
- package/esm/src/observability/otlp.js +98 -0
- package/esm/src/observability/phoenix.d.ts +30 -0
- package/esm/src/observability/phoenix.js +71 -0
- package/esm/src/observability/policy.d.ts +17 -0
- package/esm/src/observability/policy.js +73 -0
- package/esm/src/observability/resolve-policy.d.ts +8 -0
- package/esm/src/observability/resolve-policy.js +64 -0
- package/esm/src/observability/spans.d.ts +22 -0
- package/esm/src/observability/spans.js +72 -0
- package/esm/src/observability/trace-catalog.d.ts +128 -0
- package/esm/src/observability/trace-catalog.js +906 -0
- package/esm/src/observability/trace-record.d.ts +23 -0
- package/esm/src/observability/trace-record.js +228 -0
- package/esm/src/observability/trace-schema.d.ts +57 -0
- package/esm/src/observability/trace-schema.js +59 -0
- package/esm/src/observability/trace-sink.d.ts +14 -0
- package/esm/src/observability/trace-sink.js +3 -0
- package/esm/src/observability/trace-span.d.ts +98 -0
- package/esm/src/observability/trace-span.js +201 -0
- package/esm/src/observability/trace.d.ts +13 -0
- package/esm/src/observability/trace.js +37 -0
- package/esm/src/observability/types.d.ts +109 -0
- package/esm/src/observability/types.js +3 -0
- package/esm/src/presets/google/speech-voices.d.ts +9 -0
- package/esm/src/presets/google/speech-voices.js +38 -0
- package/esm/src/presets/google-limits.d.ts +13 -0
- package/esm/src/presets/google-limits.js +18 -0
- package/esm/src/presets/google.d.ts +119 -0
- package/esm/src/presets/google.js +197 -0
- package/esm/src/presets/local.d.ts +26 -0
- package/esm/src/presets/local.js +30 -0
- package/esm/src/presets/mod.d.ts +11 -0
- package/esm/src/presets/mod.js +9 -0
- package/esm/src/presets/openrouter.d.ts +26 -0
- package/esm/src/presets/openrouter.js +31 -0
- package/esm/src/providers/embedding-limits.d.ts +7 -0
- package/esm/src/providers/embedding-limits.js +12 -0
- package/esm/src/providers/gemini/embedding-protocol.d.ts +8 -0
- package/esm/src/providers/gemini/embedding-protocol.js +125 -0
- package/esm/src/providers/gemini/interactions-protocol.d.ts +8 -0
- package/esm/src/providers/gemini/interactions-protocol.js +105 -0
- package/esm/src/providers/gemini/live-protocol.d.ts +3 -0
- package/esm/src/providers/gemini/live-protocol.js +191 -0
- package/esm/src/providers/gemini/prices.d.ts +3 -0
- package/esm/src/providers/gemini/prices.js +63 -0
- package/esm/src/providers/gemini/provider.d.ts +7 -0
- package/esm/src/providers/gemini/provider.js +39 -0
- package/esm/src/providers/google/api-error.d.ts +8 -0
- package/esm/src/providers/google/api-error.js +123 -0
- package/esm/src/providers/google/builtins.d.ts +14 -0
- package/esm/src/providers/google/builtins.js +57 -0
- package/esm/src/providers/google/grounding.d.ts +5 -0
- package/esm/src/providers/google/grounding.js +226 -0
- package/esm/src/providers/google/interactions/framing.d.ts +27 -0
- package/esm/src/providers/google/interactions/framing.js +304 -0
- package/esm/src/providers/google/interactions/steps.d.ts +12 -0
- package/esm/src/providers/google/interactions/steps.js +200 -0
- package/esm/src/providers/google/interactions/stream.d.ts +23 -0
- package/esm/src/providers/google/interactions/stream.js +230 -0
- package/esm/src/providers/google/live/framing.d.ts +61 -0
- package/esm/src/providers/google/live/framing.js +597 -0
- package/esm/src/providers/google/live/stream.d.ts +13 -0
- package/esm/src/providers/google/live/stream.js +57 -0
- package/esm/src/providers/google/thinking.d.ts +3 -0
- package/esm/src/providers/google/thinking.js +8 -0
- package/esm/src/providers/google/urls.d.ts +4 -0
- package/esm/src/providers/google/urls.js +4 -0
- package/esm/src/providers/google/usage.d.ts +7 -0
- package/esm/src/providers/google/usage.js +49 -0
- package/esm/src/providers/live.d.ts +6 -0
- package/esm/src/providers/live.js +6 -0
- package/esm/src/providers/local/embedding-protocol.d.ts +7 -0
- package/esm/src/providers/local/embedding-protocol.js +60 -0
- package/esm/src/providers/local/provider.d.ts +7 -0
- package/esm/src/providers/local/provider.js +28 -0
- package/esm/src/providers/mod.d.ts +32 -0
- package/esm/src/providers/mod.js +23 -0
- package/esm/src/providers/open-ai/protocol.d.ts +3 -0
- package/esm/src/providers/open-ai/protocol.js +189 -0
- package/esm/src/providers/openrouter/embedding-protocol.d.ts +6 -0
- package/esm/src/providers/openrouter/embedding-protocol.js +86 -0
- package/esm/src/providers/openrouter/image.d.ts +15 -0
- package/esm/src/providers/openrouter/image.js +34 -0
- package/esm/src/providers/openrouter/images-protocol.d.ts +3 -0
- package/esm/src/providers/openrouter/images-protocol.js +82 -0
- package/esm/src/providers/openrouter/openai/compat.d.ts +19 -0
- package/esm/src/providers/openrouter/openai/compat.js +162 -0
- package/esm/src/providers/openrouter/openai/image-payload.d.ts +9 -0
- package/esm/src/providers/openrouter/openai/image-payload.js +115 -0
- package/esm/src/providers/openrouter/openai/reasoning-state.d.ts +2 -0
- package/esm/src/providers/openrouter/openai/reasoning-state.js +31 -0
- package/esm/src/providers/openrouter/openai/usage.d.ts +8 -0
- package/esm/src/providers/openrouter/openai/usage.js +56 -0
- package/esm/src/providers/openrouter/protocol.d.ts +8 -0
- package/esm/src/providers/openrouter/protocol.js +171 -0
- package/esm/src/providers/openrouter/provider.d.ts +10 -0
- package/esm/src/providers/openrouter/provider.js +50 -0
- package/esm/src/providers/shared/carried-tool-files.d.ts +7 -0
- package/esm/src/providers/shared/carried-tool-files.js +43 -0
- package/esm/src/providers/shared/pcm.d.ts +14 -0
- package/esm/src/providers/shared/pcm.js +78 -0
- package/esm/src/providers/shared/protocol-events.d.ts +8 -0
- package/esm/src/providers/shared/protocol-events.js +20 -0
- package/esm/src/providers/shared/response-identity.d.ts +11 -0
- package/esm/src/providers/shared/response-identity.js +12 -0
- package/esm/src/providers/shared/retry.d.ts +17 -0
- package/esm/src/providers/shared/retry.js +90 -0
- package/esm/src/providers/shared/sse.d.ts +6 -0
- package/esm/src/providers/shared/sse.js +93 -0
- package/esm/src/providers/shared/structured-output.d.ts +4 -0
- package/esm/src/providers/shared/structured-output.js +11 -0
- package/esm/src/providers/shared/tool-args.d.ts +24 -0
- package/esm/src/providers/shared/tool-args.js +88 -0
- package/esm/src/providers/shared/tool-draft.d.ts +16 -0
- package/esm/src/providers/shared/tool-draft.js +14 -0
- package/esm/src/providers/shared/upstream-tap.d.ts +8 -0
- package/esm/src/providers/shared/upstream-tap.js +85 -0
- package/esm/src/providers/shared/upstream-tape.d.ts +6 -0
- package/esm/src/providers/shared/upstream-tape.js +88 -0
- package/esm/src/providers/system-one/protocol.d.ts +8 -0
- package/esm/src/providers/system-one/protocol.js +93 -0
- package/esm/src/providers/types.d.ts +51 -0
- package/esm/src/providers/types.js +2 -0
- package/esm/src/providers/typesafe/provider.d.ts +7 -0
- package/esm/src/providers/typesafe/provider.js +24 -0
- package/esm/src/surface/formats.d.ts +46 -0
- package/esm/src/surface/formats.js +178 -0
- package/esm/src/surface/mod.d.ts +16 -0
- package/esm/src/surface/mod.js +12 -0
- package/esm/src/surface/runtime.d.ts +44 -0
- package/esm/src/surface/runtime.js +441 -0
- package/esm/src/surface/tools.d.ts +9 -0
- package/esm/src/surface/tools.js +62 -0
- package/esm/src/surface/types.d.ts +110 -0
- package/esm/src/surface/types.js +4 -0
- package/package.json +100 -4
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AdvisoryLevel } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* What of a tool's text reads as an instruction to the agent: an order to set its instructions
|
|
4
|
+
* aside, the name of a tool it can call, an order, or a claim of authority. The last three count
|
|
5
|
+
* only beside a destination.
|
|
6
|
+
*/
|
|
7
|
+
declare const DIRECTIVE_SIGNALS: readonly ["override", "tool_name", "order", "authority"];
|
|
8
|
+
/** One of {@linkcode DIRECTIVE_SIGNALS}. */
|
|
9
|
+
type DirectiveSignal = (typeof DIRECTIVE_SIGNALS)[number];
|
|
10
|
+
/** A stretch of a tool's text that instructs the agent, and what about it does. */
|
|
11
|
+
interface Directive {
|
|
12
|
+
signal: DirectiveSignal;
|
|
13
|
+
start: number;
|
|
14
|
+
end: number;
|
|
15
|
+
}
|
|
16
|
+
/** One range for each run of overlapping `ranges`, in order. */
|
|
17
|
+
declare function mergedRanges(ranges: readonly [number, number][]): [number, number][];
|
|
18
|
+
/**
|
|
19
|
+
* Real indirect injection rarely names what it attacks, so this looks for content that behaves
|
|
20
|
+
* like an instruction: each order, claim of authority, tool name, and destination, where it sits
|
|
21
|
+
* in `text`. A page documenting an email API legitimately says "call send_email", so the
|
|
22
|
+
* detector that reads with this (`tool_instructions`) starts at `flag`.
|
|
23
|
+
*
|
|
24
|
+
* `callableTools` is the set the model can invoke this turn. A result naming one is the
|
|
25
|
+
* highest-precision signal: ordinary data has no reason to, and no generic filter can check it
|
|
26
|
+
* without the turn's registry.
|
|
27
|
+
*
|
|
28
|
+
* The text is read normalized. Where that changes it, each signal found is placed on the
|
|
29
|
+
* stretch of `text` it was read from (`normalizedView`).
|
|
30
|
+
*/
|
|
31
|
+
declare function directives(text: string, callableTools?: readonly string[]): Directive[];
|
|
32
|
+
/**
|
|
33
|
+
* How strongly a tool's text reads as instructions, by the `signals` found in it: `high` for an
|
|
34
|
+
* order to drop instructions, a callable tool's name, or two signals together.
|
|
35
|
+
*/
|
|
36
|
+
declare function advisoryLevel(signals: readonly (string | undefined)[]): AdvisoryLevel;
|
|
37
|
+
export type { Directive, DirectiveSignal };
|
|
38
|
+
export { advisoryLevel, DIRECTIVE_SIGNALS, directives, mergedRanges };
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { typoFolded } from './injection.js';
|
|
2
|
+
import { overrideFrame } from './injection-patterns.js';
|
|
3
|
+
import { normalizedView, normalizeForDetection } from './normalize.js';
|
|
4
|
+
/**
|
|
5
|
+
* Imperatives aimed at an agent rather than a reader.
|
|
6
|
+
*
|
|
7
|
+
* Bounded quantifiers throughout: tool results can be large, and an unbounded
|
|
8
|
+
* scanner on attacker-controlled bytes is a denial-of-service surface.
|
|
9
|
+
*/
|
|
10
|
+
const IMPERATIVES = [
|
|
11
|
+
/\b(?:next|final|remaining)\s+steps?\s*[:—-]/gi,
|
|
12
|
+
/\byou\s+(?:must|should|need\s+to|are\s+required\s+to)\s+(?:now\s+)?\w{2,20}/gi,
|
|
13
|
+
/\bto\s+(?:complete|finish|fulfil|fulfill)\s+(?:this|the)\s+\w{2,20}/gi,
|
|
14
|
+
/\b(?:call|invoke|run|execute)\s+(?:the\s+)?[\w.-]{2,40}\s+(?:tool|function)\b/gi,
|
|
15
|
+
/\b(?:send|forward|email|transfer|upload|post)\s+(?:the\s+|all\s+|your\s+)?[\w\s]{2,40}\s+to\s+\S{3,80}/gi,
|
|
16
|
+
/\b(?:delete|remove|drop|purge)\s+(?:the\s+|all\s+)?[\w\s]{2,40}\b/gi,
|
|
17
|
+
];
|
|
18
|
+
/**
|
|
19
|
+
* A concrete external destination — the thing exfiltration needs and ordinary
|
|
20
|
+
* process prose almost never carries.
|
|
21
|
+
*
|
|
22
|
+
* This is the discriminator. Directive language on its own is everywhere in
|
|
23
|
+
* legitimate tool output: documentation says "you must be an admin", support
|
|
24
|
+
* articles say "to remove a user", status reports say "the user has approved".
|
|
25
|
+
* Measured on a benign sample, directive signals alone produced false positives
|
|
26
|
+
* on most of it. Pairing a signal with a destination removed all of them.
|
|
27
|
+
*
|
|
28
|
+
* The pairing is close. A web page always carries an address somewhere, and a
|
|
29
|
+
* shop's page says "Remove This Item" and "if you need to exchange it, send us
|
|
30
|
+
* an email at …": of 20 real search results, a destination anywhere in the
|
|
31
|
+
* result flagged 3. An order or a claim counts when the destination is in what
|
|
32
|
+
* it orders, up to the next comma; a tool's name when one is in its sentence.
|
|
33
|
+
*/
|
|
34
|
+
const EXFIL_TARGET = /\b[\w.+-]{1,64}@[\w-]{1,63}(?:\.[\w-]{1,63}){1,4}\b|\bhttps?:\/\/\S{4,120}/i;
|
|
35
|
+
const EXFIL_TARGETS = new RegExp(EXFIL_TARGET.source, 'gi');
|
|
36
|
+
/** How far from a signal its sentence is read, where the text has no sentence ends. */
|
|
37
|
+
const SENTENCE_REACH = 240;
|
|
38
|
+
/** Whether a sentence of `text` ends at `at`: a line break, or `.`, `!`, or `?` before a space. */
|
|
39
|
+
function endsSentence(text, at) {
|
|
40
|
+
const ch = text[at];
|
|
41
|
+
if (ch === '\n')
|
|
42
|
+
return true;
|
|
43
|
+
return (ch === '.' || ch === '!' || ch === '?') && /\s/.test(text[at + 1] ?? ' ');
|
|
44
|
+
}
|
|
45
|
+
/** Whether a clause of `text` ends at `at`: where a sentence does, or at `,` or `;` before a space. */
|
|
46
|
+
function endsClause(text, at) {
|
|
47
|
+
const ch = text[at];
|
|
48
|
+
return endsSentence(text, at) || ((ch === ',' || ch === ';') && /\s/.test(text[at + 1] ?? ' '));
|
|
49
|
+
}
|
|
50
|
+
/** Where the stretch of `text` from `end` stops: at the first place `ends` holds, at most `SENTENCE_REACH` on. */
|
|
51
|
+
function reachFrom(text, end, ends) {
|
|
52
|
+
let to = end;
|
|
53
|
+
const ceiling = Math.min(text.length, end + SENTENCE_REACH);
|
|
54
|
+
while (to < ceiling && !ends(text, to))
|
|
55
|
+
to += 1;
|
|
56
|
+
return to;
|
|
57
|
+
}
|
|
58
|
+
/** Where the sentence of `text` holding `start` begins, at most `SENTENCE_REACH` back. */
|
|
59
|
+
function sentenceStart(text, start) {
|
|
60
|
+
let from = start;
|
|
61
|
+
const floor = Math.max(0, start - SENTENCE_REACH);
|
|
62
|
+
while (from > floor && !endsSentence(text, from - 1))
|
|
63
|
+
from -= 1;
|
|
64
|
+
return from;
|
|
65
|
+
}
|
|
66
|
+
/** Claims of permission or provenance the content cannot actually hold. */
|
|
67
|
+
const AUTHORITY = [
|
|
68
|
+
/\b(?:the\s+)?user\s+has\s+(?:already\s+)?(?:approved|authorised|authorized|confirmed|requested)\b/gi,
|
|
69
|
+
/\b(?:system|admin|operator)\s+(?:note|notice|message|override|instruction)s?\s*[:—-]/gi,
|
|
70
|
+
/\bon\s+behalf\s+of\s+the\s+(?:user|operator|admin)\b/gi,
|
|
71
|
+
/\bthis\s+(?:is|was)\s+(?:pre-?)?(?:approved|authorised|authorized)\b/gi,
|
|
72
|
+
];
|
|
73
|
+
/**
|
|
74
|
+
* An order to set instructions aside. Data has no reason to address the
|
|
75
|
+
* agent's instructions, so here the order counts when negated and with more
|
|
76
|
+
* words between its verb and its object than `injection-patterns.ts` allows.
|
|
77
|
+
*/
|
|
78
|
+
const OVERRIDE = [new RegExp(overrideFrame(5), 'gi')];
|
|
79
|
+
/** Every destination in `text` between `from` and `to`. */
|
|
80
|
+
function targetsIn(text, from, to) {
|
|
81
|
+
return [...text.slice(from, to).matchAll(EXFIL_TARGETS)].map((match) => [
|
|
82
|
+
from + match.index,
|
|
83
|
+
from + match.index + match[0].length,
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
/** Each match of `patterns` in `text` that names a destination before its clause ends, and the destinations it names. */
|
|
87
|
+
function directsOut(patterns, text) {
|
|
88
|
+
return patterns.flatMap((pattern) => [...text.matchAll(pattern)].flatMap((match) => {
|
|
89
|
+
const end = match.index + match[0].length;
|
|
90
|
+
const targets = targetsIn(text, match.index, reachFrom(text, end, endsClause));
|
|
91
|
+
return targets.length > 0 ? [[match.index, end], ...targets] : [];
|
|
92
|
+
}));
|
|
93
|
+
}
|
|
94
|
+
/** Each order in `text` to set instructions aside, read with its misspellings put right. */
|
|
95
|
+
function overrides(text) {
|
|
96
|
+
const folded = typoFolded(text);
|
|
97
|
+
return OVERRIDE.flatMap((pattern) => [...folded.text.matchAll(pattern)].map((match) => [folded.at(match.index), folded.at(match.index + match[0].length - 1) + 1]));
|
|
98
|
+
}
|
|
99
|
+
function isToolNameBoundary(ch) {
|
|
100
|
+
return ch === undefined || !/[a-z0-9_-]/i.test(ch);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Where `text` names `tool`, at word boundaries, in a sentence with a
|
|
104
|
+
* destination: the name, and the destinations of its sentence. A name inside a
|
|
105
|
+
* destination (`https://shop.example/search`) is part of the address. Registry
|
|
106
|
+
* input is not compiled as a pattern.
|
|
107
|
+
*/
|
|
108
|
+
function directsToTool(text, tool) {
|
|
109
|
+
if (tool.length < 3) {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
const haystack = text.toLowerCase();
|
|
113
|
+
const needle = tool.toLowerCase();
|
|
114
|
+
const found = [];
|
|
115
|
+
let at = haystack.indexOf(needle);
|
|
116
|
+
while (at >= 0) {
|
|
117
|
+
const end = at + needle.length;
|
|
118
|
+
if (isToolNameBoundary(haystack[at - 1]) && isToolNameBoundary(haystack[end])) {
|
|
119
|
+
const targets = targetsIn(text, sentenceStart(text, at), reachFrom(text, end, endsSentence));
|
|
120
|
+
const isInTarget = targets.some(([start, stop]) => start <= at && end <= stop);
|
|
121
|
+
if (targets.length > 0 && !isInTarget) {
|
|
122
|
+
found.push([at, end], ...targets);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
at = haystack.indexOf(needle, end);
|
|
126
|
+
}
|
|
127
|
+
return found;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* What of a tool's text reads as an instruction to the agent: an order to set its instructions
|
|
131
|
+
* aside, the name of a tool it can call, an order, or a claim of authority. The last three count
|
|
132
|
+
* only beside a destination.
|
|
133
|
+
*/
|
|
134
|
+
const DIRECTIVE_SIGNALS = ['override', 'tool_name', 'order', 'authority'];
|
|
135
|
+
/** One range for each run of overlapping `ranges`, in order. */
|
|
136
|
+
function mergedRanges(ranges) {
|
|
137
|
+
const runs = [];
|
|
138
|
+
for (const [start, end] of [...ranges].sort((a, b) => a[0] - b[0])) {
|
|
139
|
+
const last = runs.at(-1);
|
|
140
|
+
if (last && start <= last[1])
|
|
141
|
+
last[1] = Math.max(last[1], end);
|
|
142
|
+
else
|
|
143
|
+
runs.push([start, end]);
|
|
144
|
+
}
|
|
145
|
+
return runs;
|
|
146
|
+
}
|
|
147
|
+
/** One directive for each run of overlapping `ranges`. */
|
|
148
|
+
function directivesOf(signal, ranges) {
|
|
149
|
+
return mergedRanges(ranges).map(([start, end]) => ({ signal, start, end }));
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Real indirect injection rarely names what it attacks, so this looks for content that behaves
|
|
153
|
+
* like an instruction: each order, claim of authority, tool name, and destination, where it sits
|
|
154
|
+
* in `text`. A page documenting an email API legitimately says "call send_email", so the
|
|
155
|
+
* detector that reads with this (`tool_instructions`) starts at `flag`.
|
|
156
|
+
*
|
|
157
|
+
* `callableTools` is the set the model can invoke this turn. A result naming one is the
|
|
158
|
+
* highest-precision signal: ordinary data has no reason to, and no generic filter can check it
|
|
159
|
+
* without the turn's registry.
|
|
160
|
+
*
|
|
161
|
+
* The text is read normalized. Where that changes it, each signal found is placed on the
|
|
162
|
+
* stretch of `text` it was read from (`normalizedView`).
|
|
163
|
+
*/
|
|
164
|
+
function directives(text, callableTools = []) {
|
|
165
|
+
if (!text) {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
const normalized = normalizeForDetection(text);
|
|
169
|
+
const found = directivesOf('override', overrides(normalized));
|
|
170
|
+
// why: No destination, no exfiltration. Action-shaped attacks that carry no target
|
|
171
|
+
// are left to the taint gate, which does not depend on reading the content.
|
|
172
|
+
if (EXFIL_TARGET.test(normalized)) {
|
|
173
|
+
found.push(...directivesOf('tool_name', callableTools.flatMap((tool) => directsToTool(normalized, tool))), ...directivesOf('order', directsOut(IMPERATIVES, normalized)), ...directivesOf('authority', directsOut(AUTHORITY, normalized)));
|
|
174
|
+
}
|
|
175
|
+
if (normalized === text || found.length === 0)
|
|
176
|
+
return found;
|
|
177
|
+
const view = normalizedView(text);
|
|
178
|
+
return found.map(({ signal, start, end }) => ({
|
|
179
|
+
signal,
|
|
180
|
+
start: view.start[start] ?? text.length,
|
|
181
|
+
end: view.end[end - 1] ?? text.length,
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* How strongly a tool's text reads as instructions, by the `signals` found in it: `high` for an
|
|
186
|
+
* order to drop instructions, a callable tool's name, or two signals together.
|
|
187
|
+
*/
|
|
188
|
+
function advisoryLevel(signals) {
|
|
189
|
+
if (signals.length === 0) {
|
|
190
|
+
return 'none';
|
|
191
|
+
}
|
|
192
|
+
const kinds = new Set(signals);
|
|
193
|
+
return kinds.has('tool_name') || kinds.has('override') || kinds.size > 1 ? 'high' : 'elevated';
|
|
194
|
+
}
|
|
195
|
+
export { advisoryLevel, DIRECTIVE_SIGNALS, directives, mergedRanges };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `tool_leak` reads for: the names of the profile's own tools, each as a word of its own,
|
|
3
|
+
* and the names of their parameters in double quotes, as tool-call JSON writes its keys. A
|
|
4
|
+
* parameter is often a plain word (`city`, `query`), so outside quotes it is left alone.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
import type { RedactSpan } from '../observability/spans.js';
|
|
9
|
+
/** The names of a profile's own tools, and of their parameters. */
|
|
10
|
+
interface OwnTools {
|
|
11
|
+
names: readonly string[];
|
|
12
|
+
params: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
/** A literal of a name to find, and the pattern that reads a match of it. */
|
|
15
|
+
interface NamePattern {
|
|
16
|
+
literal: string;
|
|
17
|
+
regex: RegExp;
|
|
18
|
+
}
|
|
19
|
+
/** The pattern of each name of `own`. A tool's name starts and ends where a word does. */
|
|
20
|
+
declare function namePatterns(own: OwnTools): NamePattern[];
|
|
21
|
+
/** Every match of a name of `own` in `text`. */
|
|
22
|
+
declare function toolLeakSpans(text: string, own: OwnTools | undefined): RedactSpan[];
|
|
23
|
+
/** `own` without the names in `innocent`, or `undefined` when none is left. */
|
|
24
|
+
declare function ownToolsWithout(own: OwnTools | undefined, innocent?: readonly string[]): OwnTools | undefined;
|
|
25
|
+
export type { NamePattern, OwnTools };
|
|
26
|
+
export { namePatterns, ownToolsWithout, toolLeakSpans };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `tool_leak` reads for: the names of the profile's own tools, each as a word of its own,
|
|
3
|
+
* and the names of their parameters in double quotes, as tool-call JSON writes its keys. A
|
|
4
|
+
* parameter is often a plain word (`city`, `query`), so outside quotes it is left alone.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
const WORD = '[A-Za-z0-9_]';
|
|
9
|
+
function escaped(text) {
|
|
10
|
+
return text.replace(/[\\^$.*+?()[\]{}|/-]/g, '\\$&');
|
|
11
|
+
}
|
|
12
|
+
/** The pattern of each name of `own`. A tool's name starts and ends where a word does. */
|
|
13
|
+
function namePatterns(own) {
|
|
14
|
+
const after = `(?!${WORD})`;
|
|
15
|
+
const names = [...new Set(own.names)].filter(Boolean).map((name) => ({
|
|
16
|
+
literal: name,
|
|
17
|
+
regex: new RegExp(`(?<!${WORD})${escaped(name)}${after}`, 'g'),
|
|
18
|
+
}));
|
|
19
|
+
const keys = [...new Set(own.params)]
|
|
20
|
+
.filter(Boolean)
|
|
21
|
+
.flatMap((param) => [`"${param}"`, `\\"${param}\\"`])
|
|
22
|
+
.map((literal) => ({ literal, regex: new RegExp(escaped(literal), 'g') }));
|
|
23
|
+
return [...names, ...keys];
|
|
24
|
+
}
|
|
25
|
+
/** Every match of a name of `own` in `text`. */
|
|
26
|
+
function toolLeakSpans(text, own) {
|
|
27
|
+
if (!own)
|
|
28
|
+
return [];
|
|
29
|
+
return namePatterns(own).flatMap(({ literal, regex }) => text.includes(literal)
|
|
30
|
+
? [...text.matchAll(regex)].map(({ index, 0: match }) => ({
|
|
31
|
+
start: index,
|
|
32
|
+
end: index + match.length,
|
|
33
|
+
kind: 'tool',
|
|
34
|
+
}))
|
|
35
|
+
: []);
|
|
36
|
+
}
|
|
37
|
+
/** `own` without the names in `innocent`, or `undefined` when none is left. */
|
|
38
|
+
function ownToolsWithout(own, innocent = []) {
|
|
39
|
+
if (!own)
|
|
40
|
+
return undefined;
|
|
41
|
+
const names = own.names.filter((name) => !innocent.includes(name));
|
|
42
|
+
const params = own.params.filter((name) => !innocent.includes(name));
|
|
43
|
+
return names.length + params.length > 0 ? { names, params } : undefined;
|
|
44
|
+
}
|
|
45
|
+
export { namePatterns, ownToolsWithout, toolLeakSpans };
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { ToolBoundary } from './boundaries.js';
|
|
2
|
+
import { type DetectScope } from './detect-at.js';
|
|
3
|
+
import { type LabelOverrides } from './labels.js';
|
|
4
|
+
import type { AdvisoryLevel, GuardrailEvent, GuardrailHit, Provenance, ResolvedGuardrailPolicy, ToolOrigin, TurnTaint, Verdict } from './types.js';
|
|
5
|
+
/** The closing tag of the fence a tool result is wrapped in. */
|
|
6
|
+
declare const TOOL_CLOSE = "</tool_data>";
|
|
7
|
+
/** True when the tool's bytes come from outside the host. */
|
|
8
|
+
declare function isRemoteOrigin(origin: ToolOrigin): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* The origin is on the tag rather than in prose so a result cannot claim a
|
|
11
|
+
* friendlier provenance than it has by writing one into its own body.
|
|
12
|
+
*/
|
|
13
|
+
declare function wrapToolData(text: string, provenance: Provenance, advisory?: AdvisoryLevel, labels?: LabelOverrides): string;
|
|
14
|
+
/** A tool result after the guard: the text the model sees, any event, and the suspicious hits. */
|
|
15
|
+
export interface GuardedToolText {
|
|
16
|
+
/** Absent when a match blocks: the model does not read the text. */
|
|
17
|
+
text?: string;
|
|
18
|
+
/** Emitted when the guard did anything worth recording. */
|
|
19
|
+
event?: GuardrailEvent;
|
|
20
|
+
/**
|
|
21
|
+
* What `tool_instructions` found. Legitimate tool output is frequently
|
|
22
|
+
* instruction-shaped, so the detector starts at `flag`; whatever its action,
|
|
23
|
+
* these raise the turn's taint.
|
|
24
|
+
*/
|
|
25
|
+
suspicious?: GuardrailHit[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* `finding` is the tool's summary and `data` its structured payload; both reach
|
|
29
|
+
* the model, so both are guarded together rather than only the prose half.
|
|
30
|
+
*/
|
|
31
|
+
declare function composeToolText(finding: string, data: unknown): string;
|
|
32
|
+
/**
|
|
33
|
+
* Local host tools are still detected — a host tool reading a database returns
|
|
34
|
+
* data the host did not write — but only remote origins are fenced, because
|
|
35
|
+
* fencing a local tool's output would change prompts hosts have already tuned.
|
|
36
|
+
* `boundary` is absent for text that crosses none: a provider builtin's, or a
|
|
37
|
+
* failure already read at its own boundary.
|
|
38
|
+
*/
|
|
39
|
+
declare function guardToolResult(finding: string, data: unknown, provenance: Provenance, policy: ResolvedGuardrailPolicy, boundary: ToolBoundary | undefined, callableTools?: readonly string[], labels?: LabelOverrides): GuardedToolText;
|
|
40
|
+
/**
|
|
41
|
+
* A remote server authors its own error strings, so an unguarded failure message
|
|
42
|
+
* is the cleanest injection path across this boundary: it reaches the model
|
|
43
|
+
* verbatim and is framed by the kernel as a system report.
|
|
44
|
+
*/
|
|
45
|
+
declare function guardToolFailureText(message: string, provenance: Provenance, policy: ResolvedGuardrailPolicy, boundary: ToolBoundary | undefined): GuardedToolText;
|
|
46
|
+
/** What a call shows the person of one value, after it was read at `tool_shown_<kind>`. */
|
|
47
|
+
export interface ShownToolValue {
|
|
48
|
+
/** The value to show: every match set to `redact` replaced. Absent when a match blocks it. */
|
|
49
|
+
value?: unknown;
|
|
50
|
+
event?: GuardrailEvent;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A call's output, error, artifacts, and trace reach the person on the turn's events, apart from
|
|
54
|
+
* what the model read back: the host's server forwards them to the browser as they are. Each is
|
|
55
|
+
* read here at `tool_shown_<kind>`, so a key a tool returned is not shown. `readBack` is
|
|
56
|
+
* untouched: what the model read is settled at `tool_output_<kind>`.
|
|
57
|
+
*/
|
|
58
|
+
declare function guardShownValue(value: unknown, provenance: Provenance, policy: ResolvedGuardrailPolicy, boundary: ToolBoundary | undefined): ShownToolValue;
|
|
59
|
+
/** A tool call's arguments after they were read at their boundary. */
|
|
60
|
+
export interface InspectedToolArguments {
|
|
61
|
+
/** The arguments to call the tool with. Absent when a match blocks: the tool is not called. */
|
|
62
|
+
args?: unknown;
|
|
63
|
+
event?: GuardrailEvent;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Arguments are model-authored, so the risk is not instruction smuggling but
|
|
67
|
+
* exfiltration: a credential lifted from context and posted outward as a
|
|
68
|
+
* parameter. The whole call is read as it will be sent; `redact` then replaces
|
|
69
|
+
* the match inside each string it sits in, so the tool is called with the
|
|
70
|
+
* placeholder. `scope` is what the detectors of what is the profile's own
|
|
71
|
+
* read of the turn: the canary, and its system instruction.
|
|
72
|
+
*/
|
|
73
|
+
declare function inspectToolArguments(args: unknown, provenance: Provenance, policy: ResolvedGuardrailPolicy, boundary: ToolBoundary | undefined, scope?: DetectScope): InspectedToolArguments;
|
|
74
|
+
/** The guardrail event for a tool call verdict, or `undefined` when it was allowed. */
|
|
75
|
+
declare function toolCallEvent(verdict: Verdict, provenance: Provenance): GuardrailEvent | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* Only remote origins taint: a local host tool returns bytes the host's own code
|
|
78
|
+
* produced, and treating those as attacker-influenceable would make the gate
|
|
79
|
+
* useless in practice.
|
|
80
|
+
*/
|
|
81
|
+
declare function recordTaint(taint: TurnTaint | undefined, provenance: Provenance, suspicious?: GuardrailHit[]): TurnTaint;
|
|
82
|
+
/** True when the turn has read content from outside the host. */
|
|
83
|
+
declare function isTainted(taint: TurnTaint | undefined): boolean;
|
|
84
|
+
/** True when something the turn read looked like instructions. */
|
|
85
|
+
declare function isSuspicious(taint: TurnTaint | undefined): boolean;
|
|
86
|
+
/**
|
|
87
|
+
* A `flag` says the call is happening on a tainted turn and is worth recording; a
|
|
88
|
+
* `block` says the profile asked for it to be refused. Reporting happens whether
|
|
89
|
+
* or not enforcement is configured, so the risk is visible before a host opts in.
|
|
90
|
+
*/
|
|
91
|
+
declare function checkTaintGate(taint: TurnTaint | undefined, access: string, policy: ResolvedGuardrailPolicy, labels?: LabelOverrides): Verdict;
|
|
92
|
+
export { checkTaintGate, composeToolText, guardShownValue, guardToolFailureText, guardToolResult, inspectToolArguments, isRemoteOrigin, isSuspicious, isTainted, recordTaint, TOOL_CLOSE, toolCallEvent, wrapToolData, };
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import { mapStrings } from '../kernel/engine/tree.js';
|
|
2
|
+
import { detectAt, detectEvent, detectReads, } from './detect-at.js';
|
|
3
|
+
import { readValue } from './detect-reply.js';
|
|
4
|
+
import { labelText } from './labels.js';
|
|
5
|
+
import { DETECT_RULES, EGRESS_RULES, TOOL_RULES } from './rules.js';
|
|
6
|
+
import { textForScan } from './serialize.js';
|
|
7
|
+
import { advisoryLevel } from './tool-directives.js';
|
|
8
|
+
/** The closing tag of the fence a tool result is wrapped in. */
|
|
9
|
+
const TOOL_CLOSE = '</tool_data>';
|
|
10
|
+
const TOOL_OPEN = '<tool_data';
|
|
11
|
+
/** Origins whose bytes the host does not author and cannot vouch for. */
|
|
12
|
+
const REMOTE_ORIGINS = new Set(['http', 'mcp', 'delegated']);
|
|
13
|
+
/** True when the tool's bytes come from outside the host. */
|
|
14
|
+
function isRemoteOrigin(origin) {
|
|
15
|
+
return REMOTE_ORIGINS.has(origin);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Strip fence markers a tool result tried to forge before wrapping it.
|
|
19
|
+
* Linear scan — avoids polynomial regex on forged `<tool_data…>` runs.
|
|
20
|
+
*/
|
|
21
|
+
function stripToolFences(text) {
|
|
22
|
+
const lower = text.toLowerCase();
|
|
23
|
+
let out = '';
|
|
24
|
+
let i = 0;
|
|
25
|
+
while (i < text.length) {
|
|
26
|
+
const openAt = lower.indexOf(TOOL_OPEN, i);
|
|
27
|
+
const closeAt = lower.indexOf(TOOL_CLOSE, i);
|
|
28
|
+
let next = -1;
|
|
29
|
+
let kind = null;
|
|
30
|
+
if (openAt >= 0 && (closeAt < 0 || openAt <= closeAt)) {
|
|
31
|
+
next = openAt;
|
|
32
|
+
kind = 'open';
|
|
33
|
+
}
|
|
34
|
+
else if (closeAt >= 0) {
|
|
35
|
+
next = closeAt;
|
|
36
|
+
kind = 'close';
|
|
37
|
+
}
|
|
38
|
+
if (next < 0 || kind === null) {
|
|
39
|
+
out += text.slice(i);
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
out += text.slice(i, next);
|
|
43
|
+
if (kind === 'close') {
|
|
44
|
+
i = next + TOOL_CLOSE.length;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const gt = text.indexOf('>', next + TOOL_OPEN.length);
|
|
48
|
+
if (gt < 0) {
|
|
49
|
+
out += text.slice(next);
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
i = gt + 1;
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The origin is on the tag rather than in prose so a result cannot claim a
|
|
58
|
+
* friendlier provenance than it has by writing one into its own body.
|
|
59
|
+
*/
|
|
60
|
+
function wrapToolData(text, provenance, advisory = 'none', labels) {
|
|
61
|
+
const guidance = labelText('advisory.guidance', {}, labels).trim();
|
|
62
|
+
const attrs = `tool="${provenance.tool}" origin="${provenance.origin}"` +
|
|
63
|
+
(advisory === 'none' ? '' : ` advisory="${advisory}"`);
|
|
64
|
+
const notice = advisory === 'none'
|
|
65
|
+
? ''
|
|
66
|
+
: `${advisoryNotice(advisory, labels)}${guidance ? ` ${guidance}` : ''}\n`;
|
|
67
|
+
return `<${'tool_data'} ${attrs}>\n${notice}${stripToolFences(text)}\n${TOOL_CLOSE}`;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Deliberately an observation, not an instruction: what the agent should do about
|
|
71
|
+
* it is product behaviour, supplied by the host as labels `advisory.guidance`. Emitted
|
|
72
|
+
* only when signals fired, so it stays rare enough to carry weight — a warning on
|
|
73
|
+
* every fetch is one the model learns to skip.
|
|
74
|
+
*/
|
|
75
|
+
function advisoryNotice(advisory, labels) {
|
|
76
|
+
const key = advisory === 'high' ? 'advisory.notice_high' : 'advisory.notice_elevated';
|
|
77
|
+
return labelText(key, {}, labels);
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* `finding` is the tool's summary and `data` its structured payload; both reach
|
|
81
|
+
* the model, so both are guarded together rather than only the prose half.
|
|
82
|
+
*/
|
|
83
|
+
function composeToolText(finding, data) {
|
|
84
|
+
if (data === undefined) {
|
|
85
|
+
return finding;
|
|
86
|
+
}
|
|
87
|
+
const rendered = textForScan(data);
|
|
88
|
+
if (rendered.unscannable) {
|
|
89
|
+
return finding;
|
|
90
|
+
}
|
|
91
|
+
return `${finding}\n${rendered.text}`;
|
|
92
|
+
}
|
|
93
|
+
const NOTHING_FOUND = { action: 'allow', hits: [] };
|
|
94
|
+
/** `text` read at `boundary`, or passed as it is when the text crosses none. */
|
|
95
|
+
function readAt(text, boundary, detect, scope) {
|
|
96
|
+
return boundary ? detectAt(text, boundary, detect, scope) : { ...NOTHING_FOUND, text };
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Local host tools are still detected — a host tool reading a database returns
|
|
100
|
+
* data the host did not write — but only remote origins are fenced, because
|
|
101
|
+
* fencing a local tool's output would change prompts hosts have already tuned.
|
|
102
|
+
* `boundary` is absent for text that crosses none: a provider builtin's, or a
|
|
103
|
+
* failure already read at its own boundary.
|
|
104
|
+
*/
|
|
105
|
+
function guardToolResult(finding, data, provenance, policy, boundary, callableTools = [], labels) {
|
|
106
|
+
const composed = composeToolText(finding, data);
|
|
107
|
+
const detected = readAt(composed, boundary, policy.detect, { callable: callableTools });
|
|
108
|
+
const suspicious = detected.hits.filter(({ rule }) => rule === DETECT_RULES.tool_instructions);
|
|
109
|
+
const remote = isRemoteOrigin(provenance.origin);
|
|
110
|
+
const event = detected.action === 'allow'
|
|
111
|
+
? undefined
|
|
112
|
+
: {
|
|
113
|
+
stage: 'tool_result',
|
|
114
|
+
...(boundary ? { boundary } : {}),
|
|
115
|
+
trust: 'untrusted',
|
|
116
|
+
action: detected.action,
|
|
117
|
+
hits: detected.hits,
|
|
118
|
+
provenance,
|
|
119
|
+
};
|
|
120
|
+
const found = {
|
|
121
|
+
...(suspicious.length > 0 ? { suspicious } : {}),
|
|
122
|
+
...(event ? { event } : {}),
|
|
123
|
+
};
|
|
124
|
+
if (detected.text === undefined)
|
|
125
|
+
return found;
|
|
126
|
+
const fenced = remote
|
|
127
|
+
? wrapToolData(detected.text, provenance, advisoryLevel(suspicious.map(({ signal }) => signal)), labels)
|
|
128
|
+
: detected.text;
|
|
129
|
+
return { text: fenced, ...found };
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* A remote server authors its own error strings, so an unguarded failure message
|
|
133
|
+
* is the cleanest injection path across this boundary: it reaches the model
|
|
134
|
+
* verbatim and is framed by the kernel as a system report.
|
|
135
|
+
*/
|
|
136
|
+
function guardToolFailureText(message, provenance, policy, boundary) {
|
|
137
|
+
const detected = readAt(stripToolFences(message), boundary, policy.detect);
|
|
138
|
+
const event = boundary ? detectEvent(boundary, detected, provenance) : undefined;
|
|
139
|
+
return {
|
|
140
|
+
...(detected.text === undefined ? {} : { text: detected.text }),
|
|
141
|
+
...(event ? { event } : {}),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* A call's output, error, artifacts, and trace reach the person on the turn's events, apart from
|
|
146
|
+
* what the model read back: the host's server forwards them to the browser as they are. Each is
|
|
147
|
+
* read here at `tool_shown_<kind>`, so a key a tool returned is not shown. `readBack` is
|
|
148
|
+
* untouched: what the model read is settled at `tool_output_<kind>`.
|
|
149
|
+
*/
|
|
150
|
+
function guardShownValue(value, provenance, policy, boundary) {
|
|
151
|
+
if (!boundary || value === undefined)
|
|
152
|
+
return { value };
|
|
153
|
+
const found = readValue(value, boundary, policy.detect);
|
|
154
|
+
const event = detectEvent(boundary, found, provenance);
|
|
155
|
+
return {
|
|
156
|
+
...(found.action === 'block' ? {} : { value: found.value }),
|
|
157
|
+
...(event ? { event } : {}),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Arguments are model-authored, so the risk is not instruction smuggling but
|
|
162
|
+
* exfiltration: a credential lifted from context and posted outward as a
|
|
163
|
+
* parameter. The whole call is read as it will be sent; `redact` then replaces
|
|
164
|
+
* the match inside each string it sits in, so the tool is called with the
|
|
165
|
+
* placeholder. `scope` is what the detectors of what is the profile's own
|
|
166
|
+
* read of the turn: the canary, and its system instruction.
|
|
167
|
+
*/
|
|
168
|
+
function inspectToolArguments(args, provenance, policy, boundary, scope = {}) {
|
|
169
|
+
if (!(boundary && detectReads([boundary], policy.detect))) {
|
|
170
|
+
return { args };
|
|
171
|
+
}
|
|
172
|
+
const stopped = (hits) => {
|
|
173
|
+
const event = detectEvent(boundary, { action: 'block', hits }, provenance);
|
|
174
|
+
return event ? { event } : {};
|
|
175
|
+
};
|
|
176
|
+
const rendered = textForScan(args);
|
|
177
|
+
if (rendered.unscannable) {
|
|
178
|
+
// why: Cannot inspect it, so cannot vouch for it. Fail closed.
|
|
179
|
+
return stopped([{ rule: EGRESS_RULES.unscannable, severity: 'high' }]);
|
|
180
|
+
}
|
|
181
|
+
const detected = detectAt(rendered.text, boundary, policy.detect, scope);
|
|
182
|
+
if (detected.action === 'block')
|
|
183
|
+
return stopped(detected.hits);
|
|
184
|
+
const event = detectEvent(boundary, detected, provenance);
|
|
185
|
+
if (detected.action !== 'redact')
|
|
186
|
+
return { args, ...(event ? { event } : {}) };
|
|
187
|
+
const safe = mapStrings(args, (text) => detectAt(text, boundary, policy.detect, scope).text ?? '');
|
|
188
|
+
const left = detectAt(textForScan(safe).text, boundary, policy.detect, scope).action;
|
|
189
|
+
// why: A match in a key, or one that runs across values, has no string to replace: the tool is not called.
|
|
190
|
+
if (left === 'redact' || left === 'block')
|
|
191
|
+
return stopped(detected.hits);
|
|
192
|
+
return { args: safe, ...(event ? { event } : {}) };
|
|
193
|
+
}
|
|
194
|
+
/** The guardrail event for a tool call verdict, or `undefined` when it was allowed. */
|
|
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
|
+
* Only remote origins taint: a local host tool returns bytes the host's own code
|
|
209
|
+
* produced, and treating those as attacker-influenceable would make the gate
|
|
210
|
+
* useless in practice.
|
|
211
|
+
*/
|
|
212
|
+
function recordTaint(taint, provenance, suspicious = []) {
|
|
213
|
+
const sources = taint?.sources ?? [];
|
|
214
|
+
const prior = taint?.suspicious ?? [];
|
|
215
|
+
if (!isRemoteOrigin(provenance.origin)) {
|
|
216
|
+
return { sources, suspicious: prior };
|
|
217
|
+
}
|
|
218
|
+
return { sources: [...sources, provenance], suspicious: [...prior, ...suspicious] };
|
|
219
|
+
}
|
|
220
|
+
/** True when the turn has read content from outside the host. */
|
|
221
|
+
function isTainted(taint) {
|
|
222
|
+
return (taint?.sources.length ?? 0) > 0;
|
|
223
|
+
}
|
|
224
|
+
/** True when something the turn read looked like instructions. */
|
|
225
|
+
function isSuspicious(taint) {
|
|
226
|
+
return (taint?.suspicious.length ?? 0) > 0;
|
|
227
|
+
}
|
|
228
|
+
/** Capability rank, so a single threshold can express "this and anything worse". */
|
|
229
|
+
const CAPABILITY_RANK = {
|
|
230
|
+
'read-only': 0,
|
|
231
|
+
'read-write': 1,
|
|
232
|
+
destructive: 2,
|
|
233
|
+
};
|
|
234
|
+
const GATE_RANK = {
|
|
235
|
+
off: Number.POSITIVE_INFINITY,
|
|
236
|
+
destructive: 2,
|
|
237
|
+
write: 1,
|
|
238
|
+
};
|
|
239
|
+
/**
|
|
240
|
+
* A `flag` says the call is happening on a tainted turn and is worth recording; a
|
|
241
|
+
* `block` says the profile asked for it to be refused. Reporting happens whether
|
|
242
|
+
* or not enforcement is configured, so the risk is visible before a host opts in.
|
|
243
|
+
*/
|
|
244
|
+
function checkTaintGate(taint, access, policy, labels) {
|
|
245
|
+
if (!isTainted(taint)) {
|
|
246
|
+
return { action: 'allow' };
|
|
247
|
+
}
|
|
248
|
+
const rank = CAPABILITY_RANK[access] ?? 0;
|
|
249
|
+
if (rank === 0) {
|
|
250
|
+
return { action: 'allow' };
|
|
251
|
+
}
|
|
252
|
+
const suspicious = isSuspicious(taint);
|
|
253
|
+
const hits = [
|
|
254
|
+
{
|
|
255
|
+
rule: suspicious ? TOOL_RULES.steeredTurn : TOOL_RULES.taintedTurn,
|
|
256
|
+
severity: 'high',
|
|
257
|
+
},
|
|
258
|
+
];
|
|
259
|
+
// why: Gated on origin alone. Whether the content looked directive changes the rule
|
|
260
|
+
// reported, never whether the call is refused.
|
|
261
|
+
if (rank < GATE_RANK[policy.taint?.afterRemoteRead ?? 'off']) {
|
|
262
|
+
return { action: 'flag', hits };
|
|
263
|
+
}
|
|
264
|
+
const read = taint?.sources.map((s) => s.tool).join(', ') ?? '';
|
|
265
|
+
const reason = labelText(suspicious ? 'taint.reason_steered' : 'taint.reason_tainted', {}, labels);
|
|
266
|
+
return {
|
|
267
|
+
action: 'block',
|
|
268
|
+
hits,
|
|
269
|
+
rejection: labelText('taint.blocked', { access, sources: read, reason }, labels),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
export { checkTaintGate, composeToolText, guardShownValue, guardToolFailureText, guardToolResult, inspectToolArguments, isRemoteOrigin, isSuspicious, isTainted, recordTaint, TOOL_CLOSE, toolCallEvent, wrapToolData, };
|