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.
Files changed (305) hide show
  1. package/README.md +241 -98
  2. package/esm/mod.d.ts +57 -28
  3. package/esm/mod.js +43 -23
  4. package/esm/src/cli/commands/bench.js +18 -18
  5. package/esm/src/cli/commands/fuzz-canary.d.ts +13 -0
  6. package/esm/src/cli/commands/fuzz-canary.js +191 -0
  7. package/esm/src/cli/commands/fuzz-guardrails.d.ts +3 -5
  8. package/esm/src/cli/commands/fuzz-guardrails.js +4 -581
  9. package/esm/src/cli/commands/guardrails-eval.d.ts +14 -0
  10. package/esm/src/cli/commands/guardrails-eval.js +15 -0
  11. package/esm/src/cli/commands/profile.js +35 -15
  12. package/esm/src/cli/commands/run.d.ts +3 -0
  13. package/esm/src/cli/commands/run.js +23 -32
  14. package/esm/src/cli/commands/test.d.ts +10 -1
  15. package/esm/src/cli/commands/test.js +34 -34
  16. package/esm/src/cli/event-log.d.ts +19 -0
  17. package/esm/src/cli/event-log.js +147 -0
  18. package/esm/src/cli/index.js +57 -11
  19. package/esm/src/cli/matrix/synthesizer.d.ts +10 -12
  20. package/esm/src/cli/matrix/synthesizer.js +45 -118
  21. package/esm/src/guardrails/canary-gate.d.ts +21 -0
  22. package/esm/src/guardrails/canary-gate.js +32 -0
  23. package/esm/src/guardrails/canary.d.ts +34 -0
  24. package/esm/src/guardrails/canary.js +150 -0
  25. package/esm/src/guardrails/corpus/canary-egress-attacks.d.ts +17 -0
  26. package/esm/src/guardrails/corpus/canary-egress-attacks.js +151 -0
  27. package/esm/src/guardrails/corpus/fuzz-inbound.d.ts +11 -0
  28. package/esm/src/guardrails/corpus/fuzz-inbound.js +213 -0
  29. package/esm/src/guardrails/corpus/inbound-payloads.d.ts +10 -0
  30. package/esm/src/guardrails/corpus/inbound-payloads.js +125 -0
  31. package/esm/src/guardrails/corpus/live-attacks.d.ts +20 -0
  32. package/esm/src/guardrails/corpus/live-attacks.js +231 -0
  33. package/esm/src/guardrails/corpus/mod.d.ts +14 -0
  34. package/esm/src/guardrails/corpus/mod.js +11 -0
  35. package/esm/src/guardrails/corpus/secrets.d.ts +17 -0
  36. package/esm/src/guardrails/corpus/secrets.js +17 -0
  37. package/esm/src/guardrails/corpus/strings.d.ts +28 -0
  38. package/esm/src/guardrails/corpus/strings.js +34 -0
  39. package/esm/src/guardrails/corpus/types.d.ts +38 -0
  40. package/esm/src/guardrails/corpus/types.js +6 -0
  41. package/esm/src/guardrails/egress.d.ts +32 -0
  42. package/esm/src/guardrails/egress.js +87 -0
  43. package/esm/src/guardrails/error.d.ts +14 -23
  44. package/esm/src/guardrails/error.js +87 -76
  45. package/esm/src/guardrails/eval/corpus.d.ts +108 -0
  46. package/esm/src/guardrails/eval/corpus.js +978 -0
  47. package/esm/src/guardrails/eval/mod.d.ts +51 -0
  48. package/esm/src/guardrails/eval/mod.js +133 -0
  49. package/esm/src/guardrails/eval/score.d.ts +66 -0
  50. package/esm/src/guardrails/eval/score.js +114 -0
  51. package/esm/src/guardrails/events.d.ts +25 -0
  52. package/esm/src/guardrails/events.js +56 -0
  53. package/esm/src/guardrails/hits.d.ts +24 -0
  54. package/esm/src/guardrails/hits.js +45 -0
  55. package/esm/src/guardrails/injection.js +28 -5
  56. package/esm/src/guardrails/lexicon.d.ts +39 -0
  57. package/esm/src/guardrails/lexicon.js +200 -0
  58. package/esm/src/guardrails/live-outbound-gate.d.ts +41 -0
  59. package/esm/src/guardrails/live-outbound-gate.js +222 -0
  60. package/esm/src/guardrails/mod.d.ts +30 -6
  61. package/esm/src/guardrails/mod.js +20 -5
  62. package/esm/src/guardrails/network.d.ts +19 -0
  63. package/esm/src/guardrails/network.js +234 -0
  64. package/esm/src/guardrails/policy.d.ts +35 -0
  65. package/esm/src/guardrails/policy.js +50 -0
  66. package/esm/src/guardrails/progressive-yield.d.ts +51 -0
  67. package/esm/src/guardrails/progressive-yield.js +98 -0
  68. package/esm/src/guardrails/quota.d.ts +17 -3
  69. package/esm/src/guardrails/quota.js +18 -4
  70. package/esm/src/guardrails/sanitize.d.ts +45 -19
  71. package/esm/src/guardrails/sanitize.js +177 -94
  72. package/esm/src/guardrails/sensitive.js +2 -1
  73. package/esm/src/guardrails/serialize.d.ts +35 -0
  74. package/esm/src/guardrails/serialize.js +58 -0
  75. package/esm/src/guardrails/testing.d.ts +17 -0
  76. package/esm/src/guardrails/testing.js +13 -0
  77. package/esm/src/guardrails/theorum-error.d.ts +12 -0
  78. package/esm/src/guardrails/theorum-error.js +15 -0
  79. package/esm/src/guardrails/tool-directives.d.ts +48 -0
  80. package/esm/src/guardrails/tool-directives.js +124 -0
  81. package/esm/src/guardrails/tool-result.d.ts +93 -0
  82. package/esm/src/guardrails/tool-result.js +276 -0
  83. package/esm/src/guardrails/types.d.ts +291 -0
  84. package/esm/src/guardrails/types.js +72 -0
  85. package/esm/src/host/client-turn.d.ts +19 -0
  86. package/esm/src/host/client-turn.js +36 -0
  87. package/esm/src/host/mint-trace.d.ts +1 -1
  88. package/esm/src/host/mod.d.ts +5 -3
  89. package/esm/src/host/mod.js +4 -3
  90. package/esm/src/kernel/auth/crypto.d.ts +42 -0
  91. package/esm/src/kernel/auth/crypto.js +106 -0
  92. package/esm/src/kernel/auth/mod.d.ts +11 -0
  93. package/esm/src/kernel/auth/mod.js +11 -0
  94. package/esm/src/kernel/auth/oauth.d.ts +47 -0
  95. package/esm/src/kernel/auth/oauth.js +278 -0
  96. package/esm/src/kernel/auth/types.d.ts +133 -0
  97. package/esm/src/kernel/auth/types.js +13 -0
  98. package/esm/src/kernel/engine/delta.d.ts +24 -2
  99. package/esm/src/kernel/engine/delta.js +478 -39
  100. package/esm/src/kernel/engine/live-inbound.d.ts +21 -0
  101. package/esm/src/kernel/engine/live-inbound.js +31 -0
  102. package/esm/src/kernel/engine/live-ingress.d.ts +19 -0
  103. package/esm/src/kernel/engine/live-ingress.js +47 -0
  104. package/esm/src/kernel/engine/repair.js +13 -12
  105. package/esm/src/kernel/engine/runner/gates.d.ts +1 -1
  106. package/esm/src/kernel/engine/runner/gates.js +130 -43
  107. package/esm/src/kernel/engine/runner/mod.d.ts +6 -4
  108. package/esm/src/kernel/engine/runner/mod.js +192 -53
  109. package/esm/src/kernel/engine/runner/schema-validation.js +3 -3
  110. package/esm/src/kernel/engine/runner/stages.d.ts +39 -0
  111. package/esm/src/kernel/engine/runner/stages.js +89 -0
  112. package/esm/src/kernel/engine/runner/state.d.ts +31 -0
  113. package/esm/src/kernel/engine/runner/steps.d.ts +1 -1
  114. package/esm/src/kernel/engine/runner/steps.js +244 -43
  115. package/esm/src/kernel/engine/runner/stream.d.ts +9 -3
  116. package/esm/src/kernel/engine/runner/stream.js +140 -44
  117. package/esm/src/kernel/engine/session/mod.d.ts +25 -0
  118. package/esm/src/kernel/engine/session/mod.js +557 -0
  119. package/esm/src/kernel/interaction-parts.d.ts +14 -0
  120. package/esm/src/kernel/interaction-parts.js +23 -0
  121. package/esm/src/kernel/mod.d.ts +21 -10
  122. package/esm/src/kernel/mod.js +11 -8
  123. package/esm/src/kernel/profile-graph.d.ts +159 -0
  124. package/esm/src/kernel/profile-graph.js +156 -0
  125. package/esm/src/kernel/registry/attachments.d.ts +12 -10
  126. package/esm/src/kernel/registry/attachments.js +33 -27
  127. package/esm/src/kernel/registry/catalog.d.ts +25 -24
  128. package/esm/src/kernel/registry/catalog.js +60 -101
  129. package/esm/src/kernel/registry/ingress.d.ts +9 -4
  130. package/esm/src/kernel/registry/ingress.js +97 -75
  131. package/esm/src/kernel/registry/profile-outputs.d.ts +4 -0
  132. package/esm/src/kernel/registry/profile-outputs.js +8 -0
  133. package/esm/src/kernel/registry/profiles.d.ts +55 -12
  134. package/esm/src/kernel/registry/profiles.js +413 -73
  135. package/esm/src/kernel/registry/provider-request.js +13 -7
  136. package/esm/src/kernel/registry/resolve.d.ts +8 -8
  137. package/esm/src/kernel/registry/resolve.js +169 -154
  138. package/esm/src/kernel/registry/schemas.js +1 -1
  139. package/esm/src/kernel/registry/sole-model.d.ts +8 -0
  140. package/esm/src/kernel/registry/sole-model.js +10 -0
  141. package/esm/src/kernel/registry/system-prompt.d.ts +10 -0
  142. package/esm/src/kernel/registry/system-prompt.js +40 -0
  143. package/esm/src/kernel/registry/system-role.d.ts +8 -0
  144. package/esm/src/kernel/registry/system-role.js +14 -0
  145. package/esm/src/kernel/registry/vault.d.ts +12 -7
  146. package/esm/src/kernel/registry/vault.js +32 -10
  147. package/esm/src/kernel/schema.d.ts +231 -0
  148. package/esm/src/kernel/schema.js +607 -0
  149. package/esm/src/kernel/stages.d.ts +175 -0
  150. package/esm/src/kernel/stages.js +476 -0
  151. package/esm/src/kernel/stop.d.ts +78 -19
  152. package/esm/src/kernel/stop.js +51 -16
  153. package/esm/src/kernel/tools/events.d.ts +41 -0
  154. package/esm/src/kernel/tools/events.js +71 -0
  155. package/esm/src/kernel/tools/execute.d.ts +84 -0
  156. package/esm/src/kernel/tools/execute.js +614 -0
  157. package/esm/src/kernel/tools/harness.d.ts +8 -0
  158. package/esm/src/kernel/tools/harness.js +46 -0
  159. package/esm/src/kernel/tools/invoke.d.ts +10 -0
  160. package/esm/src/kernel/tools/invoke.js +101 -0
  161. package/esm/src/kernel/tools/mod.d.ts +13 -0
  162. package/esm/src/kernel/tools/mod.js +11 -0
  163. package/esm/src/kernel/tools/permission.d.ts +15 -0
  164. package/esm/src/kernel/tools/permission.js +47 -0
  165. package/esm/src/kernel/tools/project.d.ts +12 -0
  166. package/esm/src/kernel/tools/project.js +36 -0
  167. package/esm/src/kernel/tools/registry.d.ts +23 -0
  168. package/esm/src/kernel/tools/registry.js +81 -0
  169. package/esm/src/kernel/tools/remote.d.ts +94 -0
  170. package/esm/src/kernel/tools/remote.js +577 -0
  171. package/esm/src/kernel/tools/resolve.d.ts +39 -0
  172. package/esm/src/kernel/tools/resolve.js +283 -0
  173. package/esm/src/kernel/tools/schema.d.ts +15 -0
  174. package/esm/src/kernel/tools/schema.js +176 -0
  175. package/esm/src/kernel/tools/stage-run.d.ts +105 -0
  176. package/esm/src/kernel/tools/stage-run.js +155 -0
  177. package/esm/src/kernel/tools/types.d.ts +394 -0
  178. package/esm/src/kernel/tools/types.js +9 -0
  179. package/esm/src/kernel/types.d.ts +540 -256
  180. package/esm/src/kernel/util/find-last.d.ts +2 -0
  181. package/esm/src/kernel/util/find-last.js +10 -0
  182. package/esm/src/observability/destinations.d.ts +31 -0
  183. package/esm/src/observability/destinations.js +67 -0
  184. package/esm/src/observability/mod.d.ts +10 -3
  185. package/esm/src/observability/mod.js +6 -2
  186. package/esm/src/observability/policy.d.ts +27 -0
  187. package/esm/src/observability/policy.js +80 -0
  188. package/esm/src/observability/resolve-policy.d.ts +16 -0
  189. package/esm/src/observability/resolve-policy.js +64 -0
  190. package/esm/src/observability/trace-attach.d.ts +8 -4
  191. package/esm/src/observability/trace-attach.js +50 -29
  192. package/esm/src/observability/trace-record.d.ts +23 -13
  193. package/esm/src/observability/trace-record.js +96 -39
  194. package/esm/src/observability/trace-sink.d.ts +19 -0
  195. package/esm/src/observability/trace-sink.js +10 -0
  196. package/esm/src/observability/trace-usage.d.ts +10 -3
  197. package/esm/src/observability/trace-usage.js +70 -17
  198. package/esm/src/observability/trace.d.ts +18 -7
  199. package/esm/src/observability/trace.js +34 -17
  200. package/esm/src/observability/types.d.ts +113 -0
  201. package/esm/src/observability/types.js +11 -0
  202. package/esm/src/presets/google/speech-voices.d.ts +11 -0
  203. package/esm/src/presets/google/speech-voices.js +41 -0
  204. package/esm/src/presets/google.d.ts +36 -24
  205. package/esm/src/presets/google.js +50 -63
  206. package/esm/src/presets/mod.d.ts +2 -2
  207. package/esm/src/presets/mod.js +1 -1
  208. package/esm/src/providers/create-provider.d.ts +20 -17
  209. package/esm/src/providers/create-provider.js +72 -26
  210. package/esm/src/providers/google/interactions/framing.d.ts +23 -0
  211. package/esm/src/providers/google/interactions/framing.js +269 -0
  212. package/esm/src/providers/google/interactions/mod.d.ts +7 -0
  213. package/esm/src/providers/google/interactions/mod.js +7 -0
  214. package/esm/src/providers/google/interactions/stream.d.ts +83 -0
  215. package/esm/src/providers/google/interactions/stream.js +588 -0
  216. package/esm/src/providers/google/keys.d.ts +26 -0
  217. package/esm/src/providers/{keys.js → google/keys.js} +19 -31
  218. package/esm/src/providers/google/live/framing.d.ts +49 -0
  219. package/esm/src/providers/google/live/framing.js +552 -0
  220. package/esm/src/providers/google/live/openapi-schema.d.ts +6 -0
  221. package/esm/src/providers/google/live/openapi-schema.js +46 -0
  222. package/esm/src/providers/google/live/session.d.ts +25 -0
  223. package/esm/src/providers/google/live/session.js +134 -0
  224. package/esm/src/providers/google/live/stream.d.ts +45 -0
  225. package/esm/src/providers/google/live/stream.js +214 -0
  226. package/esm/src/providers/google/urls.d.ts +6 -0
  227. package/esm/src/providers/google/urls.js +6 -0
  228. package/esm/src/providers/local/local.d.ts +30 -0
  229. package/esm/src/providers/{local.js → local/local.js} +66 -126
  230. package/esm/src/providers/local/mod.d.ts +9 -0
  231. package/esm/src/providers/local/mod.js +9 -0
  232. package/esm/src/providers/mod.d.ts +6 -3
  233. package/esm/src/providers/mod.js +3 -1
  234. package/esm/src/providers/openrouter/cache-control.d.ts +24 -0
  235. package/esm/src/providers/openrouter/cache-control.js +23 -0
  236. package/esm/src/providers/openrouter/chat.d.ts +107 -0
  237. package/esm/src/providers/{openrouter.js → openrouter/chat.js} +117 -231
  238. package/esm/src/providers/openrouter/image.d.ts +34 -0
  239. package/esm/src/providers/openrouter/image.js +275 -0
  240. package/esm/src/providers/openrouter/openai/chat-payload.d.ts +24 -0
  241. package/esm/src/providers/openrouter/openai/chat-payload.js +82 -0
  242. package/esm/src/providers/openrouter/openai/compat.d.ts +53 -0
  243. package/esm/src/providers/openrouter/openai/compat.js +213 -0
  244. package/esm/src/providers/openrouter/openai/image-payload.d.ts +18 -0
  245. package/esm/src/providers/openrouter/openai/image-payload.js +90 -0
  246. package/esm/src/providers/openrouter/openai/sdk-messages.d.ts +22 -0
  247. package/esm/src/providers/openrouter/openai/sdk-messages.js +122 -0
  248. package/esm/src/providers/openrouter/resolve-api-key.d.ts +9 -0
  249. package/esm/src/providers/openrouter/resolve-api-key.js +24 -0
  250. package/esm/src/providers/openrouter/speech.d.ts +23 -0
  251. package/esm/src/providers/{speech.js → openrouter/speech.js} +32 -55
  252. package/esm/src/providers/probe.d.ts +1 -0
  253. package/esm/src/providers/probe.js +22 -0
  254. package/esm/src/providers/shared/pcm.d.ts +12 -0
  255. package/esm/src/providers/{pcm.js → shared/pcm.js} +16 -3
  256. package/esm/src/providers/shared/sse.d.ts +18 -0
  257. package/esm/src/providers/shared/sse.js +87 -0
  258. package/esm/src/providers/shared/tool-args.d.ts +17 -0
  259. package/esm/src/providers/shared/tool-args.js +45 -0
  260. package/esm/src/providers/shared/upstream-tap.d.ts +5 -0
  261. package/esm/src/providers/{google-tap.js → shared/upstream-tap.js} +4 -7
  262. package/esm/src/providers/shared/upstream-tape.d.ts +6 -0
  263. package/esm/src/providers/{gemini-tape.js → shared/upstream-tape.js} +12 -22
  264. package/esm/src/providers/types.d.ts +27 -0
  265. package/esm/src/providers/types.js +1 -0
  266. package/package.json +11 -7
  267. package/docs/cli.md +0 -97
  268. package/docs/guardrails.md +0 -178
  269. package/docs/host.md +0 -97
  270. package/docs/kernel.md +0 -404
  271. package/docs/observability.md +0 -105
  272. package/docs/openrouter.md +0 -125
  273. package/docs/presets-google.md +0 -91
  274. package/docs/presets.md +0 -88
  275. package/docs/providers.md +0 -202
  276. package/docs/streaming.md +0 -96
  277. package/esm/src/kernel/engine/boundary.d.ts +0 -10
  278. package/esm/src/kernel/engine/boundary.js +0 -55
  279. package/esm/src/kernel/engine/runner/tools.d.ts +0 -13
  280. package/esm/src/kernel/engine/runner/tools.js +0 -198
  281. package/esm/src/kernel/registry/tools.d.ts +0 -12
  282. package/esm/src/kernel/registry/tools.js +0 -36
  283. package/esm/src/providers/expose-for-tests.d.ts +0 -1
  284. package/esm/src/providers/expose-for-tests.js +0 -25
  285. package/esm/src/providers/gemini-tape.d.ts +0 -2
  286. package/esm/src/providers/google-tap.d.ts +0 -3
  287. package/esm/src/providers/interactions.d.ts +0 -5
  288. package/esm/src/providers/interactions.js +0 -169
  289. package/esm/src/providers/keys.d.ts +0 -19
  290. package/esm/src/providers/local.d.ts +0 -29
  291. package/esm/src/providers/openrouter-mod.d.ts +0 -13
  292. package/esm/src/providers/openrouter-mod.js +0 -12
  293. package/esm/src/providers/openrouter-payload.d.ts +0 -39
  294. package/esm/src/providers/openrouter-payload.js +0 -195
  295. package/esm/src/providers/openrouter.d.ts +0 -15
  296. package/esm/src/providers/pcm.d.ts +0 -7
  297. package/esm/src/providers/provider.d.ts +0 -15
  298. package/esm/src/providers/provider.js +0 -202
  299. package/esm/src/providers/speech.d.ts +0 -23
  300. package/esm/src/providers/sse.d.ts +0 -7
  301. package/esm/src/providers/sse.js +0 -55
  302. package/esm/src/streaming/mod.d.ts +0 -9
  303. package/esm/src/streaming/mod.js +0 -8
  304. /package/esm/src/{streaming → host}/readStreamingJsonStringField.d.ts +0 -0
  305. /package/esm/src/{streaming → host}/readStreamingJsonStringField.js +0 -0
@@ -1,60 +1,60 @@
1
1
  /**
2
2
  * Request sanitization utilities for THEORUM.
3
3
  *
4
- * Sanitization removes inbound prompt-injection spans and sensitive-data spans
5
- * according to the active profile guardrail flags. The host remains responsible
6
- * for domain policy.
7
- *
8
4
  * @module
9
5
  */
10
- import { mapStrings } from '../kernel/engine/tree.js';
11
6
  import { sanitizeTurnBlobsForProfile } from '../kernel/registry/attachments.js';
12
7
  import { getProfile } from '../kernel/registry/profiles.js';
13
8
  import { applySpans } from '../observability/spans.js';
9
+ import { guardrailFromHits } from './events.js';
10
+ import { hitFromSpan } from './hits.js';
14
11
  import { injectionSpans } from './injection.js';
12
+ import { detectionForTrust, resolveGuardrailPolicy } from './policy.js';
15
13
  import { sensitiveSpans } from './sensitive.js';
16
- /** Sanitize one text value using prompt-injection and sensitive-data detectors. */
17
- function sanitizeText(text, options) {
14
+ /**
15
+ * Detect and redact injection / sensitive spans. Returns hits for observability
16
+ * (rule + offsets + optional `match` preview for debugging).
17
+ */
18
+ function detectText(text, options) {
18
19
  const sanitizeInput = options?.sanitizeInput ?? true;
19
20
  const redactSensitive = options?.redactSensitive ?? true;
20
21
  if (!sanitizeInput && !redactSensitive) {
21
- return text;
22
+ return { text, hits: [] };
22
23
  }
23
24
  const spans = [
24
25
  ...(sanitizeInput ? injectionSpans(text) : []),
25
26
  ...(redactSensitive ? sensitiveSpans(text) : []),
26
27
  ];
27
- return applySpans(text, spans);
28
+ const hits = spans.map((span) => hitFromSpan(text, span, span.kind === 'injection' ? 'sanitize.injection' : 'sanitize.sensitive', 'high'));
29
+ return { text: applySpans(text, spans), hits };
28
30
  }
29
- /** Redact only sensitive data (credentials, PII) — skip injection patterns.
30
- * Use for model output text that never contained user-authored injection attempts. */
31
+ /** Sanitize one text value using prompt-injection and sensitive-data detectors. */
32
+ function sanitizeText(text, options) {
33
+ return detectText(text, options).text;
34
+ }
35
+ /** Redact only sensitive data (credentials, PII) — skip injection patterns. */
31
36
  function redactSensitiveOnly(text) {
32
- const spans = sensitiveSpans(text);
33
- if (spans.length === 0)
34
- return text;
35
- return applySpans(text, spans);
37
+ return detectText(text, { sanitizeInput: false, redactSensitive: true }).text;
38
+ }
39
+ function appendHits(into, hits) {
40
+ for (const hit of hits) {
41
+ into.push(hit);
42
+ }
36
43
  }
37
- function sanitizeSlots(slots, options) {
44
+ function sanitizeSlots(slots, options, hits) {
38
45
  if (!slots) {
39
46
  return slots;
40
47
  }
41
48
  const out = {};
42
49
  for (const [key, value] of Object.entries(slots)) {
43
- out[key] = sanitizeText(value, options);
50
+ const detected = detectText(value, options);
51
+ appendHits(hits, detected.hits);
52
+ out[key] = detected.text;
44
53
  }
45
54
  return out;
46
55
  }
47
- function sanitizeArgs(args, options) {
48
- const next = mapStrings(args, (t) => sanitizeText(t, options));
49
- if (next && typeof next === 'object' && !Array.isArray(next)) {
50
- return next;
51
- }
52
- return args;
53
- }
54
- /** Maximum retained length for trace-safe host project ids. */
55
56
  const PROJECT_ID_MAX = 128;
56
57
  const PROJECT_ID_OK = /^[A-Za-z0-9._-]+$/;
57
- /** Return a trace-safe project id or `undefined` when the input is unsafe. */
58
58
  function sanitizeProjectId(id) {
59
59
  if (!id) {
60
60
  return undefined;
@@ -65,95 +65,178 @@ function sanitizeProjectId(id) {
65
65
  }
66
66
  return trimmed;
67
67
  }
68
- function sanitizeRepair(repair, options) {
68
+ function sanitizeRepair(repair, options, hits) {
69
69
  if (!repair) {
70
70
  return repair;
71
71
  }
72
+ const previous = detectText(repair.previousOutput, options);
73
+ const rejection = detectText(repair.rejection, options);
74
+ appendHits(hits, previous.hits);
75
+ appendHits(hits, rejection.hits);
76
+ let guidance = repair.guidance;
77
+ if (guidance) {
78
+ const detected = detectText(guidance, options);
79
+ appendHits(hits, detected.hits);
80
+ guidance = detected.text;
81
+ }
72
82
  return {
73
- previousOutput: sanitizeText(repair.previousOutput, options),
74
- rejection: sanitizeText(repair.rejection, options),
75
- guidance: repair.guidance ? sanitizeText(repair.guidance, options) : undefined,
83
+ previousOutput: previous.text,
84
+ rejection: rejection.text,
85
+ ...(guidance ? { guidance } : {}),
76
86
  };
77
87
  }
78
- function sanitizeHistory(history, options) {
79
- if (!history) {
80
- return history;
81
- }
82
- return history.map((m) => ({
83
- role: m.role,
84
- ...(m.content !== undefined ? { content: sanitizeText(m.content, options) } : {}),
85
- ...(m.parts
86
- ? {
87
- parts: m.parts.map((p) => p.type === 'text' ? { ...p, text: sanitizeText(p.text, options) } : p),
88
- }
89
- : {}),
90
- ...(m.tool_calls ? { tool_calls: m.tool_calls } : {}),
91
- ...(m.tool_call_id ? { tool_call_id: m.tool_call_id } : {}),
92
- ...(m.name ? { name: m.name } : {}),
93
- ...(m.metadata ? { metadata: m.metadata } : {}),
94
- }));
88
+ /**
89
+ * Sanitize the text of each history message; tool calls, ids, and metadata pass
90
+ * through untouched.
91
+ *
92
+ * Exported because every path that injects messages into a turn needs it — turn
93
+ * history, and host steer injects mid-turn. A second copy would drift.
94
+ */
95
+ function sanitizeHistory(history, options, hits = []) {
96
+ return history.map((m) => {
97
+ let content = m.content;
98
+ if (content !== undefined) {
99
+ const detected = detectText(content, options);
100
+ appendHits(hits, detected.hits);
101
+ content = detected.text;
102
+ }
103
+ let parts = m.parts;
104
+ if (parts) {
105
+ parts = parts.map((p) => {
106
+ if (p.type !== 'text') {
107
+ return p;
108
+ }
109
+ const detected = detectText(p.text, options);
110
+ appendHits(hits, detected.hits);
111
+ return { ...p, text: detected.text };
112
+ });
113
+ }
114
+ return {
115
+ role: m.role,
116
+ ...(content !== undefined ? { content } : {}),
117
+ ...(parts ? { parts } : {}),
118
+ ...(m.tool_calls ? { tool_calls: m.tool_calls } : {}),
119
+ ...(m.tool_call_id ? { tool_call_id: m.tool_call_id } : {}),
120
+ ...(m.name ? { name: m.name } : {}),
121
+ ...(m.metadata ? { metadata: m.metadata } : {}),
122
+ };
123
+ });
95
124
  }
96
- function sanitizeDynamicTool(decl, options) {
97
- const clean = { ...decl };
98
- if (clean.description !== undefined) {
99
- clean.description = sanitizeText(clean.description, options);
125
+ /**
126
+ * Detection switches for one profile at one trust level.
127
+ *
128
+ * Falls back to full detection when the profile is not registered yet, so an
129
+ * unknown id never silently disables guardrails.
130
+ */
131
+ function detectionForProfile(profileId, trust) {
132
+ let spec;
133
+ try {
134
+ spec = getProfile(profileId)?.guardrails;
100
135
  }
101
- if (clean.parameters !== undefined) {
102
- clean.parameters = sanitizeArgs(clean.parameters, options);
136
+ catch {
137
+ // If profile not registered yet, default to full guardrails.
103
138
  }
104
- return clean;
139
+ return detectionForTrust(resolveGuardrailPolicy(spec), trust);
105
140
  }
106
- /** Sanitize description and parameter schema text in dynamic tool declarations. */
107
- function sanitizeDynamicTools(tools, options) {
108
- if (!tools || tools.length === 0) {
109
- return tools;
141
+ function pushStageEvent(events, stage, trust, hits) {
142
+ const event = guardrailFromHits(stage, trust, hits, 'redact');
143
+ if (event) {
144
+ events.push(event);
110
145
  }
111
- return tools.map((decl) => sanitizeDynamicTool(decl, options));
112
146
  }
113
- /** Sanitize all user-controlled text and blobs in a turn request. */
114
- function sanitizeTurnRequest(req) {
115
- let profileGuardrails;
116
- try {
117
- profileGuardrails = getProfile(req.profile)?.guardrails;
118
- }
119
- catch {
120
- // If profile not registered yet, default to full guardrails
121
- }
122
- const options = {
123
- sanitizeInput: profileGuardrails?.sanitizeInput ?? true,
124
- redactSensitive: profileGuardrails?.redactSensitive ?? true,
125
- };
147
+ /**
148
+ * Sanitize user-controlled text fields; leave attachments/voice untouched.
149
+ *
150
+ * Returns `{ type: 'guardrail' }` events for stages that redacted something.
151
+ * Clean surfaces emit nothing.
152
+ *
153
+ * `req.system` is host-assembled per turn — it interpolates retrieval and user
154
+ * data, so it is treated as `assembled`, not trusted. `identity.system` never
155
+ * reaches this path and stays verbatim.
156
+ */
157
+ function sanitizeTurnRequestText(req, profileId) {
158
+ const untrusted = detectionForProfile(profileId, 'untrusted');
159
+ const assembled = detectionForProfile(profileId, 'assembled');
126
160
  const input = req.input ?? {};
127
- const { toolInvoke } = req;
161
+ const events = [];
162
+ const inputHits = [];
163
+ const historyHits = [];
164
+ const systemHits = [];
128
165
  const { text: rawText } = input;
129
- let invoke = toolInvoke;
130
- if (toolInvoke) {
131
- invoke = { ...toolInvoke, arguments: sanitizeArgs(toolInvoke.arguments, options) };
132
- }
133
166
  let text = rawText;
134
167
  if (rawText !== undefined) {
135
- text = sanitizeText(rawText, options);
168
+ const detected = detectText(rawText, untrusted);
169
+ appendHits(inputHits, detected.hits);
170
+ text = detected.text;
136
171
  }
137
172
  let system = req.system;
138
173
  if (system !== undefined) {
139
- system = sanitizeText(system, options);
140
- }
174
+ const detected = detectText(system, assembled);
175
+ appendHits(systemHits, detected.hits);
176
+ system = detected.text;
177
+ }
178
+ const slots = sanitizeSlots(input.slots, untrusted, inputHits);
179
+ const repair = sanitizeRepair(input.repair, untrusted, inputHits);
180
+ const history = input.history
181
+ ? sanitizeHistory(input.history, untrusted, historyHits)
182
+ : undefined;
183
+ pushStageEvent(events, 'input', 'untrusted', inputHits);
184
+ pushStageEvent(events, 'history', 'untrusted', historyHits);
185
+ pushStageEvent(events, 'system', 'assembled', systemHits);
186
+ return {
187
+ request: {
188
+ ...req,
189
+ system,
190
+ projectId: sanitizeProjectId(req.projectId),
191
+ input: {
192
+ ...input,
193
+ text,
194
+ slots,
195
+ repair,
196
+ history,
197
+ },
198
+ },
199
+ events,
200
+ };
201
+ }
202
+ /** Sanitize all user-controlled text and blobs in a turn request. */
203
+ function sanitizeTurnRequest(req) {
204
+ return sanitizeTurnRequestWithEvents(req).request;
205
+ }
206
+ /**
207
+ * Sanitize a turn request and return guardrail events for any redactionsactions spans.
208
+ * Attachments/voice are validated but do not emit content-span events.
209
+ */
210
+ function sanitizeTurnRequestWithEvents(req) {
211
+ const { request: textSafe, events } = sanitizeTurnRequestText(req, req.profile);
212
+ const input = textSafe.input ?? {};
141
213
  const { attachments, voice } = sanitizeTurnBlobsForProfile(req.profile, input.attachments, input.voice);
142
214
  return {
143
- ...req,
144
- system,
145
- projectId: sanitizeProjectId(req.projectId),
146
- dynamicTools: sanitizeDynamicTools(req.dynamicTools, options),
147
- input: {
148
- ...input,
149
- text,
150
- slots: sanitizeSlots(input.slots, options),
151
- attachments,
152
- voice,
153
- repair: sanitizeRepair(input.repair, options),
154
- history: sanitizeHistory(input.history, options),
215
+ request: {
216
+ ...textSafe,
217
+ input: {
218
+ ...input,
219
+ attachments,
220
+ voice,
221
+ },
155
222
  },
156
- toolInvoke: invoke,
223
+ events,
157
224
  };
158
225
  }
159
- export { PROJECT_ID_MAX, redactSensitiveOnly, sanitizeDynamicTools, sanitizeProjectId, sanitizeText, sanitizeTurnRequest, };
226
+ /**
227
+ * Trace-safe request sanitize. Prefers full `sanitizeTurnRequest`; if blob/policy
228
+ * checks throw, still redacts text and keeps attachments for hashing — never invents empty input.
229
+ */
230
+ function sanitizeTurnRequestForTrace(req) {
231
+ try {
232
+ return { request: sanitizeTurnRequest(req) };
233
+ }
234
+ catch (err) {
235
+ const message = err instanceof Error ? err.message : String(err);
236
+ return {
237
+ request: sanitizeTurnRequestText(req, req.profile).request,
238
+ sanitizeError: message,
239
+ };
240
+ }
241
+ }
242
+ export { detectionForProfile, detectText, PROJECT_ID_MAX, redactSensitiveOnly, sanitizeHistory, sanitizeProjectId, sanitizeText, sanitizeTurnRequest, sanitizeTurnRequestForTrace, sanitizeTurnRequestWithEvents, };
@@ -23,7 +23,8 @@ const GITHUB_PAT = /\bgithub_pat_[A-Za-z0-9_]{20,}\b/g;
23
23
  const GITHUB_TOKEN = /\bghp_[A-Za-z0-9]{36}\b/g;
24
24
  const SLACK_TOKEN = /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g;
25
25
  const BEARER = /\bBearer\s+[A-Za-z0-9._~+/-]+=*/gi;
26
- const PEM_KEY = /-----BEGIN (?:RSA )?PRIVATE KEY-----[\s\S]+?-----END (?:RSA )?PRIVATE KEY-----/g;
26
+ /** Bounded payload so PEM redaction cannot ReDoS on repeated BEGIN markers. */
27
+ const PEM_KEY = /-----BEGIN (?:RSA )?PRIVATE KEY-----[\s\S]{0,16384}?-----END (?:RSA )?PRIVATE KEY-----/g;
27
28
  const CARD_CANDIDATE = /\b(?:\d[\s.-]*?){13,19}\b/g;
28
29
  const KEY_PATTERNS = [
29
30
  SSN,
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Serialization for guardrail scanning.
3
+ *
4
+ * Detectors work on strings, so non-text event payloads (structured output, tool
5
+ * arguments, grounding metadata) must be flattened before they can be inspected.
6
+ * A guardrail must never be the thing that throws, so this never propagates a
7
+ * serializer error: cycles and bigints are represented rather than fatal, and a
8
+ * payload that still cannot be rendered is reported so the caller can fail closed.
9
+ *
10
+ * @module
11
+ */
12
+ /** Marker substituted for a repeated reference so a cycle terminates. */
13
+ declare const CIRCULAR = "[circular]";
14
+ export interface ScanText {
15
+ text: string;
16
+ /** True when the payload could not be rendered and was not inspected. */
17
+ unscannable: boolean;
18
+ }
19
+ /**
20
+ * Flatten an arbitrary payload to text for detector scanning.
21
+ *
22
+ * Cycles collapse to `[circular]` and bigints render as digits, so the common
23
+ * unserializable shapes still get inspected instead of aborting the turn. Only a
24
+ * payload that defeats that (a throwing `toJSON`, for instance) comes back
25
+ * `unscannable`.
26
+ */
27
+ declare function textForScan(value: unknown): ScanText;
28
+ /**
29
+ * Scan-ready text, discarding the unscannable signal.
30
+ *
31
+ * For callers whose only question is "does this contain X" and for whom an
32
+ * unrenderable payload is the same as no match.
33
+ */
34
+ declare function scanTextOf(value: unknown): string;
35
+ export { CIRCULAR, scanTextOf, textForScan };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Serialization for guardrail scanning.
3
+ *
4
+ * Detectors work on strings, so non-text event payloads (structured output, tool
5
+ * arguments, grounding metadata) must be flattened before they can be inspected.
6
+ * A guardrail must never be the thing that throws, so this never propagates a
7
+ * serializer error: cycles and bigints are represented rather than fatal, and a
8
+ * payload that still cannot be rendered is reported so the caller can fail closed.
9
+ *
10
+ * @module
11
+ */
12
+ /** Marker substituted for a repeated reference so a cycle terminates. */
13
+ const CIRCULAR = '[circular]';
14
+ /**
15
+ * Flatten an arbitrary payload to text for detector scanning.
16
+ *
17
+ * Cycles collapse to `[circular]` and bigints render as digits, so the common
18
+ * unserializable shapes still get inspected instead of aborting the turn. Only a
19
+ * payload that defeats that (a throwing `toJSON`, for instance) comes back
20
+ * `unscannable`.
21
+ */
22
+ function textForScan(value) {
23
+ if (value === undefined) {
24
+ return { text: '', unscannable: false };
25
+ }
26
+ if (typeof value === 'string') {
27
+ return { text: value, unscannable: false };
28
+ }
29
+ const seen = new WeakSet();
30
+ try {
31
+ const json = JSON.stringify(value, (_key, val) => {
32
+ if (typeof val === 'bigint') {
33
+ return val.toString();
34
+ }
35
+ if (typeof val === 'object' && val !== null) {
36
+ if (seen.has(val)) {
37
+ return CIRCULAR;
38
+ }
39
+ seen.add(val);
40
+ }
41
+ return val;
42
+ });
43
+ return { text: json ?? '', unscannable: false };
44
+ }
45
+ catch {
46
+ return { text: '', unscannable: true };
47
+ }
48
+ }
49
+ /**
50
+ * Scan-ready text, discarding the unscannable signal.
51
+ *
52
+ * For callers whose only question is "does this contain X" and for whom an
53
+ * unrenderable payload is the same as no match.
54
+ */
55
+ function scanTextOf(value) {
56
+ return textForScan(value).text;
57
+ }
58
+ export { CIRCULAR, scanTextOf, textForScan };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Guardrails testing surface — adversarial corpus, fuzz runners, live attack builders.
3
+ *
4
+ * Import via `theorum/guardrails/testing` (not published on the production guardrails entry).
5
+ *
6
+ * @module
7
+ */
8
+ /** lexicon-exempt-file: adversarial harness helpers — not runtime user or model copy (P2) */
9
+ import "../../_dnt.polyfills.js";
10
+ export type { CanaryEgressAttack, CanaryEgressCatalogEntry, InboundFuzzPayload, InboundFuzzResult, LiveAttack, } from './corpus/mod.js';
11
+ export { buildCanaryEgressAttacks, buildLiveAttacks, canaryEgressCatalog, FIXED_CANARY, filterLiveAttacks, inboundFuzzPayloads, inboundPayloadByName, runInboundGuardrailFuzz, summarizeAttackBank, } from './corpus/mod.js';
12
+ export type { CorpusSample, CorpusSource } from './eval/corpus.js';
13
+ export { createCorpusCache, parseLabelledCsv, recordsFromYaml, SOURCES } from './eval/corpus.js';
14
+ export type { EvalOptions, EvalReport } from './eval/mod.js';
15
+ export { DETECTORS, formatReport, runGuardrailEval } from './eval/mod.js';
16
+ export type { DetectorScore, EvalDetector } from './eval/score.js';
17
+ export { formatScores, scoreAll, scoreDetector } from './eval/score.js';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Guardrails testing surface — adversarial corpus, fuzz runners, live attack builders.
3
+ *
4
+ * Import via `theorum/guardrails/testing` (not published on the production guardrails entry).
5
+ *
6
+ * @module
7
+ */
8
+ /** lexicon-exempt-file: adversarial harness helpers — not runtime user or model copy (P2) */
9
+ import "../../_dnt.polyfills.js";
10
+ export { buildCanaryEgressAttacks, buildLiveAttacks, canaryEgressCatalog, FIXED_CANARY, filterLiveAttacks, inboundFuzzPayloads, inboundPayloadByName, runInboundGuardrailFuzz, summarizeAttackBank, } from './corpus/mod.js';
11
+ export { createCorpusCache, parseLabelledCsv, recordsFromYaml, SOURCES } from './eval/corpus.js';
12
+ export { DETECTORS, formatReport, runGuardrailEval } from './eval/mod.js';
13
+ export { formatScores, scoreAll, scoreDetector } from './eval/score.js';
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Contract-failure error class for THEORUM.
3
+ *
4
+ * Lives in its own module so `lexicon.ts` can throw it without importing
5
+ * `error.ts` (which resolves public copy through the lexicon).
6
+ *
7
+ * @module
8
+ */
9
+ /** Error class used for expected THEORUM contract failures. */
10
+ export declare class TheorumError extends Error {
11
+ constructor(message?: string, options?: ErrorOptions);
12
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Contract-failure error class for THEORUM.
3
+ *
4
+ * Lives in its own module so `lexicon.ts` can throw it without importing
5
+ * `error.ts` (which resolves public copy through the lexicon).
6
+ *
7
+ * @module
8
+ */
9
+ /** Error class used for expected THEORUM contract failures. */
10
+ export class TheorumError extends Error {
11
+ constructor(message = '', options) {
12
+ super(message, options);
13
+ this.name = 'TheorumError';
14
+ }
15
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Directive detection for tool ingress.
3
+ *
4
+ * Tool results carry a different threat than user text. The jailbreak phrasings in
5
+ * `injection.ts` name the thing they attack — "ignore previous instructions",
6
+ * "reveal your system prompt" — and real indirect injection rarely does. It reads
7
+ * like a status update or a helpful next step, and pattern-matching for the word
8
+ * "instructions" misses all of it.
9
+ *
10
+ * What is anomalous in *data* is content that behaves like an instruction: naming
11
+ * a tool the agent can call, issuing an imperative at the agent, or claiming an
12
+ * authority the content does not have.
13
+ *
14
+ * A signal only counts when it co-occurs with a concrete external destination —
15
+ * an address or URL. Directive language alone is far too common in legitimate
16
+ * output to act on. These signals raise the turn's taint rather than rewriting the text. A page
17
+ * documenting an email API legitimately says "call send_email"; redacting that
18
+ * would corrupt content the model needs. Being wrong here should cost a refused
19
+ * write — recoverable and visible — not silently damaged input.
20
+ *
21
+ * @module
22
+ */
23
+ import type { AdvisoryLevel, GuardrailHit } from './types.js';
24
+ /** Rule ids emitted by tool-ingress directive detection. */
25
+ export declare const DIRECTIVE_RULES: {
26
+ readonly toolName: "tool_result.names-callable-tool";
27
+ readonly imperative: "tool_result.imperative";
28
+ readonly authority: "tool_result.authority-claim";
29
+ };
30
+ /**
31
+ * Detect instruction-shaped content in a tool result.
32
+ *
33
+ * `callableTools` is the set the model can actually invoke this turn. A result
34
+ * naming one is the highest-precision signal available — ordinary data has no
35
+ * reason to name the agent's tools, and no generic content filter can check it
36
+ * because it requires the turn's registry.
37
+ */
38
+ declare function directiveHits(text: string, callableTools?: readonly string[]): GuardrailHit[];
39
+ /** True when a result looked like it was trying to steer the agent. */
40
+ declare function looksDirective(hits: GuardrailHit[]): boolean;
41
+ /**
42
+ * Strength of the signals, read off the hits rather than invented.
43
+ *
44
+ * Naming a tool the model can call is the sharpest signal available, so it alone
45
+ * reaches `high`; so does agreement between two different signal kinds.
46
+ */
47
+ declare function advisoryLevel(hits: GuardrailHit[]): AdvisoryLevel;
48
+ export { advisoryLevel, directiveHits, looksDirective };