@intutic/gate 1.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +261 -0
  3. package/dist/actions.d.ts +86 -0
  4. package/dist/actions.d.ts.map +1 -0
  5. package/dist/actions.js +239 -0
  6. package/dist/actions.js.map +1 -0
  7. package/dist/client.d.ts +94 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +207 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/dsh.d.ts +162 -0
  12. package/dist/dsh.d.ts.map +1 -0
  13. package/dist/dsh.js +189 -0
  14. package/dist/dsh.js.map +1 -0
  15. package/dist/errors.d.ts +41 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +53 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/eve.d.ts +425 -0
  20. package/dist/eve.d.ts.map +1 -0
  21. package/dist/eve.js +378 -0
  22. package/dist/eve.js.map +1 -0
  23. package/dist/gate.d.ts +113 -0
  24. package/dist/gate.d.ts.map +1 -0
  25. package/dist/gate.js +274 -0
  26. package/dist/gate.js.map +1 -0
  27. package/dist/harness.d.ts +482 -0
  28. package/dist/harness.d.ts.map +1 -0
  29. package/dist/harness.js +504 -0
  30. package/dist/harness.js.map +1 -0
  31. package/dist/headers.d.ts +26 -0
  32. package/dist/headers.d.ts.map +1 -0
  33. package/dist/headers.js +30 -0
  34. package/dist/headers.js.map +1 -0
  35. package/dist/imagecheck.d.ts +93 -0
  36. package/dist/imagecheck.d.ts.map +1 -0
  37. package/dist/imagecheck.js +286 -0
  38. package/dist/imagecheck.js.map +1 -0
  39. package/dist/index.d.ts +26 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +19 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/managedAgents.d.ts +375 -0
  44. package/dist/managedAgents.d.ts.map +1 -0
  45. package/dist/managedAgents.js +446 -0
  46. package/dist/managedAgents.js.map +1 -0
  47. package/dist/mastra.d.ts +175 -0
  48. package/dist/mastra.d.ts.map +1 -0
  49. package/dist/mastra.js +161 -0
  50. package/dist/mastra.js.map +1 -0
  51. package/dist/openai.d.ts +392 -0
  52. package/dist/openai.d.ts.map +1 -0
  53. package/dist/openai.js +658 -0
  54. package/dist/openai.js.map +1 -0
  55. package/dist/shlex.d.ts +16 -0
  56. package/dist/shlex.d.ts.map +1 -0
  57. package/dist/shlex.js +78 -0
  58. package/dist/shlex.js.map +1 -0
  59. package/dist/snapshot.d.ts +111 -0
  60. package/dist/snapshot.d.ts.map +1 -0
  61. package/dist/snapshot.js +243 -0
  62. package/dist/snapshot.js.map +1 -0
  63. package/dist/soprules.d.ts +88 -0
  64. package/dist/soprules.d.ts.map +1 -0
  65. package/dist/soprules.js +176 -0
  66. package/dist/soprules.js.map +1 -0
  67. package/dist/trueforge.d.ts +191 -0
  68. package/dist/trueforge.d.ts.map +1 -0
  69. package/dist/trueforge.js +184 -0
  70. package/dist/trueforge.js.map +1 -0
  71. package/dist/vercel.d.ts +174 -0
  72. package/dist/vercel.d.ts.map +1 -0
  73. package/dist/vercel.js +157 -0
  74. package/dist/vercel.js.map +1 -0
  75. package/dist/workflow.d.ts +209 -0
  76. package/dist/workflow.d.ts.map +1 -0
  77. package/dist/workflow.js +254 -0
  78. package/dist/workflow.js.map +1 -0
  79. package/dist/wrapTools.d.ts +68 -0
  80. package/dist/wrapTools.d.ts.map +1 -0
  81. package/dist/wrapTools.js +95 -0
  82. package/dist/wrapTools.js.map +1 -0
  83. package/package.json +123 -0
@@ -0,0 +1,504 @@
1
+ /**
2
+ * `@ai-sdk/harness` adapter: `intuticApprovalResponder()`, `intuticSubmitApprovals()`,
3
+ * `intuticStaticApprovals()`, `recommendedHarnessSettings()`, and
4
+ * `intuticSandboxBootstrap()`.
5
+ *
6
+ * `@intutic/gate/harness` — the same idea `vercel.ts` implements for the base
7
+ * `ai` package (wrap the framework's own veto point around this package's
8
+ * `Gate.guard()`), reshaped for Vercel's sandboxed coding-agent runtime,
9
+ * whose veto surface is materially different from `generateText`'s.
10
+ *
11
+ * ## Confirmed against a real install
12
+ *
13
+ * `@ai-sdk/harness@1.0.75` (plus `@ai-sdk/harness-claude-code@1.0.78` and
14
+ * `@ai-sdk/harness-grok-build@1.0.12`) were installed and their shipped
15
+ * `.d.ts`/compiled source read directly (not inferred from docs alone):
16
+ *
17
+ * 1. **The `toolApproval` setting is STATIC.** `HarnessAgentSettings.toolApproval`
18
+ * is `HarnessAgentToolApprovalConfiguration = Readonly<Record<string,
19
+ * ToolApprovalStatus>>` (`dist/agent/index.d.ts`), whose own doc comment says
20
+ * it mirrors "AI SDK `toolApproval` object configuration for host-executed
21
+ * tools, **without callback support**". `vercel.ts`'s callback-shaped
22
+ * `intuticToolApproval()` therefore does NOT apply here — there is no place
23
+ * to hang a function. Per-call gating instead goes through the approval
24
+ * FLOW: mark tools `'user-approval'` (see {@link intuticStaticApprovals}),
25
+ * let the turn pause, and answer each pending approval with the gate
26
+ * (see {@link intuticApprovalResponder}).
27
+ * 2. **The portable answer path is continuation-based.**
28
+ * `HarnessAgentToolApprovalContinuation` carries `{ approvalResponse,
29
+ * toolCall }`; `continueTurn`/`continueGenerate`/`continueStream` accept
30
+ * `toolApprovalContinuations` (dist/agent/index.d.ts). The adapter-session
31
+ * method `session.submitToolApproval?({approvalId, approved, reason})` is
32
+ * OPTIONAL (`dist/index.d.ts` — `HarnessV1PromptControl`), and support
33
+ * genuinely varies per adapter: `@ai-sdk/harness-claude-code@1.0.78`
34
+ * implements it; `@ai-sdk/harness-grok-build@1.0.12` does not (zero
35
+ * occurrences in its shipped dist). This module therefore builds the
36
+ * responder on the continuation path and treats `submitToolApproval` as an
37
+ * optimization when present ({@link intuticSubmitApprovals}).
38
+ * 3. **Built-in sandbox tools are NOT governed by `toolApproval` at all.**
39
+ * They are governed only by `permissionMode: 'allow-reads' | 'allow-edits'
40
+ * | 'allow-all'`, which **defaults to `'allow-all'`** — the shipped doc
41
+ * comment says so verbatim: "Defaults to `'allow-all'`, preserving the
42
+ * existing bypass-permissions behavior unless users opt in." A HarnessAgent
43
+ * with default settings runs read/write/edit/bash/glob/grep in the sandbox
44
+ * with no approval step this gate could hook.
45
+ * {@link recommendedHarnessSettings} exists to steer callers off that
46
+ * default.
47
+ * 4. **Execution is server-side, in a sandbox.** The agent's tools run inside
48
+ * a microVM (`@ai-sdk/sandbox-vercel` and friends), NOT on the machine
49
+ * where the Intutic proxy runs — the laptop proxy cannot see sandbox
50
+ * egress. The only egress control available at this layer is the sandbox's
51
+ * own `HarnessV1NetworkPolicy` (`{mode:'allow-all'|'deny-all'} | {mode:
52
+ * 'custom', allowedHosts/allowedCIDRs/deniedCIDRs}`), applied via the
53
+ * sandbox session's optional `setNetworkPolicy?.()`. That is coarse
54
+ * host-level filtering, not Intutic DLP — stated plainly in
55
+ * `apps/docs/integrations/ai-sdk-harness.md` rather than oversold.
56
+ *
57
+ * ## What this module does NOT import
58
+ *
59
+ * Deliberately no runtime dependency on `@ai-sdk/harness` — the structural
60
+ * types below capture only the shapes this adapter touches, matching the
61
+ * convention `vercel.ts`/`mastra.ts`/`dsh.ts` established. The real package
62
+ * is a devDependency used by `__tests__/harness.test.ts` to (a) structurally
63
+ * check these types against the shipped ones and (b) run this responder's
64
+ * output through the REAL `collectHarnessAgentToolApprovalContinuations`
65
+ * machinery.
66
+ *
67
+ * @module
68
+ */
69
+ import { createHash } from 'node:crypto';
70
+ import { active as activeGate } from './gate.js';
71
+ import { IntuticGateRefusal } from './errors.js';
72
+ /**
73
+ * Render a pending approval's `input` as the `tool_input` object
74
+ * {@link Gate.guard} evaluates.
75
+ *
76
+ * Mirrors `dsh.ts`'s `renderDshToolInput`, plus one harness-specific case: a
77
+ * `HarnessAgentPendingToolApproval` carries its input as a JSON **string**,
78
+ * so strings are parsed first. A string that does not parse (or parses to a
79
+ * non-object) falls back to `{ args: [...] }` so the gate still has
80
+ * something to evaluate rather than throwing on a shape it does not
81
+ * recognise.
82
+ */
83
+ export function renderHarnessToolInput(input) {
84
+ let value = input;
85
+ if (typeof value === 'string') {
86
+ try {
87
+ value = JSON.parse(value);
88
+ }
89
+ catch {
90
+ return { args: [input] };
91
+ }
92
+ }
93
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
94
+ return value;
95
+ }
96
+ return { args: [value] };
97
+ }
98
+ /** Parsed-if-possible view of a request's input, used on the continuation's
99
+ * `toolCall.input` so the shape matches what the tool call actually carried
100
+ * (a pending-approval row serializes it to a JSON string in transit). */
101
+ function parsedInput(input) {
102
+ if (typeof input !== 'string')
103
+ return input;
104
+ try {
105
+ return JSON.parse(input);
106
+ }
107
+ catch {
108
+ return input;
109
+ }
110
+ }
111
+ /**
112
+ * Build an approval responder: a function that evaluates each pending
113
+ * tool-approval request via `Gate.guard()` and produces the
114
+ * `toolApprovalContinuations` array for the continue-turn call —
115
+ * `agent.continueGenerate({ session, toolApprovalContinuations })` /
116
+ * `continueStream(...)`:
117
+ *
118
+ * ```ts
119
+ * import { HarnessAgent } from '@ai-sdk/harness/agent'
120
+ * import { Gate, install } from '@intutic/gate'
121
+ * import {
122
+ * intuticApprovalResponder,
123
+ * intuticStaticApprovals,
124
+ * recommendedHarnessSettings,
125
+ * } from '@intutic/gate/harness'
126
+ *
127
+ * install(new Gate({ workspaceId: process.env.INTUTIC_WORKSPACE_ID }))
128
+ *
129
+ * const agent = new HarnessAgent({
130
+ * harness,
131
+ * sandbox,
132
+ * tools,
133
+ * ...recommendedHarnessSettings(), // permissionMode: 'allow-edits'
134
+ * toolApproval: intuticStaticApprovals(tools), // every custom tool -> 'user-approval'
135
+ * })
136
+ *
137
+ * const respond = intuticApprovalResponder()
138
+ * // ...run a turn; when it pauses awaiting approval, collect the pending
139
+ * // tool-approval requests (stream parts or suspended-session rows), then:
140
+ * const toolApprovalContinuations = await respond(pendingApprovals)
141
+ * await agent.continueGenerate({ session, toolApprovalContinuations })
142
+ * ```
143
+ *
144
+ * Decision mapping, per request:
145
+ *
146
+ * - gate allows -> `approved: true`
147
+ * - `IntuticGateRefusal` -> `approved: false`, `reason` = the
148
+ * `[Intutic Governance] BLOCKED: ...` message (the runtime submits an
149
+ * `execution-denied` result to the model in place of a tool result)
150
+ * - any OTHER throw -> `approved: false` with a "gate crashed"
151
+ * reason — **fail closed**, same posture as `dsh.ts`'s listener: this
152
+ * responder's output is a decision list consumed by a paused turn, and a
153
+ * deny is deliverable and auditable where an escaped throw could leave
154
+ * the turn suspended with no answer at all.
155
+ *
156
+ * Requires a `Gate` — either passed via `{ gate }`, or installed process-wide
157
+ * with `install(new Gate(...))`. A call with neither rejects before
158
+ * evaluating anything, refusing to answer approvals unguarded.
159
+ */
160
+ export function intuticApprovalResponder(opts = {}) {
161
+ return async (requests) => {
162
+ const g = opts.gate ?? activeGate();
163
+ if (g === null) {
164
+ throw new Error('No gate configured: call install(new Gate(...)) from @intutic/gate, or pass ' +
165
+ '{ gate } to intuticApprovalResponder(). Refusing to answer tool approvals unguarded.');
166
+ }
167
+ const continuations = [];
168
+ for (const req of requests) {
169
+ let approved = true;
170
+ let reason;
171
+ try {
172
+ await g.guard(req.toolName, renderHarnessToolInput(req.input));
173
+ }
174
+ catch (exc) {
175
+ approved = false;
176
+ if (exc instanceof IntuticGateRefusal) {
177
+ reason = exc.message;
178
+ }
179
+ else {
180
+ const detail = exc instanceof Error ? exc.message : String(exc);
181
+ reason =
182
+ `[Intutic Governance] BLOCKED: gate crashed (${detail}) — failing closed rather ` +
183
+ 'than approving an unevaluated call.';
184
+ }
185
+ }
186
+ continuations.push({
187
+ approvalResponse: {
188
+ type: 'tool-approval-response',
189
+ approvalId: req.approvalId,
190
+ approved,
191
+ ...(reason !== undefined ? { reason } : {}),
192
+ ...(req.providerExecuted !== undefined ? { providerExecuted: req.providerExecuted } : {}),
193
+ },
194
+ toolCall: {
195
+ type: 'tool-call',
196
+ toolCallId: req.toolCallId,
197
+ toolName: req.toolName,
198
+ input: parsedInput(req.input),
199
+ ...(req.providerExecuted !== undefined ? { providerExecuted: req.providerExecuted } : {}),
200
+ },
201
+ });
202
+ }
203
+ return continuations;
204
+ };
205
+ }
206
+ /**
207
+ * Evaluate pending approvals and deliver the decisions through
208
+ * `session.submitToolApproval` when the adapter supports it.
209
+ *
210
+ * `submitToolApproval` is an OPTIONAL adapter-session method and per-adapter
211
+ * support varies (`harness-claude-code` implements it, `harness-grok-build`
212
+ * does not — see module doc point 2). This helper treats it as an
213
+ * optimization: when present, every decision is submitted directly (the turn
214
+ * resumes in place); when absent, nothing is submitted and the caller passes
215
+ * the returned `continuations` to `continueGenerate`/`continueStream` — the
216
+ * portable path — instead. Either way the SAME evaluated decisions are
217
+ * returned, so the caller never re-runs the gate for one delivery mechanism
218
+ * vs. the other.
219
+ */
220
+ export async function intuticSubmitApprovals(session, requests, opts = {}) {
221
+ const continuations = await intuticApprovalResponder(opts)(requests);
222
+ const submit = session.submitToolApproval?.bind(session);
223
+ if (submit === undefined) {
224
+ return { continuations, submitted: false };
225
+ }
226
+ for (const c of continuations) {
227
+ await submit({
228
+ approvalId: c.approvalResponse.approvalId,
229
+ approved: c.approvalResponse.approved,
230
+ ...(c.approvalResponse.reason !== undefined ? { reason: c.approvalResponse.reason } : {}),
231
+ });
232
+ }
233
+ return { continuations, submitted: true };
234
+ }
235
+ /**
236
+ * Build the static `toolApproval` record for `HarnessAgentSettings`.
237
+ *
238
+ * The record shape is `Readonly<Record<string, ToolApprovalStatus>>` — STATIC
239
+ * per-tool statuses "without callback support" (the shipped doc comment's own
240
+ * words), so a per-call gate function cannot be expressed here at all. What a
241
+ * static record CAN express is deny-by-default **routing**: every listed tool
242
+ * is marked `'user-approval'`, which pauses the turn on every call to it, and
243
+ * {@link intuticApprovalResponder} then answers each pause with a real
244
+ * per-call `Gate.guard()` verdict. `'denied'` is deliberately NOT the default
245
+ * here — it would refuse every call to the tool unconditionally, without the
246
+ * gate ever seeing the arguments; use `{ deny: [...] }` for tools where that
247
+ * is actually what you want.
248
+ *
249
+ * Two scope limits, stated plainly:
250
+ *
251
+ * - This covers CUSTOM host-executed tools only. Built-in sandbox tools
252
+ * (read/write/edit/bash/...) never consult `toolApproval` — they are
253
+ * governed solely by `permissionMode`, which defaults to `'allow-all'`.
254
+ * See {@link recommendedHarnessSettings}.
255
+ * - A tool absent from this record has status `undefined` =
256
+ * "not-applicable" and runs WITHOUT any approval pause. Pass the same
257
+ * `tools` record you give the agent (or every tool name), not a subset.
258
+ */
259
+ export function intuticStaticApprovals(tools, opts = {}) {
260
+ const names = Array.isArray(tools) ? tools : Object.keys(tools);
261
+ const denied = new Set(opts.deny ?? []);
262
+ const record = {};
263
+ for (const name of names) {
264
+ record[name] = denied.has(name) ? 'denied' : 'user-approval';
265
+ }
266
+ return record;
267
+ }
268
+ /**
269
+ * The settings posture this integration recommends, as a value callers can
270
+ * spread/apply rather than prose they have to transcribe:
271
+ *
272
+ * - `permissionMode: 'allow-edits'` (or `'allow-reads'`) — because the
273
+ * framework's own default is `'allow-all'`, under which built-in sandbox
274
+ * tools (including `bash`) run with no approval surface this gate can
275
+ * reach. There is no per-call gate for built-ins at ANY permissionMode;
276
+ * the mode itself is the entire control, which is why steering it off
277
+ * `'allow-all'` matters.
278
+ * - a `networkPolicy` for the sandbox — `{ mode: 'deny-all' }` by default,
279
+ * or a `custom` allow-list (with the cloud-metadata CIDR denied) when
280
+ * `allowedHosts` is given. This is the honest egress-governance story for
281
+ * server-side sandbox execution: the Intutic proxy on the developer's
282
+ * machine never sees sandbox traffic, and host-level filtering is what
283
+ * the sandbox layer actually offers. It is coarse — no DLP, no
284
+ * per-request policy — and the integration docs say so.
285
+ */
286
+ export function recommendedHarnessSettings(opts = {}) {
287
+ const networkPolicy = opts.allowedHosts !== undefined && opts.allowedHosts.length > 0
288
+ ? {
289
+ mode: 'custom',
290
+ allowedHosts: opts.allowedHosts,
291
+ // Cloud metadata endpoint — deny it even when the caller's allow
292
+ // list is broad; deniedCIDRs wins over allows in the real policy.
293
+ deniedCIDRs: ['169.254.169.254/32'],
294
+ }
295
+ : { mode: 'deny-all' };
296
+ return {
297
+ permissionMode: opts.permissionMode ?? 'allow-edits',
298
+ networkPolicy,
299
+ ...(opts.filterBash === false ? {} : { inactiveTools: ['bash'] }),
300
+ };
301
+ }
302
+ function sandboxJoin(workDir, relative) {
303
+ const base = workDir.endsWith('/') ? workDir.slice(0, -1) : workDir;
304
+ return `${base}/${relative}`;
305
+ }
306
+ /**
307
+ * Self-contained (zero-`require`) hand-port of `snapshot.ts`'s
308
+ * `normalise`/`evaluate` — Tier A1 only. See the module-level comment above
309
+ * for what this deliberately does and does not cover, and
310
+ * `__tests__/harness.test.ts` for the behavioural-parity check against the
311
+ * real `snapshot.evaluate()` this port is pinned to.
312
+ *
313
+ * Contract matches the laptop's `claude-code-check.js`
314
+ * (`services/sync-daemon/src/harness/claudeCodeHooks.ts`) deliberately:
315
+ * Claude Code's PreToolUse hook stdin carries `{tool_name, tool_input,
316
+ * session_id}` (this reads both the snake_case and camelCase field names,
317
+ * same as the laptop script); exit `2` blocks the call, exit `0` allows it;
318
+ * any parse/read error fails CLOSED (exit `2`), the same posture
319
+ * `emitJsFailClosedPrelude` documents for the laptop's version.
320
+ */
321
+ function renderSandboxGateScript(rulesFileName) {
322
+ return `#!/usr/bin/env node
323
+ 'use strict';
324
+ /**
325
+ * Intutic sandbox PreToolUse gate (Tier A1 only — policy-snapshot rules).
326
+ * Auto-generated by @intutic/gate's intuticSandboxBootstrap(). DO NOT EDIT.
327
+ *
328
+ * Unlike the laptop's claude-code-check.js, this script has no live
329
+ * control-plane connection at call time (the recommended sandbox network
330
+ * policy is deny-all) and therefore implements NEITHER the SOP tier (A3)
331
+ * NOR review-hold parking NOR governance-event draining. See TD-417.
332
+ */
333
+ const fs = require('fs');
334
+ const path = require('path');
335
+
336
+ const RULES_PATH = path.join(__dirname, ${JSON.stringify(rulesFileName)});
337
+
338
+ function normalise(v) {
339
+ const s = v === null || v === undefined ? '' : String(v);
340
+ return ' ' + s.replace(/\\s+/g, ' ').trim() + ' ';
341
+ }
342
+
343
+ function loadRules() {
344
+ let text;
345
+ try {
346
+ text = fs.readFileSync(RULES_PATH, 'utf-8');
347
+ } catch (e) {
348
+ return [];
349
+ }
350
+ const rules = [];
351
+ for (const line of text.split('\\n')) {
352
+ if (!line || line.charAt(0) === '#') continue;
353
+ const f = line.split('\\t');
354
+ if (f.length < 6 || !f[5]) continue;
355
+ try {
356
+ rules.push({
357
+ id: f[0],
358
+ severity: f[1],
359
+ subject: f[3] || 'any',
360
+ reason: f[4],
361
+ pattern: new RegExp(f[5], f[2] === 'i' ? 'i' : ''),
362
+ });
363
+ } catch (e) {
364
+ // Regex would not compile — dropped, not fatal. Matches snapshot.ts.
365
+ }
366
+ }
367
+ return rules;
368
+ }
369
+
370
+ function evaluate(toolName, target, command, rules) {
371
+ const nTool = normalise(toolName);
372
+ const nCommand = normalise(command);
373
+ const nTarget = normalise(target);
374
+ for (const rule of rules) {
375
+ const subjects =
376
+ rule.subject === 'tool' ? [nTool] :
377
+ rule.subject === 'command' ? [nCommand] :
378
+ rule.subject === 'target' ? [nTarget] :
379
+ [nCommand, nTarget];
380
+ for (const subject of subjects) {
381
+ if (!rule.pattern.test(subject)) continue;
382
+ return { severity: rule.severity, reason: rule.reason + ' [' + rule.id + ']' };
383
+ }
384
+ }
385
+ return null;
386
+ }
387
+
388
+ let inputData = '';
389
+ process.stdin.on('data', (chunk) => { inputData += chunk; });
390
+ process.stdin.on('end', () => {
391
+ try {
392
+ const ctx = JSON.parse(inputData || '{}');
393
+ const toolName = ctx.tool_name || ctx.toolName || '';
394
+ const toolInput = ctx.tool_input || ctx.toolInput || {};
395
+ const target = toolInput.path || toolInput.file_path || toolInput.filePath ||
396
+ toolInput.new_path || toolInput.target || toolInput.notebook_path || '';
397
+ const command = String(toolInput.command || toolInput.cmd || toolInput.script || '');
398
+ const rules = loadRules();
399
+ const decision = evaluate(toolName, target, command, rules);
400
+ if (decision && decision.severity === 'block') {
401
+ console.error('[Intutic Guardrail] BLOCKED: ' + decision.reason);
402
+ process.exit(2);
403
+ }
404
+ if (decision && (decision.severity === 'warn' || decision.severity === 'shadow')) {
405
+ console.error('[Intutic Guardrail] FLAGGED (' + decision.severity + '): ' + decision.reason);
406
+ }
407
+ process.exit(0);
408
+ } catch (err) {
409
+ // Fail CLOSED — any hook execution error blocks the tool call, the same
410
+ // posture emitJsFailClosedPrelude documents for the laptop script.
411
+ console.error('[Intutic Guardrail] Hook error (blocking for safety):', err && err.message ? err.message : err);
412
+ process.exit(2);
413
+ }
414
+ });
415
+ `;
416
+ }
417
+ /**
418
+ * Compiles the `.claude/settings.json` content that registers the generated
419
+ * hook script — same matcher set and hook shape as the laptop writer's
420
+ * `hookEntry()` (`services/sync-daemon/src/harness/claudeCodeHooks.ts`):
421
+ * `Bash`, `Edit`, `Write`, `MultiEdit`, and `mcp__.*`.
422
+ */
423
+ function renderSandboxClaudeSettings(hookScriptSandboxPath) {
424
+ const hookEntry = (matcher) => ({
425
+ matcher,
426
+ hooks: [
427
+ {
428
+ type: 'command',
429
+ command: `node ${hookScriptSandboxPath}`,
430
+ timeout: 10,
431
+ statusMessage: 'Verifying tool execution against Intutic SOP policy...',
432
+ },
433
+ ],
434
+ });
435
+ const settings = {
436
+ permissions: { deny: [] },
437
+ hooks: {
438
+ PreToolUse: [hookEntry('Bash'), hookEntry('Edit'), hookEntry('Write'), hookEntry('MultiEdit'), hookEntry('mcp__.*')],
439
+ },
440
+ };
441
+ return JSON.stringify(settings, null, 2) + '\n';
442
+ }
443
+ /**
444
+ * Builds `{ bootstrapHash, onBootstrap }` for
445
+ * `HarnessAgentSettings.sandboxConfig` — the channel TD-417 identified as
446
+ * existing but unused:
447
+ *
448
+ * ```ts
449
+ * import { HarnessAgent } from '@ai-sdk/harness/agent'
450
+ * import { intuticSandboxBootstrap, recommendedHarnessSettings } from '@intutic/gate/harness'
451
+ *
452
+ * const agent = new HarnessAgent({
453
+ * harness,
454
+ * sandbox,
455
+ * ...recommendedHarnessSettings(),
456
+ * sandboxConfig: intuticSandboxBootstrap({
457
+ * policySnapshotRules: fs.readFileSync(snapshotPath(), 'utf-8'),
458
+ * workspaceId: process.env.INTUTIC_WORKSPACE_ID,
459
+ * }),
460
+ * })
461
+ * ```
462
+ *
463
+ * Writes two files under `<workDir>/<bootstrapDir>` (default
464
+ * `.intutic/hooks`) — the rules text verbatim, and a self-contained hook
465
+ * script implementing Tier A1 against it (see
466
+ * {@link renderSandboxGateScript}) — plus `.claude/settings.json` at
467
+ * `<workDir>/.claude/settings.json` registering that script as a
468
+ * `PreToolUse` hook. See the module-level comment above this section for
469
+ * what this does and does not cover, and why.
470
+ */
471
+ export function intuticSandboxBootstrap(opts = {}) {
472
+ const bootstrapDir = opts.bootstrapDir ?? '.intutic/hooks';
473
+ const rulesRelPath = `${bootstrapDir}/policy-snapshot.rules`;
474
+ const scriptRelPath = `${bootstrapDir}/claude-code-check.js`;
475
+ const rulesContent = opts.policySnapshotRules ?? '';
476
+ const scriptContent = renderSandboxGateScript('policy-snapshot.rules');
477
+ // Same hashing convention `snapshot.ts` already uses for the policy
478
+ // digest (`createHash('sha256')` from node:crypto) — not a new mechanism.
479
+ const bootstrapHash = createHash('sha256')
480
+ .update(`workspace:${opts.workspaceId ?? ''} `)
481
+ .update(`${rulesRelPath}\x00${rulesContent}\x00${scriptRelPath}\x00${scriptContent}`)
482
+ .digest('hex');
483
+ return {
484
+ bootstrapHash,
485
+ onBootstrap: async ({ session, workDir }) => {
486
+ // Absolute paths throughout, matching the laptop writer's own
487
+ // convention (claudeCodeHooks.ts's hookEntry() always joins an
488
+ // absolute workspaceRoot) - a PreToolUse hook's invocation cwd is not
489
+ // guaranteed to be workDir, so a relative command would be fragile.
490
+ const scriptAbsPath = sandboxJoin(workDir, scriptRelPath);
491
+ const files = [
492
+ { path: sandboxJoin(workDir, rulesRelPath), content: rulesContent },
493
+ { path: scriptAbsPath, content: scriptContent },
494
+ { path: sandboxJoin(workDir, '.claude/settings.json'), content: renderSandboxClaudeSettings(scriptAbsPath) },
495
+ ];
496
+ for (const f of files) {
497
+ await session.writeTextFile({ path: f.path, content: f.content });
498
+ }
499
+ },
500
+ };
501
+ }
502
+ // Exposed for the behavioural-parity test — not part of the public surface.
503
+ export const _internal = { renderSandboxGateScript, renderSandboxClaudeSettings, sandboxJoin };
504
+ //# sourceMappingURL=harness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.js","sourceRoot":"","sources":["../src/harness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Port of `intutic_headers()` from
3
+ * `packages/intutic-clawde/intutic_clawde/gate/framework.py`.
4
+ */
5
+ export interface IntuticHeadersOptions {
6
+ sessionId?: string;
7
+ workspaceId?: string;
8
+ /** Attributes traces to this adapter. Defaults to `"generic"`. */
9
+ harness?: string;
10
+ }
11
+ /**
12
+ * Default headers for a client pointed at the Intutic proxy (an OpenAI-
13
+ * compatible chat client, a raw `fetch` call, etc.).
14
+ *
15
+ * `x-session-id` matters: the proxy defaults an unset one to the literal
16
+ * `"unknown"`, which merges every run into a single dashboard session.
17
+ * `x-intutic-harness` attributes traces to this adapter — the proxy honours
18
+ * it for trace attribution (unknown headers are ignored by older proxies, so
19
+ * it is always safe to send).
20
+ *
21
+ * Usage:
22
+ *
23
+ * const client = new OpenAI({ baseURL: proxyUrl, defaultHeaders: intuticHeaders({ sessionId: runId }) })
24
+ */
25
+ export declare function intuticHeaders(opts?: IntuticHeadersOptions): Record<string, string>;
26
+ //# sourceMappingURL=headers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,kEAAkE;IAClE,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,IAAI,GAAE,qBAA0B,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWvF"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Port of `intutic_headers()` from
3
+ * `packages/intutic-clawde/intutic_clawde/gate/framework.py`.
4
+ */
5
+ /**
6
+ * Default headers for a client pointed at the Intutic proxy (an OpenAI-
7
+ * compatible chat client, a raw `fetch` call, etc.).
8
+ *
9
+ * `x-session-id` matters: the proxy defaults an unset one to the literal
10
+ * `"unknown"`, which merges every run into a single dashboard session.
11
+ * `x-intutic-harness` attributes traces to this adapter — the proxy honours
12
+ * it for trace attribution (unknown headers are ignored by older proxies, so
13
+ * it is always safe to send).
14
+ *
15
+ * Usage:
16
+ *
17
+ * const client = new OpenAI({ baseURL: proxyUrl, defaultHeaders: intuticHeaders({ sessionId: runId }) })
18
+ */
19
+ export function intuticHeaders(opts = {}) {
20
+ const harness = opts.harness ?? 'generic';
21
+ const headers = { 'x-intutic-harness': harness };
22
+ const sess = opts.sessionId || process.env.INTUTIC_SESSION_ID || '';
23
+ if (sess)
24
+ headers['x-session-id'] = sess;
25
+ const ws = opts.workspaceId || process.env.INTUTIC_WORKSPACE_ID || '';
26
+ if (ws)
27
+ headers['x-workspace-id'] = ws;
28
+ return headers;
29
+ }
30
+ //# sourceMappingURL=headers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headers.js","sourceRoot":"","sources":["../src/headers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AASH;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,cAAc,CAAC,OAA8B,EAAE;IAC7D,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,SAAS,CAAA;IACzC,MAAM,OAAO,GAA2B,EAAE,mBAAmB,EAAE,OAAO,EAAE,CAAA;IAExE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE,CAAA;IACnE,IAAI,IAAI;QAAE,OAAO,CAAC,cAAc,CAAC,GAAG,IAAI,CAAA;IAExC,MAAM,EAAE,GAAG,IAAI,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,EAAE,CAAA;IACrE,IAAI,EAAE;QAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,EAAE,CAAA;IAEtC,OAAO,OAAO,CAAA;AAChB,CAAC"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Container image integrity checking.
3
+ *
4
+ * Port of `packages/intutic-clawde/intutic_clawde/gate/imagecheck.py`.
5
+ *
6
+ * Given a deploy command, find the manifests it would apply, extract every
7
+ * container image reference, and check each against an allowlist of approved
8
+ * sha256 digests. Nothing else in Intutic does this — no cosign, no sigstore,
9
+ * no admission webhook — so this check is the argument-level enforcement
10
+ * point for "deploy only pinned, reviewed images".
11
+ *
12
+ * Fail-closed on anything it cannot read. That is a deliberate departure from
13
+ * Intutic's fail-open default (WASM rules, hook-gate, the MCP proxy all fail
14
+ * open). The reasoning: a gate that cannot parse what it is being asked to
15
+ * approve does not know what it is approving, and "I could not read it" is
16
+ * not evidence of safety.
17
+ */
18
+ export declare const E_UNPINNED_LATEST = "E_UNPINNED_LATEST";
19
+ export declare const E_UNPINNED_TAG = "E_UNPINNED_TAG";
20
+ export declare const E_UNKNOWN_REGISTRY = "E_UNKNOWN_REGISTRY";
21
+ export declare const E_UNKNOWN_IMAGE = "E_UNKNOWN_IMAGE";
22
+ export declare const E_DIGEST_MISMATCH = "E_DIGEST_MISMATCH";
23
+ export declare const E_MANIFEST_UNPARSEABLE = "E_MANIFEST_UNPARSEABLE";
24
+ /** A parsed container image reference. */
25
+ export interface ImageRef {
26
+ raw: string;
27
+ /** registry/namespace/name, no tag, no digest */
28
+ repository: string;
29
+ tag: string | null;
30
+ /** `sha256:...` or null */
31
+ digest: string | null;
32
+ /** which manifest file it came from */
33
+ source: string;
34
+ }
35
+ export declare function isPinned(img: ImageRef): boolean;
36
+ export interface ImagePolicy {
37
+ require_digest?: boolean;
38
+ registries_allowed?: string[];
39
+ images?: Record<string, {
40
+ approved_digests?: string[];
41
+ }>;
42
+ }
43
+ export interface Verdict {
44
+ ok: boolean;
45
+ code: string;
46
+ detail: string;
47
+ images: ImageRef[];
48
+ }
49
+ /** The single line an operator sees on the dashboard. */
50
+ export declare function verdictReason(v: Verdict): string;
51
+ /**
52
+ * Parse `registry/repo:tag@sha256:...` into its parts.
53
+ *
54
+ * Docker's own rule for splitting registry from repository: the first path
55
+ * component is a registry host only if it contains a dot or a colon, or is
56
+ * exactly `localhost`. This function does not need to make that
57
+ * classification itself — it only needs to find the tag/digest boundary,
58
+ * which is a colon (or `@`) in the LAST path component; a colon earlier
59
+ * (a registry port, `localhost:5000/foo`) is left alone.
60
+ */
61
+ export declare function parseImageRef(raw: string, source?: string): ImageRef;
62
+ /**
63
+ * Extract the -f/-k targets from a kubectl/helm command.
64
+ *
65
+ * Returns `{paths, readable}`. `readable` is false when the command applies
66
+ * something we cannot inspect — stdin (`-f -`), a URL, or a command we could
67
+ * not tokenise. That drives the fail-closed path.
68
+ */
69
+ export declare function manifestPathsFromCommand(command: string): {
70
+ paths: string[];
71
+ readable: boolean;
72
+ };
73
+ /**
74
+ * Catch images named directly on the command line.
75
+ *
76
+ * `kubectl set image deploy/catalogue catalogue=repo:latest` never touches a
77
+ * manifest, so the YAML path would miss it entirely.
78
+ */
79
+ export declare function inlineImagesFromCommand(command: string): ImageRef[];
80
+ /** Evaluate parsed references against the allowlist. First failure wins. */
81
+ export declare function checkImages(images: readonly ImageRef[], policy: ImagePolicy): Verdict;
82
+ /** Full check for a deploy command. The entry point the gate calls. */
83
+ export declare function checkCommand(command: string, repoRoot: string, policy: ImagePolicy): Verdict;
84
+ /**
85
+ * Check a manifest at authoring time, before it is ever applied.
86
+ *
87
+ * Lets the dashboard show the bad image being *written* a full turn before it
88
+ * is *applied* — the write is flagged, the apply is blocked.
89
+ */
90
+ export declare function checkWrittenManifest(path: string, content: string, policy: ImagePolicy): Verdict;
91
+ /** True when this call should be image-checked. */
92
+ export declare function isDeployCommand(toolName: string, toolInput: unknown): boolean;
93
+ //# sourceMappingURL=imagecheck.d.ts.map