open-multi-agent-kit 0.95.0 → 0.95.2

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 (281) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +1 -1
  3. package/dist/approvals/approval-receipt-store.d.ts +18 -0
  4. package/dist/approvals/approval-receipt-store.d.ts.map +1 -0
  5. package/dist/approvals/approval-receipt-store.js +169 -0
  6. package/dist/approvals/approval-receipt-store.js.map +1 -0
  7. package/dist/approvals/approval-receipt.d.ts +43 -0
  8. package/dist/approvals/approval-receipt.d.ts.map +1 -0
  9. package/dist/approvals/approval-receipt.js +208 -0
  10. package/dist/approvals/approval-receipt.js.map +1 -0
  11. package/dist/approvals/approval-verifier.d.ts +17 -0
  12. package/dist/approvals/approval-verifier.d.ts.map +1 -0
  13. package/dist/approvals/approval-verifier.js +23 -0
  14. package/dist/approvals/approval-verifier.js.map +1 -0
  15. package/dist/approvals/plannotator-approval-bridge.d.ts +58 -0
  16. package/dist/approvals/plannotator-approval-bridge.d.ts.map +1 -0
  17. package/dist/approvals/plannotator-approval-bridge.js +169 -0
  18. package/dist/approvals/plannotator-approval-bridge.js.map +1 -0
  19. package/dist/cli/args.d.ts.map +1 -1
  20. package/dist/cli/args.js +2 -0
  21. package/dist/cli/args.js.map +1 -1
  22. package/dist/commands/package-doctor-cli.d.ts +13 -0
  23. package/dist/commands/package-doctor-cli.d.ts.map +1 -0
  24. package/dist/commands/package-doctor-cli.js +80 -0
  25. package/dist/commands/package-doctor-cli.js.map +1 -0
  26. package/dist/commands/router-feedback-cli.d.ts +11 -0
  27. package/dist/commands/router-feedback-cli.d.ts.map +1 -0
  28. package/dist/commands/router-feedback-cli.js +99 -0
  29. package/dist/commands/router-feedback-cli.js.map +1 -0
  30. package/dist/commands/stats-cli.d.ts +20 -0
  31. package/dist/commands/stats-cli.d.ts.map +1 -0
  32. package/dist/commands/stats-cli.js +99 -0
  33. package/dist/commands/stats-cli.js.map +1 -0
  34. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  35. package/dist/core/agent-session-runtime.js +4 -1
  36. package/dist/core/agent-session-runtime.js.map +1 -1
  37. package/dist/core/agent-session.d.ts +97 -29
  38. package/dist/core/agent-session.d.ts.map +1 -1
  39. package/dist/core/agent-session.js +583 -199
  40. package/dist/core/agent-session.js.map +1 -1
  41. package/dist/core/bash-executor.d.ts.map +1 -1
  42. package/dist/core/bash-executor.js +8 -2
  43. package/dist/core/bash-executor.js.map +1 -1
  44. package/dist/core/compaction/transaction.d.ts +9 -0
  45. package/dist/core/compaction/transaction.d.ts.map +1 -1
  46. package/dist/core/compaction/transaction.js +17 -2
  47. package/dist/core/compaction/transaction.js.map +1 -1
  48. package/dist/core/context-budget-governor-v2.d.ts +1 -1
  49. package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
  50. package/dist/core/context-budget-governor-v2.js +1 -1
  51. package/dist/core/context-budget-governor-v2.js.map +1 -1
  52. package/dist/core/context-budget-system-prompt.d.ts.map +1 -1
  53. package/dist/core/context-budget-system-prompt.js +18 -2
  54. package/dist/core/context-budget-system-prompt.js.map +1 -1
  55. package/dist/core/context-budget-v2-cache-disk.d.ts +109 -0
  56. package/dist/core/context-budget-v2-cache-disk.d.ts.map +1 -0
  57. package/dist/core/context-budget-v2-cache-disk.js +343 -0
  58. package/dist/core/context-budget-v2-cache-disk.js.map +1 -0
  59. package/dist/core/context-budget-v2-cache-keys.d.ts +22 -0
  60. package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
  61. package/dist/core/context-budget-v2-cache-keys.js +4 -4
  62. package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
  63. package/dist/core/context-budget-v2-cache.d.ts +1 -0
  64. package/dist/core/context-budget-v2-cache.d.ts.map +1 -1
  65. package/dist/core/context-budget-v2-cache.js +1 -0
  66. package/dist/core/context-budget-v2-cache.js.map +1 -1
  67. package/dist/core/context-budget-v2-plan-cache-keys.d.ts.map +1 -1
  68. package/dist/core/context-budget-v2-plan-cache-keys.js +39 -5
  69. package/dist/core/context-budget-v2-plan-cache-keys.js.map +1 -1
  70. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  71. package/dist/core/context-budget-v2-planner.js +17 -14
  72. package/dist/core/context-budget-v2-planner.js.map +1 -1
  73. package/dist/core/context-budget-v2-types.d.ts +1 -0
  74. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  75. package/dist/core/context-budget-v2-types.js.map +1 -1
  76. package/dist/core/extensions/loader.d.ts.map +1 -1
  77. package/dist/core/extensions/loader.js +45 -22
  78. package/dist/core/extensions/loader.js.bak-sharedjiti-20260812-111246 +553 -0
  79. package/dist/core/extensions/loader.js.map +1 -1
  80. package/dist/core/extensions/types.d.ts +4 -0
  81. package/dist/core/extensions/types.d.ts.map +1 -1
  82. package/dist/core/extensions/types.js.map +1 -1
  83. package/dist/core/headroom.d.ts.map +1 -1
  84. package/dist/core/headroom.js +7 -2
  85. package/dist/core/headroom.js.map +1 -1
  86. package/dist/core/mcp/client.d.ts +105 -0
  87. package/dist/core/mcp/client.d.ts.map +1 -0
  88. package/dist/core/mcp/client.js +198 -0
  89. package/dist/core/mcp/client.js.map +1 -0
  90. package/dist/core/mcp/config.d.ts +23 -0
  91. package/dist/core/mcp/config.d.ts.map +1 -0
  92. package/dist/core/mcp/config.js +89 -0
  93. package/dist/core/mcp/config.js.map +1 -0
  94. package/dist/core/mcp/index.d.ts +15 -0
  95. package/dist/core/mcp/index.d.ts.map +1 -0
  96. package/dist/core/mcp/index.js +15 -0
  97. package/dist/core/mcp/index.js.map +1 -0
  98. package/dist/core/mcp/manager.d.ts +84 -0
  99. package/dist/core/mcp/manager.d.ts.map +1 -0
  100. package/dist/core/mcp/manager.js +173 -0
  101. package/dist/core/mcp/manager.js.map +1 -0
  102. package/dist/core/mcp/protocol.d.ts +59 -0
  103. package/dist/core/mcp/protocol.d.ts.map +1 -0
  104. package/dist/core/mcp/protocol.js +103 -0
  105. package/dist/core/mcp/protocol.js.map +1 -0
  106. package/dist/core/mcp/stdio-transport.d.ts +63 -0
  107. package/dist/core/mcp/stdio-transport.d.ts.map +1 -0
  108. package/dist/core/mcp/stdio-transport.js +132 -0
  109. package/dist/core/mcp/stdio-transport.js.map +1 -0
  110. package/dist/core/mcp/tools.d.ts +47 -0
  111. package/dist/core/mcp/tools.d.ts.map +1 -0
  112. package/dist/core/mcp/tools.js +102 -0
  113. package/dist/core/mcp/tools.js.map +1 -0
  114. package/dist/core/package-archive.d.ts +3 -0
  115. package/dist/core/package-archive.d.ts.map +1 -0
  116. package/dist/core/package-archive.js +161 -0
  117. package/dist/core/package-archive.js.map +1 -0
  118. package/dist/core/package-doctor-source-scan.d.ts +13 -0
  119. package/dist/core/package-doctor-source-scan.d.ts.map +1 -0
  120. package/dist/core/package-doctor-source-scan.js +124 -0
  121. package/dist/core/package-doctor-source-scan.js.map +1 -0
  122. package/dist/core/package-doctor.d.ts +41 -0
  123. package/dist/core/package-doctor.d.ts.map +1 -0
  124. package/dist/core/package-doctor.js +145 -0
  125. package/dist/core/package-doctor.js.map +1 -0
  126. package/dist/core/package-manager.d.ts +10 -1
  127. package/dist/core/package-manager.d.ts.map +1 -1
  128. package/dist/core/package-manager.js +175 -42
  129. package/dist/core/package-manager.js.map +1 -1
  130. package/dist/core/package-manifest.d.ts +26 -0
  131. package/dist/core/package-manifest.d.ts.map +1 -0
  132. package/dist/core/package-manifest.js +85 -0
  133. package/dist/core/package-manifest.js.map +1 -0
  134. package/dist/core/pi-compat.d.ts +5 -0
  135. package/dist/core/pi-compat.d.ts.map +1 -0
  136. package/dist/core/pi-compat.js +18 -0
  137. package/dist/core/pi-compat.js.map +1 -0
  138. package/dist/core/reasoning-router-bias.d.ts +10 -14
  139. package/dist/core/reasoning-router-bias.d.ts.map +1 -1
  140. package/dist/core/reasoning-router-bias.js +22 -15
  141. package/dist/core/reasoning-router-bias.js.map +1 -1
  142. package/dist/core/reasoning-router-v4.d.ts +9 -1
  143. package/dist/core/reasoning-router-v4.d.ts.map +1 -1
  144. package/dist/core/reasoning-router-v4.js +20 -5
  145. package/dist/core/reasoning-router-v4.js.map +1 -1
  146. package/dist/core/redaction.d.ts +2 -5
  147. package/dist/core/redaction.d.ts.map +1 -1
  148. package/dist/core/redaction.js +14 -1
  149. package/dist/core/redaction.js.map +1 -1
  150. package/dist/core/repository-learning-scope.d.ts +10 -0
  151. package/dist/core/repository-learning-scope.d.ts.map +1 -0
  152. package/dist/core/repository-learning-scope.js +42 -0
  153. package/dist/core/repository-learning-scope.js.map +1 -0
  154. package/dist/core/resource-loader.d.ts.map +1 -1
  155. package/dist/core/resource-loader.js +16 -1
  156. package/dist/core/resource-loader.js.map +1 -1
  157. package/dist/core/router-feedback-collector.d.ts +2 -1
  158. package/dist/core/router-feedback-collector.d.ts.map +1 -1
  159. package/dist/core/router-feedback-collector.js +2 -2
  160. package/dist/core/router-feedback-collector.js.map +1 -1
  161. package/dist/core/runtime-provenance.d.ts +54 -0
  162. package/dist/core/runtime-provenance.d.ts.map +1 -0
  163. package/dist/core/runtime-provenance.js +115 -0
  164. package/dist/core/runtime-provenance.js.map +1 -0
  165. package/dist/core/sandbox/backend.d.ts.map +1 -1
  166. package/dist/core/sandbox/backend.js +39 -21
  167. package/dist/core/sandbox/backend.js.map +1 -1
  168. package/dist/core/sandbox/default-policy.d.ts +4 -5
  169. package/dist/core/sandbox/default-policy.d.ts.map +1 -1
  170. package/dist/core/sandbox/default-policy.js +9 -11
  171. package/dist/core/sandbox/default-policy.js.map +1 -1
  172. package/dist/core/session-bash-runtime.d.ts +4 -4
  173. package/dist/core/session-bash-runtime.d.ts.map +1 -1
  174. package/dist/core/session-bash-runtime.js +6 -5
  175. package/dist/core/session-bash-runtime.js.map +1 -1
  176. package/dist/core/session-compaction-service.d.ts.map +1 -1
  177. package/dist/core/session-compaction-service.js +7 -7
  178. package/dist/core/session-compaction-service.js.map +1 -1
  179. package/dist/core/session-manager.d.ts +4 -1
  180. package/dist/core/session-manager.d.ts.map +1 -1
  181. package/dist/core/session-manager.js +47 -7
  182. package/dist/core/session-manager.js.map +1 -1
  183. package/dist/core/settings-manager.d.ts +7 -4
  184. package/dist/core/settings-manager.d.ts.map +1 -1
  185. package/dist/core/settings-manager.js +11 -3
  186. package/dist/core/settings-manager.js.map +1 -1
  187. package/dist/core/system-prompt.d.ts +9 -1
  188. package/dist/core/system-prompt.d.ts.map +1 -1
  189. package/dist/core/system-prompt.js +10 -4
  190. package/dist/core/system-prompt.js.map +1 -1
  191. package/dist/core/tools/bash.d.ts +2 -2
  192. package/dist/core/tools/bash.d.ts.map +1 -1
  193. package/dist/core/tools/bash.js +2 -1
  194. package/dist/core/tools/bash.js.map +1 -1
  195. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  196. package/dist/core/tools/tool-definition-wrapper.js +15 -0
  197. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  198. package/dist/core/turn-metrics.d.ts +135 -0
  199. package/dist/core/turn-metrics.d.ts.map +1 -0
  200. package/dist/core/turn-metrics.js +261 -0
  201. package/dist/core/turn-metrics.js.map +1 -0
  202. package/dist/core/verified-bash-runtime.d.ts.map +1 -1
  203. package/dist/core/verified-bash-runtime.js +10 -4
  204. package/dist/core/verified-bash-runtime.js.map +1 -1
  205. package/dist/guardrails/replay-ledger-lock-owner.d.ts +7 -0
  206. package/dist/guardrails/replay-ledger-lock-owner.d.ts.map +1 -1
  207. package/dist/guardrails/replay-ledger-lock-owner.js +39 -0
  208. package/dist/guardrails/replay-ledger-lock-owner.js.map +1 -1
  209. package/dist/index.d.ts +6 -1
  210. package/dist/index.d.ts.map +1 -1
  211. package/dist/index.js +6 -1
  212. package/dist/index.js.map +1 -1
  213. package/dist/main.d.ts.map +1 -1
  214. package/dist/main.js +18 -0
  215. package/dist/main.js.map +1 -1
  216. package/dist/modes/interactive/components/control-panel-layout.d.ts.map +1 -1
  217. package/dist/modes/interactive/components/control-panel-layout.js +2 -2
  218. package/dist/modes/interactive/components/control-panel-layout.js.map +1 -1
  219. package/dist/modes/interactive/components/footer.d.ts +3 -0
  220. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  221. package/dist/modes/interactive/components/footer.js +40 -12
  222. package/dist/modes/interactive/components/footer.js.map +1 -1
  223. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  224. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  225. package/dist/modes/interactive/components/settings-selector.js +10 -0
  226. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  227. package/dist/modes/interactive/components/status-sidebar.d.ts +14 -2
  228. package/dist/modes/interactive/components/status-sidebar.d.ts.map +1 -1
  229. package/dist/modes/interactive/components/status-sidebar.js +106 -19
  230. package/dist/modes/interactive/components/status-sidebar.js.map +1 -1
  231. package/dist/modes/interactive/interactive-mode.d.ts +10 -1
  232. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  233. package/dist/modes/interactive/interactive-mode.js +104 -17
  234. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  235. package/dist/modes/interactive/theme/dark.json +1 -1
  236. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  237. package/dist/modes/interactive/theme/theme.js +1 -0
  238. package/dist/modes/interactive/theme/theme.js.map +1 -1
  239. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  240. package/dist/modes/rpc/rpc-mode.js +1 -1
  241. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  242. package/docs/compaction.md +25 -3
  243. package/docs/containerization.md +14 -2
  244. package/docs/docs.json +8 -0
  245. package/docs/environment-variables.md +1 -1
  246. package/docs/extensions.md +75 -16
  247. package/docs/mcp.md +97 -0
  248. package/docs/metrics.md +87 -0
  249. package/docs/packages.md +25 -4
  250. package/docs/providers.md +1 -1
  251. package/docs/sdk.md +25 -4
  252. package/docs/settings.md +8 -7
  253. package/docs/usage.md +13 -3
  254. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  255. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  256. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  257. package/examples/extensions/gondolin/package-lock.json +2 -2
  258. package/examples/extensions/gondolin/package.json +1 -1
  259. package/examples/extensions/plannotator-approval-bridge.ts +83 -0
  260. package/examples/extensions/sandbox/package-lock.json +2 -2
  261. package/examples/extensions/sandbox/package.json +1 -1
  262. package/examples/extensions/subagent/README.md +21 -11
  263. package/examples/extensions/subagent/adaptive-agent-runtime.ts +9 -3
  264. package/examples/extensions/subagent/agents/planner.md +1 -3
  265. package/examples/extensions/subagent/agents/reviewer.md +1 -3
  266. package/examples/extensions/subagent/agents/scout.md +1 -3
  267. package/examples/extensions/subagent/agents/worker.md +0 -1
  268. package/examples/extensions/subagent/agents.test.ts +9 -0
  269. package/examples/extensions/subagent/agents.ts +11 -4
  270. package/examples/extensions/subagent/deadline-budget.test.ts +32 -0
  271. package/examples/extensions/subagent/deadline-budget.ts +25 -1
  272. package/examples/extensions/subagent/fixtures/fake-omk-json.mjs +10 -0
  273. package/examples/extensions/subagent/index.ts +234 -55
  274. package/examples/extensions/subagent/subagent-execution-policy.test.ts +27 -0
  275. package/examples/extensions/subagent/subagent-extension-smoke.test.ts +167 -2
  276. package/examples/extensions/subagent/workflow-graph.test.ts +80 -0
  277. package/examples/extensions/subagent/workflow-graph.ts +89 -0
  278. package/examples/extensions/with-deps/package-lock.json +2 -2
  279. package/examples/extensions/with-deps/package.json +1 -1
  280. package/npm-shrinkwrap.json +253 -439
  281. package/package.json +8 -8
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Runtime provenance projection for OMK observability artifacts.
3
+ *
4
+ * Records the requested, selected, and provider-reported models plus the
5
+ * requested and effective thinking levels for one agent turn. This answers the
6
+ * question "which model actually answered?" without trusting a single source:
7
+ * a provider failover or subagent fallback shows up as selected vs response
8
+ * divergence instead of silently rewriting history.
9
+ *
10
+ * Posture-neutral by design: this module only projects facts into metrics and
11
+ * report artifacts. It never gates, routes, or alters any runtime behavior,
12
+ * and it carries no prompt text, tool arguments, output, or credentials.
13
+ *
14
+ * Two boundaries, two policies:
15
+ * - `buildRuntimeProvenance` (trusted caller): sanitizes each field and maps
16
+ * anything unusable to `null`. Never throws.
17
+ * - `parseRuntimeProvenance` (untrusted persisted data): rejects the whole
18
+ * record on any invalid or credential-shaped field. Never throws; returns
19
+ * `null` so a tampered line can never upgrade trust.
20
+ */
21
+ export declare const RUNTIME_PROVENANCE_SCHEMA_VERSION: "omk-runtime-provenance-1";
22
+ export interface RuntimeProvenance {
23
+ readonly schemaVersion: typeof RUNTIME_PROVENANCE_SCHEMA_VERSION;
24
+ /** `provider/model` the turn was requested with, captured at turn start. */
25
+ readonly requestedModel: string | null;
26
+ /** `provider/model` selected for the turn, captured at turn end. */
27
+ readonly selectedModel: string | null;
28
+ /** Concrete provider-reported model, else `provider/model` of the assistant message. */
29
+ readonly responseModel: string | null;
30
+ /** Requested thinking: `"auto"` in auto mode, else the level at turn start. */
31
+ readonly requestedThinking: string | null;
32
+ /** Effective thinking level after resolution, captured at turn end. */
33
+ readonly effectiveThinking: string | null;
34
+ readonly source: "session" | "assistant-message" | "subagent-child";
35
+ readonly observedAt: string;
36
+ }
37
+ export interface RuntimeProvenanceInput {
38
+ readonly requestedModel?: string | null;
39
+ readonly selectedModel?: string | null;
40
+ readonly responseModel?: string | null;
41
+ readonly requestedThinking?: string | null;
42
+ readonly effectiveThinking?: string | null;
43
+ readonly source?: RuntimeProvenance["source"];
44
+ readonly observedAt?: string;
45
+ }
46
+ /** Build a provenance record from trusted caller-supplied facts. Never throws. */
47
+ export declare function buildRuntimeProvenance(input?: RuntimeProvenanceInput): RuntimeProvenance;
48
+ /**
49
+ * Fail-closed parse of untrusted persisted data. Returns `null` (never throws)
50
+ * when any field is invalid, credential-shaped, oversized, or when the key set
51
+ * or schema version is wrong. Missing fields become `null` values.
52
+ */
53
+ export declare function parseRuntimeProvenance(value: unknown): RuntimeProvenance | null;
54
+ //# sourceMappingURL=runtime-provenance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-provenance.d.ts","sourceRoot":"","sources":["../../src/core/runtime-provenance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,eAAO,MAAM,iCAAiC,4BAAsC,CAAC;AAErF,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,aAAa,EAAE,OAAO,iCAAiC,CAAC;IACjE,4EAA4E;IAC5E,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,oEAAoE;IACpE,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,wFAAwF;IACxF,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,+EAA+E;IAC/E,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,uEAAuE;IACvE,QAAQ,CAAC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,SAAS,GAAG,mBAAmB,GAAG,gBAAgB,CAAC;IACpE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3C,QAAQ,CAAC,MAAM,CAAC,EAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAC9C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAyCD,kFAAkF;AAClF,wBAAgB,sBAAsB,CAAC,KAAK,GAAE,sBAA2B,GAAG,iBAAiB,CAW5F;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,GAAG,IAAI,CAwB/E","sourcesContent":["/**\n * Runtime provenance projection for OMK observability artifacts.\n *\n * Records the requested, selected, and provider-reported models plus the\n * requested and effective thinking levels for one agent turn. This answers the\n * question \"which model actually answered?\" without trusting a single source:\n * a provider failover or subagent fallback shows up as selected vs response\n * divergence instead of silently rewriting history.\n *\n * Posture-neutral by design: this module only projects facts into metrics and\n * report artifacts. It never gates, routes, or alters any runtime behavior,\n * and it carries no prompt text, tool arguments, output, or credentials.\n *\n * Two boundaries, two policies:\n * - `buildRuntimeProvenance` (trusted caller): sanitizes each field and maps\n * anything unusable to `null`. Never throws.\n * - `parseRuntimeProvenance` (untrusted persisted data): rejects the whole\n * record on any invalid or credential-shaped field. Never throws; returns\n * `null` so a tampered line can never upgrade trust.\n */\n\nexport const RUNTIME_PROVENANCE_SCHEMA_VERSION = \"omk-runtime-provenance-1\" as const;\n\nexport interface RuntimeProvenance {\n\treadonly schemaVersion: typeof RUNTIME_PROVENANCE_SCHEMA_VERSION;\n\t/** `provider/model` the turn was requested with, captured at turn start. */\n\treadonly requestedModel: string | null;\n\t/** `provider/model` selected for the turn, captured at turn end. */\n\treadonly selectedModel: string | null;\n\t/** Concrete provider-reported model, else `provider/model` of the assistant message. */\n\treadonly responseModel: string | null;\n\t/** Requested thinking: `\"auto\"` in auto mode, else the level at turn start. */\n\treadonly requestedThinking: string | null;\n\t/** Effective thinking level after resolution, captured at turn end. */\n\treadonly effectiveThinking: string | null;\n\treadonly source: \"session\" | \"assistant-message\" | \"subagent-child\";\n\treadonly observedAt: string;\n}\n\nexport interface RuntimeProvenanceInput {\n\treadonly requestedModel?: string | null;\n\treadonly selectedModel?: string | null;\n\treadonly responseModel?: string | null;\n\treadonly requestedThinking?: string | null;\n\treadonly effectiveThinking?: string | null;\n\treadonly source?: RuntimeProvenance[\"source\"];\n\treadonly observedAt?: string;\n}\n\nconst KEYS = [\n\t\"schemaVersion\",\n\t\"requestedModel\",\n\t\"selectedModel\",\n\t\"responseModel\",\n\t\"requestedThinking\",\n\t\"effectiveThinking\",\n\t\"source\",\n\t\"observedAt\",\n] as const;\n\nconst ALLOWED_KEYS: ReadonlySet<string> = new Set(KEYS);\n\n/** Model refs and thinking tokens: bounded length, no control chars or whitespace. */\nconst SAFE_FIELD = /^[A-Za-z0-9][A-Za-z0-9._/:@+-]{0,127}$/;\nconst ISO_TIMESTAMP = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$/;\n// Credential shapes from the mask-at-ingest policy; a provider echoing one as a\n// \"model name\" is dropped, never persisted.\nconst CREDENTIAL_SHAPE = /sk-ant-|AKIA[0-9A-Z]{8,}|ghp_[0-9A-Za-z]{10,}|xox[baprs]-|\\bBearer\\s/i;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction sanitizeField(value: unknown): string | null {\n\tif (typeof value !== \"string\") return null;\n\tconst trimmed = value.trim();\n\tif (trimmed.length === 0 || !SAFE_FIELD.test(trimmed) || CREDENTIAL_SHAPE.test(trimmed)) return null;\n\treturn trimmed;\n}\n\nfunction sanitizeSource(value: unknown): RuntimeProvenance[\"source\"] {\n\treturn value === \"session\" || value === \"assistant-message\" || value === \"subagent-child\" ? value : \"session\";\n}\n\nfunction sanitizeTimestamp(value: unknown): string {\n\treturn typeof value === \"string\" && ISO_TIMESTAMP.test(value) ? value : new Date().toISOString();\n}\n\n/** Build a provenance record from trusted caller-supplied facts. Never throws. */\nexport function buildRuntimeProvenance(input: RuntimeProvenanceInput = {}): RuntimeProvenance {\n\treturn Object.freeze({\n\t\tschemaVersion: RUNTIME_PROVENANCE_SCHEMA_VERSION,\n\t\trequestedModel: sanitizeField(input.requestedModel),\n\t\tselectedModel: sanitizeField(input.selectedModel),\n\t\tresponseModel: sanitizeField(input.responseModel),\n\t\trequestedThinking: sanitizeField(input.requestedThinking),\n\t\teffectiveThinking: sanitizeField(input.effectiveThinking),\n\t\tsource: sanitizeSource(input.source),\n\t\tobservedAt: sanitizeTimestamp(input.observedAt),\n\t});\n}\n\n/**\n * Fail-closed parse of untrusted persisted data. Returns `null` (never throws)\n * when any field is invalid, credential-shaped, oversized, or when the key set\n * or schema version is wrong. Missing fields become `null` values.\n */\nexport function parseRuntimeProvenance(value: unknown): RuntimeProvenance | null {\n\tif (!isRecord(value)) return null;\n\tfor (const key of Object.keys(value)) {\n\t\tif (!ALLOWED_KEYS.has(key)) return null;\n\t}\n\tif (value.schemaVersion !== RUNTIME_PROVENANCE_SCHEMA_VERSION) return null;\n\tif (value.source !== \"session\" && value.source !== \"assistant-message\" && value.source !== \"subagent-child\") {\n\t\treturn null;\n\t}\n\tif (value.observedAt !== undefined && !ISO_TIMESTAMP.test(String(value.observedAt))) return null;\n\ttry {\n\t\treturn Object.freeze({\n\t\t\tschemaVersion: RUNTIME_PROVENANCE_SCHEMA_VERSION,\n\t\t\trequestedModel: parseRequiredField(value.requestedModel),\n\t\t\tselectedModel: parseRequiredField(value.selectedModel),\n\t\t\tresponseModel: parseRequiredField(value.responseModel),\n\t\t\trequestedThinking: parseRequiredField(value.requestedThinking),\n\t\t\teffectiveThinking: parseRequiredField(value.effectiveThinking),\n\t\t\tsource: value.source,\n\t\t\tobservedAt: value.observedAt === undefined ? new Date().toISOString() : String(value.observedAt),\n\t\t});\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/** Missing fields parse as `null`; anything present but unusable throws (record fails closed). */\nfunction parseRequiredField(field: unknown): string | null {\n\tif (field === undefined || field === null) return null;\n\tif (typeof field !== \"string\") throw new TypeError(\"runtime provenance field is not a string\");\n\tconst sanitized = sanitizeField(field);\n\tif (sanitized === null) throw new TypeError(\"runtime provenance field is invalid or credential-shaped\");\n\treturn sanitized;\n}\n"]}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Runtime provenance projection for OMK observability artifacts.
3
+ *
4
+ * Records the requested, selected, and provider-reported models plus the
5
+ * requested and effective thinking levels for one agent turn. This answers the
6
+ * question "which model actually answered?" without trusting a single source:
7
+ * a provider failover or subagent fallback shows up as selected vs response
8
+ * divergence instead of silently rewriting history.
9
+ *
10
+ * Posture-neutral by design: this module only projects facts into metrics and
11
+ * report artifacts. It never gates, routes, or alters any runtime behavior,
12
+ * and it carries no prompt text, tool arguments, output, or credentials.
13
+ *
14
+ * Two boundaries, two policies:
15
+ * - `buildRuntimeProvenance` (trusted caller): sanitizes each field and maps
16
+ * anything unusable to `null`. Never throws.
17
+ * - `parseRuntimeProvenance` (untrusted persisted data): rejects the whole
18
+ * record on any invalid or credential-shaped field. Never throws; returns
19
+ * `null` so a tampered line can never upgrade trust.
20
+ */
21
+ export const RUNTIME_PROVENANCE_SCHEMA_VERSION = "omk-runtime-provenance-1";
22
+ const KEYS = [
23
+ "schemaVersion",
24
+ "requestedModel",
25
+ "selectedModel",
26
+ "responseModel",
27
+ "requestedThinking",
28
+ "effectiveThinking",
29
+ "source",
30
+ "observedAt",
31
+ ];
32
+ const ALLOWED_KEYS = new Set(KEYS);
33
+ /** Model refs and thinking tokens: bounded length, no control chars or whitespace. */
34
+ const SAFE_FIELD = /^[A-Za-z0-9][A-Za-z0-9._/:@+-]{0,127}$/;
35
+ const ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
36
+ // Credential shapes from the mask-at-ingest policy; a provider echoing one as a
37
+ // "model name" is dropped, never persisted.
38
+ const CREDENTIAL_SHAPE = /sk-ant-|AKIA[0-9A-Z]{8,}|ghp_[0-9A-Za-z]{10,}|xox[baprs]-|\bBearer\s/i;
39
+ function isRecord(value) {
40
+ return typeof value === "object" && value !== null && !Array.isArray(value);
41
+ }
42
+ function sanitizeField(value) {
43
+ if (typeof value !== "string")
44
+ return null;
45
+ const trimmed = value.trim();
46
+ if (trimmed.length === 0 || !SAFE_FIELD.test(trimmed) || CREDENTIAL_SHAPE.test(trimmed))
47
+ return null;
48
+ return trimmed;
49
+ }
50
+ function sanitizeSource(value) {
51
+ return value === "session" || value === "assistant-message" || value === "subagent-child" ? value : "session";
52
+ }
53
+ function sanitizeTimestamp(value) {
54
+ return typeof value === "string" && ISO_TIMESTAMP.test(value) ? value : new Date().toISOString();
55
+ }
56
+ /** Build a provenance record from trusted caller-supplied facts. Never throws. */
57
+ export function buildRuntimeProvenance(input = {}) {
58
+ return Object.freeze({
59
+ schemaVersion: RUNTIME_PROVENANCE_SCHEMA_VERSION,
60
+ requestedModel: sanitizeField(input.requestedModel),
61
+ selectedModel: sanitizeField(input.selectedModel),
62
+ responseModel: sanitizeField(input.responseModel),
63
+ requestedThinking: sanitizeField(input.requestedThinking),
64
+ effectiveThinking: sanitizeField(input.effectiveThinking),
65
+ source: sanitizeSource(input.source),
66
+ observedAt: sanitizeTimestamp(input.observedAt),
67
+ });
68
+ }
69
+ /**
70
+ * Fail-closed parse of untrusted persisted data. Returns `null` (never throws)
71
+ * when any field is invalid, credential-shaped, oversized, or when the key set
72
+ * or schema version is wrong. Missing fields become `null` values.
73
+ */
74
+ export function parseRuntimeProvenance(value) {
75
+ if (!isRecord(value))
76
+ return null;
77
+ for (const key of Object.keys(value)) {
78
+ if (!ALLOWED_KEYS.has(key))
79
+ return null;
80
+ }
81
+ if (value.schemaVersion !== RUNTIME_PROVENANCE_SCHEMA_VERSION)
82
+ return null;
83
+ if (value.source !== "session" && value.source !== "assistant-message" && value.source !== "subagent-child") {
84
+ return null;
85
+ }
86
+ if (value.observedAt !== undefined && !ISO_TIMESTAMP.test(String(value.observedAt)))
87
+ return null;
88
+ try {
89
+ return Object.freeze({
90
+ schemaVersion: RUNTIME_PROVENANCE_SCHEMA_VERSION,
91
+ requestedModel: parseRequiredField(value.requestedModel),
92
+ selectedModel: parseRequiredField(value.selectedModel),
93
+ responseModel: parseRequiredField(value.responseModel),
94
+ requestedThinking: parseRequiredField(value.requestedThinking),
95
+ effectiveThinking: parseRequiredField(value.effectiveThinking),
96
+ source: value.source,
97
+ observedAt: value.observedAt === undefined ? new Date().toISOString() : String(value.observedAt),
98
+ });
99
+ }
100
+ catch {
101
+ return null;
102
+ }
103
+ }
104
+ /** Missing fields parse as `null`; anything present but unusable throws (record fails closed). */
105
+ function parseRequiredField(field) {
106
+ if (field === undefined || field === null)
107
+ return null;
108
+ if (typeof field !== "string")
109
+ throw new TypeError("runtime provenance field is not a string");
110
+ const sanitized = sanitizeField(field);
111
+ if (sanitized === null)
112
+ throw new TypeError("runtime provenance field is invalid or credential-shaped");
113
+ return sanitized;
114
+ }
115
+ //# sourceMappingURL=runtime-provenance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-provenance.js","sourceRoot":"","sources":["../../src/core/runtime-provenance.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Runtime provenance projection for OMK observability artifacts.\n *\n * Records the requested, selected, and provider-reported models plus the\n * requested and effective thinking levels for one agent turn. This answers the\n * question \"which model actually answered?\" without trusting a single source:\n * a provider failover or subagent fallback shows up as selected vs response\n * divergence instead of silently rewriting history.\n *\n * Posture-neutral by design: this module only projects facts into metrics and\n * report artifacts. It never gates, routes, or alters any runtime behavior,\n * and it carries no prompt text, tool arguments, output, or credentials.\n *\n * Two boundaries, two policies:\n * - `buildRuntimeProvenance` (trusted caller): sanitizes each field and maps\n * anything unusable to `null`. Never throws.\n * - `parseRuntimeProvenance` (untrusted persisted data): rejects the whole\n * record on any invalid or credential-shaped field. Never throws; returns\n * `null` so a tampered line can never upgrade trust.\n */\n\nexport const RUNTIME_PROVENANCE_SCHEMA_VERSION = \"omk-runtime-provenance-1\" as const;\n\nexport interface RuntimeProvenance {\n\treadonly schemaVersion: typeof RUNTIME_PROVENANCE_SCHEMA_VERSION;\n\t/** `provider/model` the turn was requested with, captured at turn start. */\n\treadonly requestedModel: string | null;\n\t/** `provider/model` selected for the turn, captured at turn end. */\n\treadonly selectedModel: string | null;\n\t/** Concrete provider-reported model, else `provider/model` of the assistant message. */\n\treadonly responseModel: string | null;\n\t/** Requested thinking: `\"auto\"` in auto mode, else the level at turn start. */\n\treadonly requestedThinking: string | null;\n\t/** Effective thinking level after resolution, captured at turn end. */\n\treadonly effectiveThinking: string | null;\n\treadonly source: \"session\" | \"assistant-message\" | \"subagent-child\";\n\treadonly observedAt: string;\n}\n\nexport interface RuntimeProvenanceInput {\n\treadonly requestedModel?: string | null;\n\treadonly selectedModel?: string | null;\n\treadonly responseModel?: string | null;\n\treadonly requestedThinking?: string | null;\n\treadonly effectiveThinking?: string | null;\n\treadonly source?: RuntimeProvenance[\"source\"];\n\treadonly observedAt?: string;\n}\n\nconst KEYS = [\n\t\"schemaVersion\",\n\t\"requestedModel\",\n\t\"selectedModel\",\n\t\"responseModel\",\n\t\"requestedThinking\",\n\t\"effectiveThinking\",\n\t\"source\",\n\t\"observedAt\",\n] as const;\n\nconst ALLOWED_KEYS: ReadonlySet<string> = new Set(KEYS);\n\n/** Model refs and thinking tokens: bounded length, no control chars or whitespace. */\nconst SAFE_FIELD = /^[A-Za-z0-9][A-Za-z0-9._/:@+-]{0,127}$/;\nconst ISO_TIMESTAMP = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$/;\n// Credential shapes from the mask-at-ingest policy; a provider echoing one as a\n// \"model name\" is dropped, never persisted.\nconst CREDENTIAL_SHAPE = /sk-ant-|AKIA[0-9A-Z]{8,}|ghp_[0-9A-Za-z]{10,}|xox[baprs]-|\\bBearer\\s/i;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction sanitizeField(value: unknown): string | null {\n\tif (typeof value !== \"string\") return null;\n\tconst trimmed = value.trim();\n\tif (trimmed.length === 0 || !SAFE_FIELD.test(trimmed) || CREDENTIAL_SHAPE.test(trimmed)) return null;\n\treturn trimmed;\n}\n\nfunction sanitizeSource(value: unknown): RuntimeProvenance[\"source\"] {\n\treturn value === \"session\" || value === \"assistant-message\" || value === \"subagent-child\" ? value : \"session\";\n}\n\nfunction sanitizeTimestamp(value: unknown): string {\n\treturn typeof value === \"string\" && ISO_TIMESTAMP.test(value) ? value : new Date().toISOString();\n}\n\n/** Build a provenance record from trusted caller-supplied facts. Never throws. */\nexport function buildRuntimeProvenance(input: RuntimeProvenanceInput = {}): RuntimeProvenance {\n\treturn Object.freeze({\n\t\tschemaVersion: RUNTIME_PROVENANCE_SCHEMA_VERSION,\n\t\trequestedModel: sanitizeField(input.requestedModel),\n\t\tselectedModel: sanitizeField(input.selectedModel),\n\t\tresponseModel: sanitizeField(input.responseModel),\n\t\trequestedThinking: sanitizeField(input.requestedThinking),\n\t\teffectiveThinking: sanitizeField(input.effectiveThinking),\n\t\tsource: sanitizeSource(input.source),\n\t\tobservedAt: sanitizeTimestamp(input.observedAt),\n\t});\n}\n\n/**\n * Fail-closed parse of untrusted persisted data. Returns `null` (never throws)\n * when any field is invalid, credential-shaped, oversized, or when the key set\n * or schema version is wrong. Missing fields become `null` values.\n */\nexport function parseRuntimeProvenance(value: unknown): RuntimeProvenance | null {\n\tif (!isRecord(value)) return null;\n\tfor (const key of Object.keys(value)) {\n\t\tif (!ALLOWED_KEYS.has(key)) return null;\n\t}\n\tif (value.schemaVersion !== RUNTIME_PROVENANCE_SCHEMA_VERSION) return null;\n\tif (value.source !== \"session\" && value.source !== \"assistant-message\" && value.source !== \"subagent-child\") {\n\t\treturn null;\n\t}\n\tif (value.observedAt !== undefined && !ISO_TIMESTAMP.test(String(value.observedAt))) return null;\n\ttry {\n\t\treturn Object.freeze({\n\t\t\tschemaVersion: RUNTIME_PROVENANCE_SCHEMA_VERSION,\n\t\t\trequestedModel: parseRequiredField(value.requestedModel),\n\t\t\tselectedModel: parseRequiredField(value.selectedModel),\n\t\t\tresponseModel: parseRequiredField(value.responseModel),\n\t\t\trequestedThinking: parseRequiredField(value.requestedThinking),\n\t\t\teffectiveThinking: parseRequiredField(value.effectiveThinking),\n\t\t\tsource: value.source,\n\t\t\tobservedAt: value.observedAt === undefined ? new Date().toISOString() : String(value.observedAt),\n\t\t});\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/** Missing fields parse as `null`; anything present but unusable throws (record fails closed). */\nfunction parseRequiredField(field: unknown): string | null {\n\tif (field === undefined || field === null) return null;\n\tif (typeof field !== \"string\") throw new TypeError(\"runtime provenance field is not a string\");\n\tconst sanitized = sanitizeField(field);\n\tif (sanitized === null) throw new TypeError(\"runtime provenance field is invalid or credential-shaped\");\n\treturn sanitized;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/backend.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAErG,MAAM,MAAM,kBAAkB,GAAG,aAAa,GAAG,UAAU,GAAG,YAAY,CAAC;AAE3E,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE;QACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;QACtC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;QACvC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;QACtC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;KACtC,CAAC;IACF,QAAQ,CAAC,OAAO,EAAE;QACjB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;QAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5C,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;KAC3C,CAAC;CACF;AAuBD,wBAAgB,oBAAoB,IAAI,oBAAoB,CAsB3D;AAgCD,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,iBAAiB,GAAG,SAAS,MAAM,EAAE,CAElF;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,iBAAiB,GAAG,SAAS,MAAM,EAAE,CAsCpF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,iBAAiB,GAAG,SAAS,MAAM,EAAE,CAQhH;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,oBAAoB,GAAG,eAAe,CA2BjH","sourcesContent":["import { execSync } from \"node:child_process\";\nimport type { NetworkMode, SandboxBackendStatus, SandboxDecision, SandboxPolicy } from \"./policy.ts\";\n\nexport type SandboxBackendType = \"unsupported\" | \"seatbelt\" | \"bubblewrap\";\n\nexport interface SandboxInvocation {\n\treadonly argv: readonly string[];\n\treadonly cwd: string;\n\treadonly env: Record<string, string>;\n\treadonly filesystem: {\n\t\treadonly root: string;\n\t\treadonly readAllow: readonly string[];\n\t\treadonly writeAllow: readonly string[];\n\t\treadonly tempWrite: readonly string[];\n\t\treadonly denyWrite: readonly string[];\n\t};\n\treadonly network: {\n\t\treadonly mode: NetworkMode;\n\t\treadonly allowedDomains?: readonly string[];\n\t\treadonly deniedDomains?: readonly string[];\n\t};\n}\n\nfunction commandExists(command: string): boolean {\n\ttry {\n\t\texecSync(`command -v ${command}`, { stdio: \"ignore\" });\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction userNamespacesEnabled(): boolean {\n\ttry {\n\t\tconst value = execSync(\"sysctl -n kernel.unprivileged_userns_clone\", {\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t\tencoding: \"utf8\",\n\t\t}).trim();\n\t\treturn value === \"1\";\n\t} catch {\n\t\treturn true;\n\t}\n}\n\nexport function detectSandboxBackend(): SandboxBackendStatus {\n\tif (process.platform === \"darwin\") {\n\t\tconst available = commandExists(\"sandbox-exec\");\n\t\treturn {\n\t\t\tplatform: \"macos\",\n\t\t\tbackendAvailable: available,\n\t\t\tdomainAllowlistAvailable: available,\n\t\t};\n\t}\n\tif (process.platform === \"linux\") {\n\t\tconst bwrap = commandExists(\"bwrap\");\n\t\tconst userns = userNamespacesEnabled();\n\t\treturn {\n\t\t\tplatform: \"linux\",\n\t\t\tbackendAvailable: bwrap && userns,\n\t\t\tdomainAllowlistAvailable: false,\n\t\t};\n\t}\n\treturn {\n\t\tplatform: \"unsupported\",\n\t\tbackendAvailable: false,\n\t};\n}\n\nfunction seatbeltProfile(invocation: SandboxInvocation): string {\n\tconst lines: string[] = [\"(version 1)\", \"(debug deny)\", `(allow default)`, `(deny network*)`];\n\n\tfor (const path of invocation.filesystem.readAllow) {\n\t\tlines.push(`(allow file-read* (subpath \"${path}\"))`);\n\t}\n\tfor (const path of invocation.filesystem.writeAllow) {\n\t\tlines.push(`(allow file-write* (subpath \"${path}\"))`);\n\t}\n\tfor (const path of invocation.filesystem.tempWrite) {\n\t\tlines.push(`(allow file-write* (subpath \"${path}\"))`);\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\tlines.push(`(deny file-write* (subpath \"${path}\"))`);\n\t}\n\n\tif (invocation.network.mode === \"all-explicit\") {\n\t\tlines.push(\"(allow network-outbound)\");\n\t} else if (invocation.network.mode === \"domain-allowlist\" && invocation.network.allowedDomains) {\n\t\tfor (const domain of invocation.network.allowedDomains) {\n\t\t\tlines.push(`(allow network-outbound (remote \"${domain}\"))`);\n\t\t}\n\t} else if (invocation.network.mode === \"loopback\") {\n\t\tlines.push('(allow network-outbound (remote \"localhost\"))');\n\t\tlines.push('(allow network-outbound (remote \"127.0.0.1\"))');\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function buildSeatbeltArgv(invocation: SandboxInvocation): readonly string[] {\n\treturn [\"sandbox-exec\", \"-p\", seatbeltProfile(invocation), ...invocation.argv];\n}\n\nexport function buildBubblewrapArgv(invocation: SandboxInvocation): readonly string[] {\n\tconst argv: string[] = [\"bwrap\", \"--unshare-all\"];\n\n\targv.push(\"--bind\", invocation.filesystem.root, invocation.filesystem.root);\n\n\tfor (const path of invocation.filesystem.readAllow) {\n\t\tif (path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--ro-bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.writeAllow) {\n\t\tif (path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.tempWrite) {\n\t\targv.push(\"--tmpfs\", path);\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\targv.push(\"--remount-ro\", path);\n\t}\n\n\tif (invocation.network.mode === \"none\") {\n\t\targv.push(\"--unshare-net\");\n\t} else if (invocation.network.mode === \"loopback\") {\n\t\targv.push(\"--share-net\");\n\t} else {\n\t\targv.push(\"--share-net\");\n\t}\n\n\targv.push(\"--chdir\", invocation.cwd);\n\targv.push(\"--clearenv\");\n\tfor (const [key, value] of Object.entries(invocation.env)) {\n\t\targv.push(\"--setenv\", key, value);\n\t}\n\n\targv.push(...invocation.argv);\n\treturn argv;\n}\n\nexport function buildBackendArgv(backend: SandboxBackendStatus, invocation: SandboxInvocation): readonly string[] {\n\tif (backend.platform === \"macos\" && backend.backendAvailable) {\n\t\treturn buildSeatbeltArgv(invocation);\n\t}\n\tif (backend.platform === \"linux\" && backend.backendAvailable) {\n\t\treturn buildBubblewrapArgv(invocation);\n\t}\n\tthrow new Error(`Unsupported or unavailable sandbox backend: ${backend.platform}`);\n}\n\nexport function strictBackendMissingVerdict(policy: SandboxPolicy, backend: SandboxBackendStatus): SandboxDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.off\",\n\t\t\treason: \"Sandbox policy is disabled.\",\n\t\t};\n\t}\n\tif (backend.backendAvailable) {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.backend_available\",\n\t\t\treason: \"Sandbox backend is available.\",\n\t\t};\n\t}\n\tif (policy.mode === \"audit\") {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.audit_fallback\",\n\t\t\treason: \"Audit mode permits execution without backend.\",\n\t\t};\n\t}\n\treturn {\n\t\tallowed: false,\n\t\trule: \"sandbox.backend_missing\",\n\t\treason: \"Enforce mode blocks shell and exec when no backend is available.\",\n\t};\n}\n"]}
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/backend.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,oBAAoB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAErG,MAAM,MAAM,kBAAkB,GAAG,aAAa,GAAG,UAAU,GAAG,YAAY,CAAC;AAE3E,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE;QACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QACtB,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;QACtC,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;QACvC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;QACtC,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;KACtC,CAAC;IACF,QAAQ,CAAC,OAAO,EAAE;QACjB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;QAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5C,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;KAC3C,CAAC;CACF;AAuBD,wBAAgB,oBAAoB,IAAI,oBAAoB,CAsB3D;AAyCD,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,iBAAiB,GAAG,SAAS,MAAM,EAAE,CAElF;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,iBAAiB,GAAG,SAAS,MAAM,EAAE,CA+CpF;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,iBAAiB,GAAG,SAAS,MAAM,EAAE,CAQhH;AAED,wBAAgB,2BAA2B,CAAC,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,oBAAoB,GAAG,eAAe,CA2BjH","sourcesContent":["import { execSync } from \"node:child_process\";\nimport type { NetworkMode, SandboxBackendStatus, SandboxDecision, SandboxPolicy } from \"./policy.ts\";\n\nexport type SandboxBackendType = \"unsupported\" | \"seatbelt\" | \"bubblewrap\";\n\nexport interface SandboxInvocation {\n\treadonly argv: readonly string[];\n\treadonly cwd: string;\n\treadonly env: Record<string, string>;\n\treadonly filesystem: {\n\t\treadonly root: string;\n\t\treadonly readAllow: readonly string[];\n\t\treadonly writeAllow: readonly string[];\n\t\treadonly tempWrite: readonly string[];\n\t\treadonly denyWrite: readonly string[];\n\t};\n\treadonly network: {\n\t\treadonly mode: NetworkMode;\n\t\treadonly allowedDomains?: readonly string[];\n\t\treadonly deniedDomains?: readonly string[];\n\t};\n}\n\nfunction commandExists(command: string): boolean {\n\ttry {\n\t\texecSync(`command -v ${command}`, { stdio: \"ignore\" });\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction userNamespacesEnabled(): boolean {\n\ttry {\n\t\tconst value = execSync(\"sysctl -n kernel.unprivileged_userns_clone\", {\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t\tencoding: \"utf8\",\n\t\t}).trim();\n\t\treturn value === \"1\";\n\t} catch {\n\t\treturn true;\n\t}\n}\n\nexport function detectSandboxBackend(): SandboxBackendStatus {\n\tif (process.platform === \"darwin\") {\n\t\tconst available = commandExists(\"sandbox-exec\");\n\t\treturn {\n\t\t\tplatform: \"macos\",\n\t\t\tbackendAvailable: available,\n\t\t\tdomainAllowlistAvailable: available,\n\t\t};\n\t}\n\tif (process.platform === \"linux\") {\n\t\tconst bwrap = commandExists(\"bwrap\");\n\t\tconst userns = userNamespacesEnabled();\n\t\treturn {\n\t\t\tplatform: \"linux\",\n\t\t\tbackendAvailable: bwrap && userns,\n\t\t\tdomainAllowlistAvailable: false,\n\t\t};\n\t}\n\treturn {\n\t\tplatform: \"unsupported\",\n\t\tbackendAvailable: false,\n\t};\n}\n\nfunction seatbeltLiteral(value: string): string {\n\treturn JSON.stringify(value);\n}\n\nfunction seatbeltProfile(invocation: SandboxInvocation): string {\n\tconst lines: string[] = [\"(version 1)\", \"(debug deny)\", \"(allow default)\"];\n\tconst writablePaths = [...new Set([...invocation.filesystem.writeAllow, ...invocation.filesystem.tempWrite])];\n\n\tif (writablePaths.length === 0) {\n\t\tlines.push(\"(deny file-write*)\");\n\t} else {\n\t\tlines.push(\"(deny file-write*\");\n\t\tfor (const path of writablePaths) {\n\t\t\tlines.push(` (require-not (subpath ${seatbeltLiteral(path)}))`);\n\t\t}\n\t\tlines.push(' (require-not (literal \"/dev/null\"))');\n\t\tlines.push(' (require-not (literal \"/dev/tty\"))');\n\t\tlines.push(\")\");\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\tlines.push(`(deny file-write* (subpath ${seatbeltLiteral(path)}))`);\n\t}\n\n\tif (invocation.network.mode === \"none\") {\n\t\tlines.push(\"(deny network*)\");\n\t} else if (invocation.network.mode === \"domain-allowlist\" && invocation.network.allowedDomains) {\n\t\tlines.push(\"(deny network*)\");\n\t\tfor (const domain of invocation.network.allowedDomains) {\n\t\t\tlines.push(`(allow network-outbound (remote ${seatbeltLiteral(domain)}))`);\n\t\t}\n\t} else if (invocation.network.mode === \"loopback\") {\n\t\tlines.push(\"(deny network*)\");\n\t\tlines.push('(allow network-outbound (remote \"localhost\"))');\n\t\tlines.push('(allow network-outbound (remote \"127.0.0.1\"))');\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function buildSeatbeltArgv(invocation: SandboxInvocation): readonly string[] {\n\treturn [\"sandbox-exec\", \"-p\", seatbeltProfile(invocation), ...invocation.argv];\n}\n\nexport function buildBubblewrapArgv(invocation: SandboxInvocation): readonly string[] {\n\tconst argv: string[] = [\n\t\t\"bwrap\",\n\t\t\"--die-with-parent\",\n\t\t\"--new-session\",\n\t\t\"--unshare-all\",\n\t\t\"--ro-bind\",\n\t\t\"/\",\n\t\t\"/\",\n\t\t\"--proc\",\n\t\t\"/proc\",\n\t\t\"--dev\",\n\t\t\"/dev\",\n\t];\n\n\tfor (const path of invocation.filesystem.readAllow) {\n\t\tif (path !== \"/\" && path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--ro-bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.tempWrite) {\n\t\targv.push(\"--bind\", path, path);\n\t}\n\targv.push(\"--bind\", invocation.filesystem.root, invocation.filesystem.root);\n\tfor (const path of invocation.filesystem.writeAllow) {\n\t\tif (path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\targv.push(\"--remount-ro\", path);\n\t}\n\n\tif (invocation.network.mode === \"none\") {\n\t\targv.push(\"--unshare-net\");\n\t} else {\n\t\targv.push(\"--share-net\");\n\t}\n\n\targv.push(\"--chdir\", invocation.cwd);\n\targv.push(\"--clearenv\");\n\tfor (const [key, value] of Object.entries(invocation.env)) {\n\t\targv.push(\"--setenv\", key, value);\n\t}\n\n\targv.push(...invocation.argv);\n\treturn argv;\n}\n\nexport function buildBackendArgv(backend: SandboxBackendStatus, invocation: SandboxInvocation): readonly string[] {\n\tif (backend.platform === \"macos\" && backend.backendAvailable) {\n\t\treturn buildSeatbeltArgv(invocation);\n\t}\n\tif (backend.platform === \"linux\" && backend.backendAvailable) {\n\t\treturn buildBubblewrapArgv(invocation);\n\t}\n\tthrow new Error(`Unsupported or unavailable sandbox backend: ${backend.platform}`);\n}\n\nexport function strictBackendMissingVerdict(policy: SandboxPolicy, backend: SandboxBackendStatus): SandboxDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.off\",\n\t\t\treason: \"Sandbox policy is disabled.\",\n\t\t};\n\t}\n\tif (backend.backendAvailable) {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.backend_available\",\n\t\t\treason: \"Sandbox backend is available.\",\n\t\t};\n\t}\n\tif (policy.mode === \"audit\") {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.audit_fallback\",\n\t\t\treason: \"Audit mode permits execution without backend.\",\n\t\t};\n\t}\n\treturn {\n\t\tallowed: false,\n\t\trule: \"sandbox.backend_missing\",\n\t\treason: \"Enforce mode blocks shell and exec when no backend is available.\",\n\t};\n}\n"]}
@@ -43,29 +43,38 @@ export function detectSandboxBackend() {
43
43
  backendAvailable: false,
44
44
  };
45
45
  }
46
+ function seatbeltLiteral(value) {
47
+ return JSON.stringify(value);
48
+ }
46
49
  function seatbeltProfile(invocation) {
47
- const lines = ["(version 1)", "(debug deny)", `(allow default)`, `(deny network*)`];
48
- for (const path of invocation.filesystem.readAllow) {
49
- lines.push(`(allow file-read* (subpath "${path}"))`);
50
+ const lines = ["(version 1)", "(debug deny)", "(allow default)"];
51
+ const writablePaths = [...new Set([...invocation.filesystem.writeAllow, ...invocation.filesystem.tempWrite])];
52
+ if (writablePaths.length === 0) {
53
+ lines.push("(deny file-write*)");
50
54
  }
51
- for (const path of invocation.filesystem.writeAllow) {
52
- lines.push(`(allow file-write* (subpath "${path}"))`);
53
- }
54
- for (const path of invocation.filesystem.tempWrite) {
55
- lines.push(`(allow file-write* (subpath "${path}"))`);
55
+ else {
56
+ lines.push("(deny file-write*");
57
+ for (const path of writablePaths) {
58
+ lines.push(` (require-not (subpath ${seatbeltLiteral(path)}))`);
59
+ }
60
+ lines.push(' (require-not (literal "/dev/null"))');
61
+ lines.push(' (require-not (literal "/dev/tty"))');
62
+ lines.push(")");
56
63
  }
57
64
  for (const path of invocation.filesystem.denyWrite) {
58
- lines.push(`(deny file-write* (subpath "${path}"))`);
65
+ lines.push(`(deny file-write* (subpath ${seatbeltLiteral(path)}))`);
59
66
  }
60
- if (invocation.network.mode === "all-explicit") {
61
- lines.push("(allow network-outbound)");
67
+ if (invocation.network.mode === "none") {
68
+ lines.push("(deny network*)");
62
69
  }
63
70
  else if (invocation.network.mode === "domain-allowlist" && invocation.network.allowedDomains) {
71
+ lines.push("(deny network*)");
64
72
  for (const domain of invocation.network.allowedDomains) {
65
- lines.push(`(allow network-outbound (remote "${domain}"))`);
73
+ lines.push(`(allow network-outbound (remote ${seatbeltLiteral(domain)}))`);
66
74
  }
67
75
  }
68
76
  else if (invocation.network.mode === "loopback") {
77
+ lines.push("(deny network*)");
69
78
  lines.push('(allow network-outbound (remote "localhost"))');
70
79
  lines.push('(allow network-outbound (remote "127.0.0.1"))');
71
80
  }
@@ -75,30 +84,39 @@ export function buildSeatbeltArgv(invocation) {
75
84
  return ["sandbox-exec", "-p", seatbeltProfile(invocation), ...invocation.argv];
76
85
  }
77
86
  export function buildBubblewrapArgv(invocation) {
78
- const argv = ["bwrap", "--unshare-all"];
79
- argv.push("--bind", invocation.filesystem.root, invocation.filesystem.root);
87
+ const argv = [
88
+ "bwrap",
89
+ "--die-with-parent",
90
+ "--new-session",
91
+ "--unshare-all",
92
+ "--ro-bind",
93
+ "/",
94
+ "/",
95
+ "--proc",
96
+ "/proc",
97
+ "--dev",
98
+ "/dev",
99
+ ];
80
100
  for (const path of invocation.filesystem.readAllow) {
81
- if (path !== invocation.filesystem.root) {
101
+ if (path !== "/" && path !== invocation.filesystem.root) {
82
102
  argv.push("--ro-bind", path, path);
83
103
  }
84
104
  }
105
+ for (const path of invocation.filesystem.tempWrite) {
106
+ argv.push("--bind", path, path);
107
+ }
108
+ argv.push("--bind", invocation.filesystem.root, invocation.filesystem.root);
85
109
  for (const path of invocation.filesystem.writeAllow) {
86
110
  if (path !== invocation.filesystem.root) {
87
111
  argv.push("--bind", path, path);
88
112
  }
89
113
  }
90
- for (const path of invocation.filesystem.tempWrite) {
91
- argv.push("--tmpfs", path);
92
- }
93
114
  for (const path of invocation.filesystem.denyWrite) {
94
115
  argv.push("--remount-ro", path);
95
116
  }
96
117
  if (invocation.network.mode === "none") {
97
118
  argv.push("--unshare-net");
98
119
  }
99
- else if (invocation.network.mode === "loopback") {
100
- argv.push("--share-net");
101
- }
102
120
  else {
103
121
  argv.push("--share-net");
104
122
  }
@@ -1 +1 @@
1
- {"version":3,"file":"backend.js","sourceRoot":"","sources":["../../../src/core/sandbox/backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from \"node:child_process\";\nimport type { NetworkMode, SandboxBackendStatus, SandboxDecision, SandboxPolicy } from \"./policy.ts\";\n\nexport type SandboxBackendType = \"unsupported\" | \"seatbelt\" | \"bubblewrap\";\n\nexport interface SandboxInvocation {\n\treadonly argv: readonly string[];\n\treadonly cwd: string;\n\treadonly env: Record<string, string>;\n\treadonly filesystem: {\n\t\treadonly root: string;\n\t\treadonly readAllow: readonly string[];\n\t\treadonly writeAllow: readonly string[];\n\t\treadonly tempWrite: readonly string[];\n\t\treadonly denyWrite: readonly string[];\n\t};\n\treadonly network: {\n\t\treadonly mode: NetworkMode;\n\t\treadonly allowedDomains?: readonly string[];\n\t\treadonly deniedDomains?: readonly string[];\n\t};\n}\n\nfunction commandExists(command: string): boolean {\n\ttry {\n\t\texecSync(`command -v ${command}`, { stdio: \"ignore\" });\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction userNamespacesEnabled(): boolean {\n\ttry {\n\t\tconst value = execSync(\"sysctl -n kernel.unprivileged_userns_clone\", {\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t\tencoding: \"utf8\",\n\t\t}).trim();\n\t\treturn value === \"1\";\n\t} catch {\n\t\treturn true;\n\t}\n}\n\nexport function detectSandboxBackend(): SandboxBackendStatus {\n\tif (process.platform === \"darwin\") {\n\t\tconst available = commandExists(\"sandbox-exec\");\n\t\treturn {\n\t\t\tplatform: \"macos\",\n\t\t\tbackendAvailable: available,\n\t\t\tdomainAllowlistAvailable: available,\n\t\t};\n\t}\n\tif (process.platform === \"linux\") {\n\t\tconst bwrap = commandExists(\"bwrap\");\n\t\tconst userns = userNamespacesEnabled();\n\t\treturn {\n\t\t\tplatform: \"linux\",\n\t\t\tbackendAvailable: bwrap && userns,\n\t\t\tdomainAllowlistAvailable: false,\n\t\t};\n\t}\n\treturn {\n\t\tplatform: \"unsupported\",\n\t\tbackendAvailable: false,\n\t};\n}\n\nfunction seatbeltProfile(invocation: SandboxInvocation): string {\n\tconst lines: string[] = [\"(version 1)\", \"(debug deny)\", `(allow default)`, `(deny network*)`];\n\n\tfor (const path of invocation.filesystem.readAllow) {\n\t\tlines.push(`(allow file-read* (subpath \"${path}\"))`);\n\t}\n\tfor (const path of invocation.filesystem.writeAllow) {\n\t\tlines.push(`(allow file-write* (subpath \"${path}\"))`);\n\t}\n\tfor (const path of invocation.filesystem.tempWrite) {\n\t\tlines.push(`(allow file-write* (subpath \"${path}\"))`);\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\tlines.push(`(deny file-write* (subpath \"${path}\"))`);\n\t}\n\n\tif (invocation.network.mode === \"all-explicit\") {\n\t\tlines.push(\"(allow network-outbound)\");\n\t} else if (invocation.network.mode === \"domain-allowlist\" && invocation.network.allowedDomains) {\n\t\tfor (const domain of invocation.network.allowedDomains) {\n\t\t\tlines.push(`(allow network-outbound (remote \"${domain}\"))`);\n\t\t}\n\t} else if (invocation.network.mode === \"loopback\") {\n\t\tlines.push('(allow network-outbound (remote \"localhost\"))');\n\t\tlines.push('(allow network-outbound (remote \"127.0.0.1\"))');\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function buildSeatbeltArgv(invocation: SandboxInvocation): readonly string[] {\n\treturn [\"sandbox-exec\", \"-p\", seatbeltProfile(invocation), ...invocation.argv];\n}\n\nexport function buildBubblewrapArgv(invocation: SandboxInvocation): readonly string[] {\n\tconst argv: string[] = [\"bwrap\", \"--unshare-all\"];\n\n\targv.push(\"--bind\", invocation.filesystem.root, invocation.filesystem.root);\n\n\tfor (const path of invocation.filesystem.readAllow) {\n\t\tif (path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--ro-bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.writeAllow) {\n\t\tif (path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.tempWrite) {\n\t\targv.push(\"--tmpfs\", path);\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\targv.push(\"--remount-ro\", path);\n\t}\n\n\tif (invocation.network.mode === \"none\") {\n\t\targv.push(\"--unshare-net\");\n\t} else if (invocation.network.mode === \"loopback\") {\n\t\targv.push(\"--share-net\");\n\t} else {\n\t\targv.push(\"--share-net\");\n\t}\n\n\targv.push(\"--chdir\", invocation.cwd);\n\targv.push(\"--clearenv\");\n\tfor (const [key, value] of Object.entries(invocation.env)) {\n\t\targv.push(\"--setenv\", key, value);\n\t}\n\n\targv.push(...invocation.argv);\n\treturn argv;\n}\n\nexport function buildBackendArgv(backend: SandboxBackendStatus, invocation: SandboxInvocation): readonly string[] {\n\tif (backend.platform === \"macos\" && backend.backendAvailable) {\n\t\treturn buildSeatbeltArgv(invocation);\n\t}\n\tif (backend.platform === \"linux\" && backend.backendAvailable) {\n\t\treturn buildBubblewrapArgv(invocation);\n\t}\n\tthrow new Error(`Unsupported or unavailable sandbox backend: ${backend.platform}`);\n}\n\nexport function strictBackendMissingVerdict(policy: SandboxPolicy, backend: SandboxBackendStatus): SandboxDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.off\",\n\t\t\treason: \"Sandbox policy is disabled.\",\n\t\t};\n\t}\n\tif (backend.backendAvailable) {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.backend_available\",\n\t\t\treason: \"Sandbox backend is available.\",\n\t\t};\n\t}\n\tif (policy.mode === \"audit\") {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.audit_fallback\",\n\t\t\treason: \"Audit mode permits execution without backend.\",\n\t\t};\n\t}\n\treturn {\n\t\tallowed: false,\n\t\trule: \"sandbox.backend_missing\",\n\t\treason: \"Enforce mode blocks shell and exec when no backend is available.\",\n\t};\n}\n"]}
1
+ {"version":3,"file":"backend.js","sourceRoot":"","sources":["../../../src/core/sandbox/backend.ts"],"names":[],"mappings":"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","sourcesContent":["import { execSync } from \"node:child_process\";\nimport type { NetworkMode, SandboxBackendStatus, SandboxDecision, SandboxPolicy } from \"./policy.ts\";\n\nexport type SandboxBackendType = \"unsupported\" | \"seatbelt\" | \"bubblewrap\";\n\nexport interface SandboxInvocation {\n\treadonly argv: readonly string[];\n\treadonly cwd: string;\n\treadonly env: Record<string, string>;\n\treadonly filesystem: {\n\t\treadonly root: string;\n\t\treadonly readAllow: readonly string[];\n\t\treadonly writeAllow: readonly string[];\n\t\treadonly tempWrite: readonly string[];\n\t\treadonly denyWrite: readonly string[];\n\t};\n\treadonly network: {\n\t\treadonly mode: NetworkMode;\n\t\treadonly allowedDomains?: readonly string[];\n\t\treadonly deniedDomains?: readonly string[];\n\t};\n}\n\nfunction commandExists(command: string): boolean {\n\ttry {\n\t\texecSync(`command -v ${command}`, { stdio: \"ignore\" });\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nfunction userNamespacesEnabled(): boolean {\n\ttry {\n\t\tconst value = execSync(\"sysctl -n kernel.unprivileged_userns_clone\", {\n\t\t\tstdio: [\"ignore\", \"pipe\", \"ignore\"],\n\t\t\tencoding: \"utf8\",\n\t\t}).trim();\n\t\treturn value === \"1\";\n\t} catch {\n\t\treturn true;\n\t}\n}\n\nexport function detectSandboxBackend(): SandboxBackendStatus {\n\tif (process.platform === \"darwin\") {\n\t\tconst available = commandExists(\"sandbox-exec\");\n\t\treturn {\n\t\t\tplatform: \"macos\",\n\t\t\tbackendAvailable: available,\n\t\t\tdomainAllowlistAvailable: available,\n\t\t};\n\t}\n\tif (process.platform === \"linux\") {\n\t\tconst bwrap = commandExists(\"bwrap\");\n\t\tconst userns = userNamespacesEnabled();\n\t\treturn {\n\t\t\tplatform: \"linux\",\n\t\t\tbackendAvailable: bwrap && userns,\n\t\t\tdomainAllowlistAvailable: false,\n\t\t};\n\t}\n\treturn {\n\t\tplatform: \"unsupported\",\n\t\tbackendAvailable: false,\n\t};\n}\n\nfunction seatbeltLiteral(value: string): string {\n\treturn JSON.stringify(value);\n}\n\nfunction seatbeltProfile(invocation: SandboxInvocation): string {\n\tconst lines: string[] = [\"(version 1)\", \"(debug deny)\", \"(allow default)\"];\n\tconst writablePaths = [...new Set([...invocation.filesystem.writeAllow, ...invocation.filesystem.tempWrite])];\n\n\tif (writablePaths.length === 0) {\n\t\tlines.push(\"(deny file-write*)\");\n\t} else {\n\t\tlines.push(\"(deny file-write*\");\n\t\tfor (const path of writablePaths) {\n\t\t\tlines.push(` (require-not (subpath ${seatbeltLiteral(path)}))`);\n\t\t}\n\t\tlines.push(' (require-not (literal \"/dev/null\"))');\n\t\tlines.push(' (require-not (literal \"/dev/tty\"))');\n\t\tlines.push(\")\");\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\tlines.push(`(deny file-write* (subpath ${seatbeltLiteral(path)}))`);\n\t}\n\n\tif (invocation.network.mode === \"none\") {\n\t\tlines.push(\"(deny network*)\");\n\t} else if (invocation.network.mode === \"domain-allowlist\" && invocation.network.allowedDomains) {\n\t\tlines.push(\"(deny network*)\");\n\t\tfor (const domain of invocation.network.allowedDomains) {\n\t\t\tlines.push(`(allow network-outbound (remote ${seatbeltLiteral(domain)}))`);\n\t\t}\n\t} else if (invocation.network.mode === \"loopback\") {\n\t\tlines.push(\"(deny network*)\");\n\t\tlines.push('(allow network-outbound (remote \"localhost\"))');\n\t\tlines.push('(allow network-outbound (remote \"127.0.0.1\"))');\n\t}\n\n\treturn lines.join(\"\\n\");\n}\n\nexport function buildSeatbeltArgv(invocation: SandboxInvocation): readonly string[] {\n\treturn [\"sandbox-exec\", \"-p\", seatbeltProfile(invocation), ...invocation.argv];\n}\n\nexport function buildBubblewrapArgv(invocation: SandboxInvocation): readonly string[] {\n\tconst argv: string[] = [\n\t\t\"bwrap\",\n\t\t\"--die-with-parent\",\n\t\t\"--new-session\",\n\t\t\"--unshare-all\",\n\t\t\"--ro-bind\",\n\t\t\"/\",\n\t\t\"/\",\n\t\t\"--proc\",\n\t\t\"/proc\",\n\t\t\"--dev\",\n\t\t\"/dev\",\n\t];\n\n\tfor (const path of invocation.filesystem.readAllow) {\n\t\tif (path !== \"/\" && path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--ro-bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.tempWrite) {\n\t\targv.push(\"--bind\", path, path);\n\t}\n\targv.push(\"--bind\", invocation.filesystem.root, invocation.filesystem.root);\n\tfor (const path of invocation.filesystem.writeAllow) {\n\t\tif (path !== invocation.filesystem.root) {\n\t\t\targv.push(\"--bind\", path, path);\n\t\t}\n\t}\n\tfor (const path of invocation.filesystem.denyWrite) {\n\t\targv.push(\"--remount-ro\", path);\n\t}\n\n\tif (invocation.network.mode === \"none\") {\n\t\targv.push(\"--unshare-net\");\n\t} else {\n\t\targv.push(\"--share-net\");\n\t}\n\n\targv.push(\"--chdir\", invocation.cwd);\n\targv.push(\"--clearenv\");\n\tfor (const [key, value] of Object.entries(invocation.env)) {\n\t\targv.push(\"--setenv\", key, value);\n\t}\n\n\targv.push(...invocation.argv);\n\treturn argv;\n}\n\nexport function buildBackendArgv(backend: SandboxBackendStatus, invocation: SandboxInvocation): readonly string[] {\n\tif (backend.platform === \"macos\" && backend.backendAvailable) {\n\t\treturn buildSeatbeltArgv(invocation);\n\t}\n\tif (backend.platform === \"linux\" && backend.backendAvailable) {\n\t\treturn buildBubblewrapArgv(invocation);\n\t}\n\tthrow new Error(`Unsupported or unavailable sandbox backend: ${backend.platform}`);\n}\n\nexport function strictBackendMissingVerdict(policy: SandboxPolicy, backend: SandboxBackendStatus): SandboxDecision {\n\tif (policy.mode === \"off\") {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.off\",\n\t\t\treason: \"Sandbox policy is disabled.\",\n\t\t};\n\t}\n\tif (backend.backendAvailable) {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.backend_available\",\n\t\t\treason: \"Sandbox backend is available.\",\n\t\t};\n\t}\n\tif (policy.mode === \"audit\") {\n\t\treturn {\n\t\t\tallowed: true,\n\t\t\trule: \"sandbox.audit_fallback\",\n\t\t\treason: \"Audit mode permits execution without backend.\",\n\t\t};\n\t}\n\treturn {\n\t\tallowed: false,\n\t\trule: \"sandbox.backend_missing\",\n\t\treason: \"Enforce mode blocks shell and exec when no backend is available.\",\n\t};\n}\n"]}
@@ -1,14 +1,13 @@
1
1
  import type { SandboxMode, SandboxPolicy } from "./policy.ts";
2
2
  export type BashSandboxMode = SandboxMode;
3
3
  /**
4
- * Resolve the bash sandbox mode. Default is `audit` (default-on); `OMK_BASH_SANDBOX=0`
5
- * restores the legacy unsandboxed path, `=enforce` activates the OS backend.
4
+ * Resolve the bash sandbox mode. Enforcement is the fail-safe default, including
5
+ * for unknown values. `audit` and `off` are explicit compatibility escape hatches.
6
6
  */
7
7
  export declare function resolveBashSandboxMode(env?: Record<string, string | undefined>): SandboxMode;
8
8
  /**
9
- * Workspace-write policy for the session bash runtime. Network stays fully open
10
- * (bash without network is not a usable tool); the filesystem is rooted at the
11
- * workspace with the OS temp dir as the only additional write target.
9
+ * Workspace-write policy for the session bash runtime. Outbound network access
10
+ * is disabled; writes are limited to the workspace and the OS temp directory.
12
11
  */
13
12
  export declare function createWorkspaceSandboxPolicy(root: string, mode: Exclude<SandboxMode, "off">): SandboxPolicy;
14
13
  //# sourceMappingURL=default-policy.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"default-policy.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/default-policy.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE9D,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC;AAK1C;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,WAAW,CAM5F;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,GAAG,aAAa,CA0B3G","sourcesContent":["/**\n * Default bash sandbox policy wiring (default-on audit, opt-out via OMK_BASH_SANDBOX=0).\n *\n * Two live modes:\n * - `audit` — preflight only, spawn stays unwrapped; every decision is eligible\n * for replay-ledger recording by the session (tamper-evident audit trail).\n * - `enforce` — wraps the spawn with the real OS backend (macOS seatbelt /\n * Linux bubblewrap) and fails closed when no backend is available.\n */\nimport { tmpdir } from \"node:os\";\nimport type { SandboxMode, SandboxPolicy } from \"./policy.ts\";\n\nexport type BashSandboxMode = SandboxMode;\n\nconst OFF_VALUES = new Set([\"0\", \"off\", \"false\", \"disable\", \"disabled\", \"none\"]);\nconst ENFORCE_VALUES = new Set([\"1\", \"true\", \"on\", \"yes\", \"enforce\", \"strict\"]);\n\n/**\n * Resolve the bash sandbox mode. Default is `audit` (default-on); `OMK_BASH_SANDBOX=0`\n * restores the legacy unsandboxed path, `=enforce` activates the OS backend.\n */\nexport function resolveBashSandboxMode(env?: Record<string, string | undefined>): SandboxMode {\n\tconst source: Record<string, string | undefined> = env ?? process.env;\n\tconst raw = (source.OMK_BASH_SANDBOX ?? \"\").trim().toLowerCase();\n\tif (OFF_VALUES.has(raw)) return \"off\";\n\tif (ENFORCE_VALUES.has(raw)) return \"enforce\";\n\treturn \"audit\";\n}\n\n/**\n * Workspace-write policy for the session bash runtime. Network stays fully open\n * (bash without network is not a usable tool); the filesystem is rooted at the\n * workspace with the OS temp dir as the only additional write target.\n */\nexport function createWorkspaceSandboxPolicy(root: string, mode: Exclude<SandboxMode, \"off\">): SandboxPolicy {\n\treturn {\n\t\tmode,\n\t\tprofile: \"workspace-write\",\n\t\tfilesystem: {\n\t\t\troot,\n\t\t\treadAllow: [root],\n\t\t\treadDeny: [],\n\t\t\twriteAllow: [root],\n\t\t\tdenyWrite: [],\n\t\t\ttempWrite: [tmpdir()],\n\t\t\tfollowSymlinks: false,\n\t\t},\n\t\tnetwork: {\n\t\t\tmode: \"all-explicit\",\n\t\t\tallowedDomains: [],\n\t\t\tdeniedDomains: [],\n\t\t\tallowUnixSockets: [],\n\t\t\tallowBrowser: false,\n\t\t},\n\t\tprocess: {\n\t\t\tallowExec: true,\n\t\t\tallowShell: true,\n\t\t\tallowPrivilege: false,\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"default-policy.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/default-policy.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE9D,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC;AAI1C;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GAAG,WAAW,CAM5F;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,GAAG,aAAa,CA0B3G","sourcesContent":["/**\n * Default bash sandbox policy wiring (default-on enforcement, explicit opt-out).\n *\n * Two live modes:\n * - `audit` — preflight only, spawn stays unwrapped; every decision is eligible\n * for replay-ledger recording by the session (tamper-evident audit trail).\n * - `enforce` — wraps the spawn with the real OS backend (macOS seatbelt /\n * Linux bubblewrap) and fails closed when no backend is available.\n */\nimport { tmpdir } from \"node:os\";\nimport type { SandboxMode, SandboxPolicy } from \"./policy.ts\";\n\nexport type BashSandboxMode = SandboxMode;\n\nconst OFF_VALUES = new Set([\"0\", \"off\", \"false\", \"disable\", \"disabled\", \"none\"]);\n\n/**\n * Resolve the bash sandbox mode. Enforcement is the fail-safe default, including\n * for unknown values. `audit` and `off` are explicit compatibility escape hatches.\n */\nexport function resolveBashSandboxMode(env?: Record<string, string | undefined>): SandboxMode {\n\tconst source: Record<string, string | undefined> = env ?? process.env;\n\tconst raw = (source.OMK_BASH_SANDBOX ?? \"\").trim().toLowerCase();\n\tif (OFF_VALUES.has(raw)) return \"off\";\n\tif (raw === \"audit\") return \"audit\";\n\treturn \"enforce\";\n}\n\n/**\n * Workspace-write policy for the session bash runtime. Outbound network access\n * is disabled; writes are limited to the workspace and the OS temp directory.\n */\nexport function createWorkspaceSandboxPolicy(root: string, mode: Exclude<SandboxMode, \"off\">): SandboxPolicy {\n\treturn {\n\t\tmode,\n\t\tprofile: \"workspace-write\",\n\t\tfilesystem: {\n\t\t\troot,\n\t\t\treadAllow: [root],\n\t\t\treadDeny: [],\n\t\t\twriteAllow: [root],\n\t\t\tdenyWrite: [],\n\t\t\ttempWrite: [tmpdir()],\n\t\t\tfollowSymlinks: false,\n\t\t},\n\t\tnetwork: {\n\t\t\tmode: \"none\",\n\t\t\tallowedDomains: [],\n\t\t\tdeniedDomains: [],\n\t\t\tallowUnixSockets: [],\n\t\t\tallowBrowser: false,\n\t\t},\n\t\tprocess: {\n\t\t\tallowExec: true,\n\t\t\tallowShell: true,\n\t\t\tallowPrivilege: false,\n\t\t},\n\t};\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Default bash sandbox policy wiring (default-on audit, opt-out via OMK_BASH_SANDBOX=0).
2
+ * Default bash sandbox policy wiring (default-on enforcement, explicit opt-out).
3
3
  *
4
4
  * Two live modes:
5
5
  * - `audit` — preflight only, spawn stays unwrapped; every decision is eligible
@@ -9,24 +9,22 @@
9
9
  */
10
10
  import { tmpdir } from "node:os";
11
11
  const OFF_VALUES = new Set(["0", "off", "false", "disable", "disabled", "none"]);
12
- const ENFORCE_VALUES = new Set(["1", "true", "on", "yes", "enforce", "strict"]);
13
12
  /**
14
- * Resolve the bash sandbox mode. Default is `audit` (default-on); `OMK_BASH_SANDBOX=0`
15
- * restores the legacy unsandboxed path, `=enforce` activates the OS backend.
13
+ * Resolve the bash sandbox mode. Enforcement is the fail-safe default, including
14
+ * for unknown values. `audit` and `off` are explicit compatibility escape hatches.
16
15
  */
17
16
  export function resolveBashSandboxMode(env) {
18
17
  const source = env ?? process.env;
19
18
  const raw = (source.OMK_BASH_SANDBOX ?? "").trim().toLowerCase();
20
19
  if (OFF_VALUES.has(raw))
21
20
  return "off";
22
- if (ENFORCE_VALUES.has(raw))
23
- return "enforce";
24
- return "audit";
21
+ if (raw === "audit")
22
+ return "audit";
23
+ return "enforce";
25
24
  }
26
25
  /**
27
- * Workspace-write policy for the session bash runtime. Network stays fully open
28
- * (bash without network is not a usable tool); the filesystem is rooted at the
29
- * workspace with the OS temp dir as the only additional write target.
26
+ * Workspace-write policy for the session bash runtime. Outbound network access
27
+ * is disabled; writes are limited to the workspace and the OS temp directory.
30
28
  */
31
29
  export function createWorkspaceSandboxPolicy(root, mode) {
32
30
  return {
@@ -42,7 +40,7 @@ export function createWorkspaceSandboxPolicy(root, mode) {
42
40
  followSymlinks: false,
43
41
  },
44
42
  network: {
45
- mode: "all-explicit",
43
+ mode: "none",
46
44
  allowedDomains: [],
47
45
  deniedDomains: [],
48
46
  allowUnixSockets: [],
@@ -1 +1 @@
1
- {"version":3,"file":"default-policy.js","sourceRoot":"","sources":["../../../src/core/sandbox/default-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAKjC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;AACjF,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;AAEhF;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAAwC,EAAe;IAC7F,MAAM,MAAM,GAAuC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACtE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACjE,IAAI,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,IAAI,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAC9C,OAAO,OAAO,CAAC;AAAA,CACf;AAED;;;;GAIG;AACH,MAAM,UAAU,4BAA4B,CAAC,IAAY,EAAE,IAAiC,EAAiB;IAC5G,OAAO;QACN,IAAI;QACJ,OAAO,EAAE,iBAAiB;QAC1B,UAAU,EAAE;YACX,IAAI;YACJ,SAAS,EAAE,CAAC,IAAI,CAAC;YACjB,QAAQ,EAAE,EAAE;YACZ,UAAU,EAAE,CAAC,IAAI,CAAC;YAClB,SAAS,EAAE,EAAE;YACb,SAAS,EAAE,CAAC,MAAM,EAAE,CAAC;YACrB,cAAc,EAAE,KAAK;SACrB;QACD,OAAO,EAAE;YACR,IAAI,EAAE,cAAc;YACpB,cAAc,EAAE,EAAE;YAClB,aAAa,EAAE,EAAE;YACjB,gBAAgB,EAAE,EAAE;YACpB,YAAY,EAAE,KAAK;SACnB;QACD,OAAO,EAAE;YACR,SAAS,EAAE,IAAI;YACf,UAAU,EAAE,IAAI;YAChB,cAAc,EAAE,KAAK;SACrB;KACD,CAAC;AAAA,CACF","sourcesContent":["/**\n * Default bash sandbox policy wiring (default-on audit, opt-out via OMK_BASH_SANDBOX=0).\n *\n * Two live modes:\n * - `audit` — preflight only, spawn stays unwrapped; every decision is eligible\n * for replay-ledger recording by the session (tamper-evident audit trail).\n * - `enforce` — wraps the spawn with the real OS backend (macOS seatbelt /\n * Linux bubblewrap) and fails closed when no backend is available.\n */\nimport { tmpdir } from \"node:os\";\nimport type { SandboxMode, SandboxPolicy } from \"./policy.ts\";\n\nexport type BashSandboxMode = SandboxMode;\n\nconst OFF_VALUES = new Set([\"0\", \"off\", \"false\", \"disable\", \"disabled\", \"none\"]);\nconst ENFORCE_VALUES = new Set([\"1\", \"true\", \"on\", \"yes\", \"enforce\", \"strict\"]);\n\n/**\n * Resolve the bash sandbox mode. Default is `audit` (default-on); `OMK_BASH_SANDBOX=0`\n * restores the legacy unsandboxed path, `=enforce` activates the OS backend.\n */\nexport function resolveBashSandboxMode(env?: Record<string, string | undefined>): SandboxMode {\n\tconst source: Record<string, string | undefined> = env ?? process.env;\n\tconst raw = (source.OMK_BASH_SANDBOX ?? \"\").trim().toLowerCase();\n\tif (OFF_VALUES.has(raw)) return \"off\";\n\tif (ENFORCE_VALUES.has(raw)) return \"enforce\";\n\treturn \"audit\";\n}\n\n/**\n * Workspace-write policy for the session bash runtime. Network stays fully open\n * (bash without network is not a usable tool); the filesystem is rooted at the\n * workspace with the OS temp dir as the only additional write target.\n */\nexport function createWorkspaceSandboxPolicy(root: string, mode: Exclude<SandboxMode, \"off\">): SandboxPolicy {\n\treturn {\n\t\tmode,\n\t\tprofile: \"workspace-write\",\n\t\tfilesystem: {\n\t\t\troot,\n\t\t\treadAllow: [root],\n\t\t\treadDeny: [],\n\t\t\twriteAllow: [root],\n\t\t\tdenyWrite: [],\n\t\t\ttempWrite: [tmpdir()],\n\t\t\tfollowSymlinks: false,\n\t\t},\n\t\tnetwork: {\n\t\t\tmode: \"all-explicit\",\n\t\t\tallowedDomains: [],\n\t\t\tdeniedDomains: [],\n\t\t\tallowUnixSockets: [],\n\t\t\tallowBrowser: false,\n\t\t},\n\t\tprocess: {\n\t\t\tallowExec: true,\n\t\t\tallowShell: true,\n\t\t\tallowPrivilege: false,\n\t\t},\n\t};\n}\n"]}
1
+ {"version":3,"file":"default-policy.js","sourceRoot":"","sources":["../../../src/core/sandbox/default-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAKjC,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;AAEjF;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,GAAwC,EAAe;IAC7F,MAAM,MAAM,GAAuC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC;IACtE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACjE,IAAI,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,IAAI,GAAG,KAAK,OAAO;QAAE,OAAO,OAAO,CAAC;IACpC,OAAO,SAAS,CAAC;AAAA,CACjB;AAED;;;GAGG;AACH,MAAM,UAAU,4BAA4B,CAAC,IAAY,EAAE,IAAiC,EAAiB;IAC5G,OAAO;QACN,IAAI;QACJ,OAAO,EAAE,iBAAiB;QAC1B,UAAU,EAAE;YACX,IAAI;YACJ,SAAS,EAAE,CAAC,IAAI,CAAC;YACjB,QAAQ,EAAE,EAAE;YACZ,UAAU,EAAE,CAAC,IAAI,CAAC;YAClB,SAAS,EAAE,EAAE;YACb,SAAS,EAAE,CAAC,MAAM,EAAE,CAAC;YACrB,cAAc,EAAE,KAAK;SACrB;QACD,OAAO,EAAE;YACR,IAAI,EAAE,MAAM;YACZ,cAAc,EAAE,EAAE;YAClB,aAAa,EAAE,EAAE;YACjB,gBAAgB,EAAE,EAAE;YACpB,YAAY,EAAE,KAAK;SACnB;QACD,OAAO,EAAE;YACR,SAAS,EAAE,IAAI;YACf,UAAU,EAAE,IAAI;YAChB,cAAc,EAAE,KAAK;SACrB;KACD,CAAC;AAAA,CACF","sourcesContent":["/**\n * Default bash sandbox policy wiring (default-on enforcement, explicit opt-out).\n *\n * Two live modes:\n * - `audit` — preflight only, spawn stays unwrapped; every decision is eligible\n * for replay-ledger recording by the session (tamper-evident audit trail).\n * - `enforce` — wraps the spawn with the real OS backend (macOS seatbelt /\n * Linux bubblewrap) and fails closed when no backend is available.\n */\nimport { tmpdir } from \"node:os\";\nimport type { SandboxMode, SandboxPolicy } from \"./policy.ts\";\n\nexport type BashSandboxMode = SandboxMode;\n\nconst OFF_VALUES = new Set([\"0\", \"off\", \"false\", \"disable\", \"disabled\", \"none\"]);\n\n/**\n * Resolve the bash sandbox mode. Enforcement is the fail-safe default, including\n * for unknown values. `audit` and `off` are explicit compatibility escape hatches.\n */\nexport function resolveBashSandboxMode(env?: Record<string, string | undefined>): SandboxMode {\n\tconst source: Record<string, string | undefined> = env ?? process.env;\n\tconst raw = (source.OMK_BASH_SANDBOX ?? \"\").trim().toLowerCase();\n\tif (OFF_VALUES.has(raw)) return \"off\";\n\tif (raw === \"audit\") return \"audit\";\n\treturn \"enforce\";\n}\n\n/**\n * Workspace-write policy for the session bash runtime. Outbound network access\n * is disabled; writes are limited to the workspace and the OS temp directory.\n */\nexport function createWorkspaceSandboxPolicy(root: string, mode: Exclude<SandboxMode, \"off\">): SandboxPolicy {\n\treturn {\n\t\tmode,\n\t\tprofile: \"workspace-write\",\n\t\tfilesystem: {\n\t\t\troot,\n\t\t\treadAllow: [root],\n\t\t\treadDeny: [],\n\t\t\twriteAllow: [root],\n\t\t\tdenyWrite: [],\n\t\t\ttempWrite: [tmpdir()],\n\t\t\tfollowSymlinks: false,\n\t\t},\n\t\tnetwork: {\n\t\t\tmode: \"none\",\n\t\t\tallowedDomains: [],\n\t\t\tdeniedDomains: [],\n\t\t\tallowUnixSockets: [],\n\t\t\tallowBrowser: false,\n\t\t},\n\t\tprocess: {\n\t\t\tallowExec: true,\n\t\t\tallowShell: true,\n\t\t\tallowPrivilege: false,\n\t\t},\n\t};\n}\n"]}
@@ -32,12 +32,12 @@ export declare class SessionBashRuntime {
32
32
  get sandboxMode(): SandboxMode;
33
33
  /** Lazy receipt executor bound to the session replay ledger (default-on path). */
34
34
  verifiedEvidenceExecutor(): VerifiedEvidenceExecutor | undefined;
35
- /** Configured preflight wins; otherwise the default audit/enforce preflight. */
35
+ /** Configured preflight wins; otherwise use the default enforce preflight. */
36
36
  sandboxPreflight(override?: BashSandboxPreflight): BashSandboxPreflight | undefined;
37
37
  /**
38
- * Default-on sandbox preflight. `audit` (default) keeps the spawn unwrapped
39
- * but reports every decision to the replay ledger; `enforce` activates the
40
- * real OS backend and fails closed when none is available.
38
+ * Default-on sandbox preflight. `enforce` wraps every local spawn with the
39
+ * OS backend and fails closed when none is available. Explicit `audit` keeps
40
+ * the spawn unwrapped but still reports decisions to the replay ledger.
41
41
  */
42
42
  private getDefaultSandboxPreflight;
43
43
  /** Git-aware workspace scope for verified bash receipts (TTL-cached). */
@@ -1 +1 @@
1
- {"version":3,"file":"session-bash-runtime.d.ts","sourceRoot":"","sources":["../../src/core/session-bash-runtime.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,KAAK,EAAwB,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAI5E,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,QAAQ,CAAC,eAAe,EAAE,MAAM,MAAM,CAAC;IACvC,mFAAmF;IACnF,QAAQ,CAAC,cAAc,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClD,QAAQ,CAAC,YAAY,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,0BAA0B,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACtE,QAAQ,CAAC,iBAAiB,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CAC9E;AAED,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,gBAAgB,CAAuC;IAC/D,OAAO,CAAC,eAAe,CAAmC;IAC1D,OAAO,CAAC,uBAAuB,CAA0C;IACzE,OAAO,CAAC,mBAAmB,CAA0B;IACrD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IAEpD,YAAY,OAAO,EAAE,yBAAyB,EAE7C;IAED;;;;OAIG;IACH,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CAGlD;IAED,IAAI,WAAW,IAAI,WAAW,CAE7B;IAED,kFAAkF;IAClF,wBAAwB,IAAI,wBAAwB,GAAG,SAAS,CAc/D;IAED,gFAAgF;IAChF,gBAAgB,CAAC,QAAQ,CAAC,EAAE,oBAAoB,GAAG,oBAAoB,GAAG,SAAS,CAOlF;IAED;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;IAoClC,yEAAyE;IACzE,cAAc,kDAEb;IAED,IAAI,MAAM,IAAI,MAAM,GAAG,SAAS,CAE/B;IAED,IAAI,MAAM,IAAI,MAAM,GAAG,SAAS,CAE/B;IAED;;;;;OAKG;IACG,eAAe,CAAC,OAAO,EAAE;QAC9B,eAAe,EAAE,MAAM,CAAC;QACxB,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;QAC9B,UAAU,EAAE,cAAc,CAAC;QAC3B,MAAM,EAAE,WAAW,CAAC;QACpB,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;KACjC,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAyClC;CACD","sourcesContent":["/**\n * Session bash runtime: verified-evidence executor, default sandbox preflight,\n * and git-aware workspace scope in one cohesive, lazily-initialized unit.\n *\n * Extracted from AgentSession so the session class stays a coordinator; every\n * default-on behavior here keeps its opt-out:\n * - `OMK_VERIFIED_BASH=0` disables the receipt-bound executor.\n * - `OMK_BASH_SANDBOX=0` disables the default sandbox preflight.\n */\nimport { join } from \"node:path\";\nimport { EvidenceReceiptStore } from \"../guardrails/evidence-receipt-store.ts\";\nimport type { ReplayLedgerManager } from \"../guardrails/evidence-system.ts\";\nimport { VerifiedEvidenceExecutor } from \"../guardrails/verified-executor.ts\";\nimport type { ReplayEventType } from \"../types/evidence.ts\";\nimport { stripAnsi } from \"../utils/ansi.ts\";\nimport { getShellConfig, sanitizeBinaryOutput } from \"../utils/shell.ts\";\nimport type { BashResult } from \"./bash-executor.ts\";\nimport { detectSandboxBackend } from \"./sandbox/backend.ts\";\nimport { createWorkspaceSandboxPolicy, resolveBashSandboxMode } from \"./sandbox/default-policy.ts\";\nimport type { SandboxBackendStatus, SandboxMode } from \"./sandbox/policy.ts\";\nimport type { BashOperations, BashSandboxPreflight } from \"./tools/bash.ts\";\nimport { executeVerifiedBash } from \"./verified-bash-adapter.ts\";\nimport { isVerifiedBashEnabled, resolveSessionWorkspaceScope } from \"./verified-bash-runtime.ts\";\n\nexport interface SessionBashRuntimeOptions {\n\treadonly cwd: string;\n\t/** Actual execution cwd authority (session manager). */\n\treadonly getExecutionCwd: () => string;\n\t/** Session file anchor for the receipt store; undefined for ephemeral sessions. */\n\treadonly getSessionFile: () => string | undefined;\n\treadonly replayLedger: ReplayLedgerManager | undefined;\n\treadonly replayGoalId: string | undefined;\n\treadonly replayLaneId: string | undefined;\n\treadonly configuredSandboxPreflight: BashSandboxPreflight | undefined;\n\treadonly appendReplayEvent: (type: ReplayEventType, payload: unknown) => void;\n}\n\nexport class SessionBashRuntime {\n\tprivate verifiedExecutor: VerifiedEvidenceExecutor | undefined;\n\tprivate detectedBackend: SandboxBackendStatus | undefined;\n\tprivate defaultSandboxPreflight: BashSandboxPreflight | null | undefined;\n\tprivate sandboxModeOverride: SandboxMode | undefined;\n\tprivate readonly options: SessionBashRuntimeOptions;\n\n\tconstructor(options: SessionBashRuntimeOptions) {\n\t\tthis.options = options;\n\t}\n\n\t/**\n\t * Interactive promotion: switch the default sandbox mode at runtime\n\t * (audit ↔ enforce ↔ off) without restarting the session. `undefined`\n\t * restores env-based resolution.\n\t */\n\tsetSandboxMode(mode: SandboxMode | undefined): void {\n\t\tthis.sandboxModeOverride = mode;\n\t\tthis.defaultSandboxPreflight = undefined;\n\t}\n\n\tget sandboxMode(): SandboxMode {\n\t\treturn this.sandboxModeOverride ?? resolveBashSandboxMode();\n\t}\n\n\t/** Lazy receipt executor bound to the session replay ledger (default-on path). */\n\tverifiedEvidenceExecutor(): VerifiedEvidenceExecutor | undefined {\n\t\tconst { replayLedger, replayGoalId, getSessionFile, cwd } = this.options;\n\t\tif (!replayLedger || !replayGoalId || !isVerifiedBashEnabled()) return undefined;\n\t\tif (!this.verifiedExecutor) {\n\t\t\tconst sessionFile = getSessionFile();\n\t\t\tconst evidenceRoot = sessionFile\n\t\t\t\t? `${sessionFile}.evidence`\n\t\t\t\t: join(cwd, \".omk\", \"session-evidence\", replayGoalId);\n\t\t\tthis.verifiedExecutor = new VerifiedEvidenceExecutor({\n\t\t\t\tstore: new EvidenceReceiptStore(join(evidenceRoot, \"receipts\")),\n\t\t\t\tledger: replayLedger,\n\t\t\t});\n\t\t}\n\t\treturn this.verifiedExecutor;\n\t}\n\n\t/** Configured preflight wins; otherwise the default audit/enforce preflight. */\n\tsandboxPreflight(override?: BashSandboxPreflight): BashSandboxPreflight | undefined {\n\t\tconst preflight = override ?? this.options.configuredSandboxPreflight ?? this.getDefaultSandboxPreflight();\n\t\tif (!preflight || preflight.policy.mode === \"off\" || preflight.backend) {\n\t\t\treturn preflight;\n\t\t}\n\t\tthis.detectedBackend ??= detectSandboxBackend();\n\t\treturn { ...preflight, backend: this.detectedBackend };\n\t}\n\n\t/**\n\t * Default-on sandbox preflight. `audit` (default) keeps the spawn unwrapped\n\t * but reports every decision to the replay ledger; `enforce` activates the\n\t * real OS backend and fails closed when none is available.\n\t */\n\tprivate getDefaultSandboxPreflight(): BashSandboxPreflight | undefined {\n\t\tif (this.defaultSandboxPreflight !== undefined) {\n\t\t\treturn this.defaultSandboxPreflight ?? undefined;\n\t\t}\n\t\tconst mode = this.sandboxMode;\n\t\tif (mode === \"off\") {\n\t\t\tthis.defaultSandboxPreflight = null;\n\t\t\treturn undefined;\n\t\t}\n\t\tthis.detectedBackend ??= detectSandboxBackend();\n\t\tconst detected = this.detectedBackend;\n\t\tconst backend: SandboxBackendStatus =\n\t\t\tmode === \"enforce\"\n\t\t\t\t? detected\n\t\t\t\t: { platform: detected.platform, backendAvailable: false, domainAllowlistAvailable: false };\n\t\t// Root must cover the actual execution cwd (session manager), not just the\n\t\t// session workspace: in-memory or RPC sessions can legitimately differ.\n\t\tconst policy = createWorkspaceSandboxPolicy(this.options.getExecutionCwd() || this.options.cwd, mode);\n\t\tthis.defaultSandboxPreflight = {\n\t\t\tpolicy,\n\t\t\tbackend,\n\t\t\tonSpawnDecision: (decision) => {\n\t\t\t\tif (decision.rule === \"sandbox.off\") return;\n\t\t\t\tthis.options.appendReplayEvent(\"sandbox_audit\", {\n\t\t\t\t\tmode,\n\t\t\t\t\trule: decision.rule,\n\t\t\t\t\treason: decision.reason,\n\t\t\t\t\twrapped: \"wrapped\" in decision ? decision.wrapped : false,\n\t\t\t\t\tbackendAvailable: backend.backendAvailable,\n\t\t\t\t\tplatform: backend.platform,\n\t\t\t\t});\n\t\t\t},\n\t\t};\n\t\treturn this.defaultSandboxPreflight;\n\t}\n\n\t/** Git-aware workspace scope for verified bash receipts (TTL-cached). */\n\tworkspaceScope() {\n\t\treturn resolveSessionWorkspaceScope(this.options.cwd);\n\t}\n\n\tget goalId(): string | undefined {\n\t\treturn this.options.replayGoalId;\n\t}\n\n\tget laneId(): string | undefined {\n\t\treturn this.options.replayLaneId;\n\t}\n\n\t/**\n\t * Receipt-bound bash execution. Returns `undefined` when the verified path\n\t * is unavailable (no ledger / opted out) so the caller falls back to the\n\t * legacy executor. Streams sanitized text through `onChunk` while the\n\t * adapter owns the raw receipt bytes.\n\t */\n\tasync executeVerified(request: {\n\t\tresolvedCommand: string;\n\t\tcwd: string;\n\t\tshellPath: string | undefined;\n\t\toperations: BashOperations;\n\t\tsignal: AbortSignal;\n\t\tonChunk: (delta: string) => void;\n\t}): Promise<BashResult | undefined> {\n\t\tconst evidenceExecutor = this.verifiedEvidenceExecutor();\n\t\tconst goalId = this.options.replayGoalId;\n\t\tif (!evidenceExecutor || !goalId) return undefined;\n\t\tconst { shell } = getShellConfig(request.shellPath);\n\t\tconst command = request.resolvedCommand;\n\t\tconst claim = command.length > 200 ? `bash: ${command.slice(0, 197)}...` : `bash: ${command}`;\n\t\tconst outputChunks: string[] = [];\n\t\tconst decoder = new TextDecoder();\n\t\tconst execution = await executeVerifiedBash({\n\t\t\tevidenceExecutor,\n\t\t\toperations: request.operations,\n\t\t\tgoalId,\n\t\t\t...(this.options.replayLaneId !== undefined ? { laneId: this.options.replayLaneId } : {}),\n\t\t\tclaim,\n\t\t\tshell,\n\t\t\tscript: command,\n\t\t\tcwd: request.cwd,\n\t\t\ttimeoutMs: null,\n\t\t\tworkspaceScope: this.workspaceScope(),\n\t\t\texecutor: \"bash-tool\",\n\t\t\tsignal: request.signal,\n\t\t\tonData: (data) => {\n\t\t\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\t\t\t\tif (!text) return;\n\t\t\t\toutputChunks.push(text);\n\t\t\t\trequest.onChunk(text);\n\t\t\t},\n\t\t});\n\t\tconst status = execution.receipt.core.status;\n\t\t// Preserve pre-adapter timeout throw semantics after the receipt is bound.\n\t\tif (status === \"timeout\") {\n\t\t\tthrow new Error(\"timeout:0\");\n\t\t}\n\t\tconst cancelled = status === \"aborted\" || request.signal.aborted;\n\t\treturn {\n\t\t\toutput: outputChunks.join(\"\"),\n\t\t\texitCode: cancelled ? undefined : (execution.receipt.core.exitCode ?? undefined),\n\t\t\tcancelled,\n\t\t\ttruncated: false,\n\t\t};\n\t}\n}\n"]}
1
+ {"version":3,"file":"session-bash-runtime.d.ts","sourceRoot":"","sources":["../../src/core/session-bash-runtime.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,wBAAwB,EAAE,MAAM,oCAAoC,CAAC;AAC9E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG5D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,KAAK,EAAwB,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAI5E,MAAM,WAAW,yBAAyB;IACzC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,QAAQ,CAAC,eAAe,EAAE,MAAM,MAAM,CAAC;IACvC,mFAAmF;IACnF,QAAQ,CAAC,cAAc,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;IAClD,QAAQ,CAAC,YAAY,EAAE,mBAAmB,GAAG,SAAS,CAAC;IACvD,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,QAAQ,CAAC,0BAA0B,EAAE,oBAAoB,GAAG,SAAS,CAAC;IACtE,QAAQ,CAAC,iBAAiB,EAAE,CAAC,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CAC9E;AAED,qBAAa,kBAAkB;IAC9B,OAAO,CAAC,gBAAgB,CAAuC;IAC/D,OAAO,CAAC,eAAe,CAAmC;IAC1D,OAAO,CAAC,uBAAuB,CAA0C;IACzE,OAAO,CAAC,mBAAmB,CAA0B;IACrD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4B;IAEpD,YAAY,OAAO,EAAE,yBAAyB,EAE7C;IAED;;;;OAIG;IACH,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CAGlD;IAED,IAAI,WAAW,IAAI,WAAW,CAE7B;IAED,kFAAkF;IAClF,wBAAwB,IAAI,wBAAwB,GAAG,SAAS,CAc/D;IAED,8EAA8E;IAC9E,gBAAgB,CAAC,QAAQ,CAAC,EAAE,oBAAoB,GAAG,oBAAoB,GAAG,SAAS,CAOlF;IAED;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;IAoClC,yEAAyE;IACzE,cAAc,kDAEb;IAED,IAAI,MAAM,IAAI,MAAM,GAAG,SAAS,CAE/B;IAED,IAAI,MAAM,IAAI,MAAM,GAAG,SAAS,CAE/B;IAED;;;;;OAKG;IACG,eAAe,CAAC,OAAO,EAAE;QAC9B,eAAe,EAAE,MAAM,CAAC;QACxB,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;QAC9B,UAAU,EAAE,cAAc,CAAC;QAC3B,MAAM,EAAE,WAAW,CAAC;QACpB,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;KACjC,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CAyClC;CACD","sourcesContent":["/**\n * Session bash runtime: verified-evidence executor, default sandbox preflight,\n * and git-aware workspace scope in one cohesive, lazily-initialized unit.\n *\n * Extracted from AgentSession so the session class stays a coordinator; every\n * default-on behavior here keeps its opt-out:\n * - `OMK_VERIFIED_BASH=0` disables the receipt-bound executor.\n * - OS sandbox enforcement and network isolation are enabled by default.\n * - `OMK_BASH_SANDBOX=audit` keeps only the ledger preflight; `=0` disables it.\n */\nimport { join } from \"node:path\";\nimport { EvidenceReceiptStore } from \"../guardrails/evidence-receipt-store.ts\";\nimport type { ReplayLedgerManager } from \"../guardrails/evidence-system.ts\";\nimport { VerifiedEvidenceExecutor } from \"../guardrails/verified-executor.ts\";\nimport type { ReplayEventType } from \"../types/evidence.ts\";\nimport { stripAnsi } from \"../utils/ansi.ts\";\nimport { getShellConfig, sanitizeBinaryOutput } from \"../utils/shell.ts\";\nimport type { BashResult } from \"./bash-executor.ts\";\nimport { detectSandboxBackend } from \"./sandbox/backend.ts\";\nimport { createWorkspaceSandboxPolicy, resolveBashSandboxMode } from \"./sandbox/default-policy.ts\";\nimport type { SandboxBackendStatus, SandboxMode } from \"./sandbox/policy.ts\";\nimport type { BashOperations, BashSandboxPreflight } from \"./tools/bash.ts\";\nimport { executeVerifiedBash } from \"./verified-bash-adapter.ts\";\nimport { isVerifiedBashEnabled, resolveSessionWorkspaceScope } from \"./verified-bash-runtime.ts\";\n\nexport interface SessionBashRuntimeOptions {\n\treadonly cwd: string;\n\t/** Actual execution cwd authority (session manager). */\n\treadonly getExecutionCwd: () => string;\n\t/** Session file anchor for the receipt store; undefined for ephemeral sessions. */\n\treadonly getSessionFile: () => string | undefined;\n\treadonly replayLedger: ReplayLedgerManager | undefined;\n\treadonly replayGoalId: string | undefined;\n\treadonly replayLaneId: string | undefined;\n\treadonly configuredSandboxPreflight: BashSandboxPreflight | undefined;\n\treadonly appendReplayEvent: (type: ReplayEventType, payload: unknown) => void;\n}\n\nexport class SessionBashRuntime {\n\tprivate verifiedExecutor: VerifiedEvidenceExecutor | undefined;\n\tprivate detectedBackend: SandboxBackendStatus | undefined;\n\tprivate defaultSandboxPreflight: BashSandboxPreflight | null | undefined;\n\tprivate sandboxModeOverride: SandboxMode | undefined;\n\tprivate readonly options: SessionBashRuntimeOptions;\n\n\tconstructor(options: SessionBashRuntimeOptions) {\n\t\tthis.options = options;\n\t}\n\n\t/**\n\t * Interactive promotion: switch the default sandbox mode at runtime\n\t * (audit ↔ enforce ↔ off) without restarting the session. `undefined`\n\t * restores env-based resolution.\n\t */\n\tsetSandboxMode(mode: SandboxMode | undefined): void {\n\t\tthis.sandboxModeOverride = mode;\n\t\tthis.defaultSandboxPreflight = undefined;\n\t}\n\n\tget sandboxMode(): SandboxMode {\n\t\treturn this.sandboxModeOverride ?? resolveBashSandboxMode();\n\t}\n\n\t/** Lazy receipt executor bound to the session replay ledger (default-on path). */\n\tverifiedEvidenceExecutor(): VerifiedEvidenceExecutor | undefined {\n\t\tconst { replayLedger, replayGoalId, getSessionFile, cwd } = this.options;\n\t\tif (!replayLedger || !replayGoalId || !isVerifiedBashEnabled()) return undefined;\n\t\tif (!this.verifiedExecutor) {\n\t\t\tconst sessionFile = getSessionFile();\n\t\t\tconst evidenceRoot = sessionFile\n\t\t\t\t? `${sessionFile}.evidence`\n\t\t\t\t: join(cwd, \".omk\", \"session-evidence\", replayGoalId);\n\t\t\tthis.verifiedExecutor = new VerifiedEvidenceExecutor({\n\t\t\t\tstore: new EvidenceReceiptStore(join(evidenceRoot, \"receipts\")),\n\t\t\t\tledger: replayLedger,\n\t\t\t});\n\t\t}\n\t\treturn this.verifiedExecutor;\n\t}\n\n\t/** Configured preflight wins; otherwise use the default enforce preflight. */\n\tsandboxPreflight(override?: BashSandboxPreflight): BashSandboxPreflight | undefined {\n\t\tconst preflight = override ?? this.options.configuredSandboxPreflight ?? this.getDefaultSandboxPreflight();\n\t\tif (!preflight || preflight.policy.mode === \"off\" || preflight.backend) {\n\t\t\treturn preflight;\n\t\t}\n\t\tthis.detectedBackend ??= detectSandboxBackend();\n\t\treturn { ...preflight, backend: this.detectedBackend };\n\t}\n\n\t/**\n\t * Default-on sandbox preflight. `enforce` wraps every local spawn with the\n\t * OS backend and fails closed when none is available. Explicit `audit` keeps\n\t * the spawn unwrapped but still reports decisions to the replay ledger.\n\t */\n\tprivate getDefaultSandboxPreflight(): BashSandboxPreflight | undefined {\n\t\tif (this.defaultSandboxPreflight !== undefined) {\n\t\t\treturn this.defaultSandboxPreflight ?? undefined;\n\t\t}\n\t\tconst mode = this.sandboxMode;\n\t\tif (mode === \"off\") {\n\t\t\tthis.defaultSandboxPreflight = null;\n\t\t\treturn undefined;\n\t\t}\n\t\tthis.detectedBackend ??= detectSandboxBackend();\n\t\tconst detected = this.detectedBackend;\n\t\tconst backend: SandboxBackendStatus =\n\t\t\tmode === \"enforce\"\n\t\t\t\t? detected\n\t\t\t\t: { platform: detected.platform, backendAvailable: false, domainAllowlistAvailable: false };\n\t\t// Root must cover the actual execution cwd (session manager), not just the\n\t\t// session workspace: in-memory or RPC sessions can legitimately differ.\n\t\tconst policy = createWorkspaceSandboxPolicy(this.options.getExecutionCwd() || this.options.cwd, mode);\n\t\tthis.defaultSandboxPreflight = {\n\t\t\tpolicy,\n\t\t\tbackend,\n\t\t\tonSpawnDecision: (decision) => {\n\t\t\t\tif (decision.rule === \"sandbox.off\") return;\n\t\t\t\tthis.options.appendReplayEvent(\"sandbox_audit\", {\n\t\t\t\t\tmode,\n\t\t\t\t\trule: decision.rule,\n\t\t\t\t\treason: decision.reason,\n\t\t\t\t\twrapped: \"wrapped\" in decision ? decision.wrapped : false,\n\t\t\t\t\tbackendAvailable: backend.backendAvailable,\n\t\t\t\t\tplatform: backend.platform,\n\t\t\t\t});\n\t\t\t},\n\t\t};\n\t\treturn this.defaultSandboxPreflight;\n\t}\n\n\t/** Git-aware workspace scope for verified bash receipts (TTL-cached). */\n\tworkspaceScope() {\n\t\treturn resolveSessionWorkspaceScope(this.options.cwd);\n\t}\n\n\tget goalId(): string | undefined {\n\t\treturn this.options.replayGoalId;\n\t}\n\n\tget laneId(): string | undefined {\n\t\treturn this.options.replayLaneId;\n\t}\n\n\t/**\n\t * Receipt-bound bash execution. Returns `undefined` when the verified path\n\t * is unavailable (no ledger / opted out) so the caller falls back to the\n\t * legacy executor. Streams sanitized text through `onChunk` while the\n\t * adapter owns the raw receipt bytes.\n\t */\n\tasync executeVerified(request: {\n\t\tresolvedCommand: string;\n\t\tcwd: string;\n\t\tshellPath: string | undefined;\n\t\toperations: BashOperations;\n\t\tsignal: AbortSignal;\n\t\tonChunk: (delta: string) => void;\n\t}): Promise<BashResult | undefined> {\n\t\tconst evidenceExecutor = this.verifiedEvidenceExecutor();\n\t\tconst goalId = this.options.replayGoalId;\n\t\tif (!evidenceExecutor || !goalId) return undefined;\n\t\tconst { shell } = getShellConfig(request.shellPath);\n\t\tconst command = request.resolvedCommand;\n\t\tconst claim = command.length > 200 ? `bash: ${command.slice(0, 197)}...` : `bash: ${command}`;\n\t\tconst outputChunks: string[] = [];\n\t\tconst decoder = new TextDecoder();\n\t\tconst execution = await executeVerifiedBash({\n\t\t\tevidenceExecutor,\n\t\t\toperations: request.operations,\n\t\t\tgoalId,\n\t\t\t...(this.options.replayLaneId !== undefined ? { laneId: this.options.replayLaneId } : {}),\n\t\t\tclaim,\n\t\t\tshell,\n\t\t\tscript: command,\n\t\t\tcwd: request.cwd,\n\t\t\ttimeoutMs: null,\n\t\t\tworkspaceScope: this.workspaceScope(),\n\t\t\texecutor: \"bash-tool\",\n\t\t\tsignal: request.signal,\n\t\t\tonData: (data) => {\n\t\t\t\tconst text = sanitizeBinaryOutput(stripAnsi(decoder.decode(data, { stream: true }))).replace(/\\r/g, \"\");\n\t\t\t\tif (!text) return;\n\t\t\t\toutputChunks.push(text);\n\t\t\t\trequest.onChunk(text);\n\t\t\t},\n\t\t});\n\t\tconst status = execution.receipt.core.status;\n\t\t// Preserve pre-adapter timeout throw semantics after the receipt is bound.\n\t\tif (status === \"timeout\") {\n\t\t\tthrow new Error(\"timeout:0\");\n\t\t}\n\t\tconst cancelled = status === \"aborted\" || request.signal.aborted;\n\t\treturn {\n\t\t\toutput: outputChunks.join(\"\"),\n\t\t\texitCode: cancelled ? undefined : (execution.receipt.core.exitCode ?? undefined),\n\t\t\tcancelled,\n\t\t\ttruncated: false,\n\t\t};\n\t}\n}\n"]}