fullcourtdefense-cli 1.35.8 → 1.35.10

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 (59) hide show
  1. package/dist/actionIdentity.js +2 -1
  2. package/dist/actionPolicyEngine.d.ts +1 -0
  3. package/dist/actionPolicyEngine.js +203 -14
  4. package/dist/approvalPrompt.js +2 -2
  5. package/dist/askDialog.js +6 -6
  6. package/dist/blockExplanation.js +2 -0
  7. package/dist/callerEnv.d.ts +43 -0
  8. package/dist/callerEnv.js +107 -0
  9. package/dist/commandNarrative.d.ts +8 -0
  10. package/dist/commandNarrative.js +179 -13
  11. package/dist/commands/agentCi.js +3 -1
  12. package/dist/commands/daemon.js +7 -2
  13. package/dist/commands/deterministicGuard.d.ts +1 -1
  14. package/dist/commands/deterministicGuard.js +319 -28
  15. package/dist/commands/discover.js +23 -0
  16. package/dist/commands/discoverAgentFiles.d.ts +11 -0
  17. package/dist/commands/discoverAgentFiles.js +107 -0
  18. package/dist/commands/discoverExtensions.d.ts +64 -0
  19. package/dist/commands/discoverExtensions.js +386 -0
  20. package/dist/commands/hook.d.ts +58 -1
  21. package/dist/commands/hook.js +223 -37
  22. package/dist/commands/local-scan.js +1 -0
  23. package/dist/commands/mcpGateway.js +273 -54
  24. package/dist/commands/protectedRun.d.ts +1 -1
  25. package/dist/commands/protectedRun.js +3 -3
  26. package/dist/commands/taintLedger.js +3 -1
  27. package/dist/commands/watchdog.js +2 -0
  28. package/dist/credentialGrant.d.ts +43 -0
  29. package/dist/credentialGrant.js +504 -0
  30. package/dist/daemonForensics.d.ts +27 -0
  31. package/dist/daemonForensics.js +47 -0
  32. package/dist/detectorBaseline.d.ts +2 -2
  33. package/dist/detectorBaseline.js +2 -2
  34. package/dist/devConfirm.d.ts +76 -13
  35. package/dist/devConfirm.js +352 -144
  36. package/dist/envDiagnostics.js +4 -2
  37. package/dist/hookSlim.d.ts +1 -1
  38. package/dist/hookSlim.js +26 -5
  39. package/dist/ideExtensionRisk.d.ts +110 -0
  40. package/dist/ideExtensionRisk.js +172 -0
  41. package/dist/machineActionVerify.d.ts +34 -8
  42. package/dist/machineActionVerify.js +76 -16
  43. package/dist/mcpProtocol.d.ts +124 -0
  44. package/dist/mcpProtocol.js +302 -0
  45. package/dist/mcpToolReview.d.ts +2 -1
  46. package/dist/mcpToolReview.js +6 -4
  47. package/dist/nativeHook.d.ts +2 -1
  48. package/dist/nativeHook.js +109 -3
  49. package/dist/notify.d.ts +7 -0
  50. package/dist/notify.js +65 -0
  51. package/dist/shellQuoteMask.d.ts +25 -0
  52. package/dist/shellQuoteMask.js +319 -0
  53. package/dist/telemetry.d.ts +4 -0
  54. package/dist/telemetry.js +6 -0
  55. package/dist/verdictIpc.js +21 -2
  56. package/dist/verdictIpcClient.d.ts +43 -13
  57. package/dist/verdictIpcClient.js +66 -27
  58. package/dist/version.json +1 -1
  59. package/package.json +8 -3
@@ -20,6 +20,14 @@ export interface CommandStep {
20
20
  text: string;
21
21
  /** The grammar recognised the program and its verb. */
22
22
  known: boolean;
23
+ /** A credential stored in a variable by this step (`$tok = gcloud auth print-access-token`). */
24
+ captures?: {
25
+ variable: string;
26
+ issuer?: {
27
+ name: string;
28
+ domains: string[];
29
+ };
30
+ };
23
31
  }
24
32
  /** Quote-aware split on `;`, `&&`, `||`, newlines. Pipes stay inside a step. */
25
33
  export declare function splitCommandSteps(command: string): string[];
@@ -20,6 +20,12 @@ exports.describeShellCommand = describeShellCommand;
20
20
  exports.describeShellCommandLines = describeShellCommandLines;
21
21
  const MAX_STEPS = 14;
22
22
  const MAX_LINE = 220;
23
+ /** Token printers whose output can be assigned; `issuer` = who accepts the token (identity tokens go to their audience). */
24
+ const TOKEN_PRINTERS = [
25
+ { re: /^gcloud(?:\.cmd|\.exe)?\s+(?:[\w-]+\s+)*?auth\s+(?:application-default\s+)?print-access-token\b/i, issuer: { name: 'Google', domains: ['googleapis.com'] } },
26
+ { re: /^gcloud(?:\.cmd|\.exe)?\s+(?:[\w-]+\s+)*?auth\s+print-identity-token\b/i },
27
+ { re: /^gh(?:\.exe)?\s+auth\s+token\b/i, issuer: { name: 'GitHub', domains: ['github.com', 'githubusercontent.com'] } },
28
+ ];
23
29
  /** Quote-aware split on `;`, `&&`, `||`, newlines. Pipes stay inside a step. */
24
30
  function splitCommandSteps(command) {
25
31
  const steps = [];
@@ -110,12 +116,12 @@ const GCLOUD_SERVICE_NAMES = {
110
116
  secrets: 'Secret Manager', iam: 'IAM', projects: 'project', functions: 'Cloud Functions', container: 'GKE', logging: 'Cloud Logging',
111
117
  artifacts: 'Artifact Registry', scheduler: 'Cloud Scheduler', tasks: 'Cloud Tasks', kms: 'Cloud KMS', builds: 'Cloud Build', auth: 'auth', config: 'config',
112
118
  };
113
- const GCLOUD_READ = /^(?:describe|list|ls|get-iam-policy|get-value|get-ancestors|read|check|versions)$/;
119
+ const GCLOUD_READ = /^(?:describe|list|ls|get-iam-policy|get-value|get-ancestors|read|check)$/;
114
120
  const GCLOUD_WRITE = /^(?:create|update|patch|replace|enable|disable|deploy|apply|start|stop|restart|resume|suspend|scale|promote|migrate|rollback|add-[a-z-]+|remove-[a-z-]+|set-[a-z-]+|unset|clear|activate|revoke|copy)$/;
115
121
  function humanVerb(verb) {
116
122
  const map = {
117
123
  describe: 'Reads details of', list: 'Lists', ls: 'Lists', 'get-iam-policy': 'Reads the access policy of', 'get-value': 'Reads the setting',
118
- read: 'Reads', check: 'Checks', versions: 'Lists versions of',
124
+ read: 'Reads', check: 'Checks',
119
125
  create: 'Creates', update: 'Updates', patch: 'Updates', replace: 'Replaces', enable: 'Enables', disable: 'Disables', deploy: 'Deploys',
120
126
  apply: 'Applies', start: 'Starts', stop: 'Stops', restart: 'Restarts', resume: 'Resumes', suspend: 'Suspends', scale: 'Scales',
121
127
  promote: 'Promotes', migrate: 'Migrates', rollback: 'Rolls back', unset: 'Unsets', clear: 'Clears', activate: 'Activates', revoke: 'Revokes', copy: 'Copies',
@@ -135,12 +141,43 @@ function humanVerb(verb) {
135
141
  return `Sets ${verb.slice(4).replace(/-/g, ' ')} on`;
136
142
  return `Runs \`${verb}\` on`;
137
143
  }
138
- function describeGcloud(toks) {
144
+ /** `gcloud secrets versions <verb>`: `access` reads the secret VALUE, `list` lists versions, `add` stores a new value. */
145
+ function describeSecretVersions(toks, verbAt, where) {
146
+ const verb = toks[verbAt] || '';
147
+ const ops = operandsAfter(toks, verbAt + 1);
148
+ const flagSecret = flagValue(toks, '--secret');
149
+ const secret = (name) => (name ? `secret ${shortValue(name, 60)}` : 'a secret');
150
+ if (verb === 'access') {
151
+ return `Reads the value of ${secret(flagSecret)} (version ${shortValue(ops[0] || 'latest', 20)}) from Secret Manager${where}`;
152
+ }
153
+ if (verb === 'list')
154
+ return `Lists the versions of ${secret(ops[0])} in Secret Manager${where}`;
155
+ if (verb === 'add') {
156
+ const dataFile = flagValue(toks, '--data-file');
157
+ return `Adds a new version to ${secret(ops[0])} in Secret Manager${dataFile ? `, value from ${dataFile === '-' ? 'standard input' : shortValue(dataFile, 60)}` : ''}${where}`;
158
+ }
159
+ const version = ops[0] ? `version ${shortValue(ops[0], 20)} of ` : '';
160
+ if (verb === 'describe')
161
+ return `Reads details of ${version}${secret(flagSecret)} in Secret Manager${where}`;
162
+ const state = { destroy: 'Destroys', disable: 'Disables', enable: 'Enables' };
163
+ if (state[verb])
164
+ return `${state[verb]} ${version}${secret(flagSecret)} in Secret Manager${where}`;
165
+ return `Runs \`gcloud secrets versions ${verb}\`${where}`;
166
+ }
167
+ /**
168
+ * `captured` = the variable the command's output is assigned to (`$tok = gcloud …`,
169
+ * `TOKEN=$(gcloud …)`): the output never reaches the terminal.
170
+ */
171
+ function describeGcloud(toks, captured) {
139
172
  let i = 1;
140
173
  while (i < toks.length && toks[i].startsWith('-'))
141
174
  i++;
142
175
  if (/^(?:alpha|beta)$/.test(toks[i] || ''))
143
176
  i++;
177
+ if (toks[i] === 'secrets' && toks[i + 1] === 'versions') {
178
+ const project = flagValue(toks, '--project');
179
+ return describeSecretVersions(toks, i + 2, project ? ` in project ${project}` : '');
180
+ }
144
181
  const groups = [];
145
182
  let verb;
146
183
  for (; i < toks.length; i++) {
@@ -183,8 +220,10 @@ function describeGcloud(toks) {
183
220
  return `Reads the contents of ${isStorageUri(first) ? `an object in storage bucket ${bucketOf(first)}` : shortValue(first, 60)}`;
184
221
  if (verb === 'rm')
185
222
  return `Deletes ${named(first)}`;
186
- if (/^print-(?:access|identity)-token$/.test(verb))
187
- return 'Prints a cloud access token to the terminal';
223
+ if (/^print-(?:access|identity)-token$/.test(verb)) {
224
+ const kind = verb === 'print-identity-token' ? 'identity' : 'access';
225
+ return captured ? `Stores a cloud ${kind} token in variable ${captured}` : `Prints a cloud ${kind} token to the terminal`;
226
+ }
188
227
  if (verb === 'login')
189
228
  return 'Signs in to Google Cloud';
190
229
  if (verb === 'add-iam-policy-binding' || verb === 'remove-iam-policy-binding') {
@@ -234,7 +273,80 @@ function describePipeTail(stages) {
234
273
  return ', and writes the output to a file';
235
274
  return '';
236
275
  }
237
- function describeOne(segmentIn) {
276
+ /**
277
+ * The whole statement is one quoted string (`"len=$($t.Length)"`, `'done'`): its text
278
+ * with each `$( … )` sub-expression shown as `…`. Undefined when it is anything else.
279
+ */
280
+ function bareStringText(head) {
281
+ const value = head.trim();
282
+ const q = value[0];
283
+ if ((q !== '"' && q !== "'") || value.length < 2 || value[value.length - 1] !== q)
284
+ return undefined;
285
+ let depth = 0;
286
+ let text = '';
287
+ for (let k = 1; k < value.length - 1; k++) {
288
+ const ch = value[k];
289
+ if (q === '"' && ch === '`') {
290
+ text += value[++k] ?? '';
291
+ continue;
292
+ }
293
+ if (q === '"' && ch === '$' && value[k + 1] === '(') {
294
+ if (depth++ === 0)
295
+ text += '…';
296
+ k++;
297
+ continue;
298
+ }
299
+ if (depth > 0) {
300
+ if (ch === '(')
301
+ depth++;
302
+ else if (ch === ')')
303
+ depth--;
304
+ continue;
305
+ }
306
+ if (ch === q) {
307
+ if (q === "'" && value[k + 1] === "'") {
308
+ text += "'";
309
+ k++;
310
+ continue;
311
+ }
312
+ return undefined;
313
+ }
314
+ text += ch;
315
+ }
316
+ return depth === 0 ? text.trim() : undefined;
317
+ }
318
+ /** `((gcloud … $p) -join '').Trim()` -> `gcloud … $p`: the command inside nested groups (quote-aware). */
319
+ function peelGroups(text) {
320
+ let value = text.trim();
321
+ for (let guard = 0; guard < 8 && value.startsWith('('); guard++) {
322
+ let depth = 0;
323
+ let end = -1;
324
+ let quote = '';
325
+ for (let k = 0; k < value.length; k++) {
326
+ const ch = value[k];
327
+ if (quote) {
328
+ if (ch === quote)
329
+ quote = '';
330
+ continue;
331
+ }
332
+ if (ch === '"' || ch === "'") {
333
+ quote = ch;
334
+ continue;
335
+ }
336
+ if (ch === '(')
337
+ depth++;
338
+ else if (ch === ')' && --depth === 0) {
339
+ end = k;
340
+ break;
341
+ }
342
+ }
343
+ if (end < 0)
344
+ break;
345
+ value = value.slice(1, end).trim();
346
+ }
347
+ return value;
348
+ }
349
+ function describeOne(segmentIn, captured) {
238
350
  const segment = segmentIn.trim();
239
351
  const [head, ...pipe] = splitPipe(segment);
240
352
  const tail = describePipeTail(pipe);
@@ -270,10 +382,19 @@ function describeOne(segmentIn) {
270
382
  }
271
383
  return step(`Sets variable ${name}${interpolates ? ' from other values' : ` to ${shortValue(bare, 60)}`}`);
272
384
  }
273
- const inner = rhs.replace(/^\(\s*/, '').replace(/\)\s*(?:\.\w+\(\))*$/, '');
274
- const innerStep = describeOne(inner);
385
+ const substituted = /^\$\((.*)\)$/s.exec(rhs) || /^`(.*)`$/s.exec(rhs);
386
+ const inner = substituted ? substituted[1].trim() : rhs.startsWith('(') ? peelGroups(rhs) : rhs.replace(/\)\s*(?:\.\w+\(\))*$/, '');
387
+ const innerStep = describeOne(inner, name);
388
+ const printer = TOKEN_PRINTERS.find(p => p.re.test(inner));
389
+ if (printer) {
390
+ return { segment, text: shortValue(`${innerStep.text}${tail}`, MAX_LINE), known: innerStep.known, captures: { variable: name, ...(printer.issuer ? { issuer: printer.issuer } : {}) } };
391
+ }
275
392
  return { segment, text: shortValue(`Stores in ${name}: ${innerStep.text.charAt(0).toLowerCase()}${innerStep.text.slice(1)}${tail}`, MAX_LINE), known: innerStep.known };
276
393
  }
394
+ // A bare string statement: PowerShell writes it to the output (`"len=$($t.Length)"`).
395
+ const bare = bareStringText(head);
396
+ if (bare !== undefined)
397
+ return step(bare ? `Prints text: ${shortValue(bare, 80)}` : 'Prints an empty line');
277
398
  const toks = tokens(head);
278
399
  if (toks.length === 0)
279
400
  return step('(empty)', false);
@@ -308,7 +429,7 @@ function describeOne(segmentIn) {
308
429
  case 'exit':
309
430
  case 'return': return step('Ends the script');
310
431
  case 'gcloud': {
311
- const text = describeGcloud(rest);
432
+ const text = describeGcloud(rest, captured);
312
433
  return text ? step(`${text}${redirectNote}`) : step(`Runs \`gcloud\``, false);
313
434
  }
314
435
  case 'gsutil': {
@@ -371,7 +492,7 @@ function describeOne(segmentIn) {
371
492
  if (/\b(?:create|edit|merge|close|delete|comment)\b/.test(sub))
372
493
  return step(`Changes GitHub ${sub.split(' ')[0]} (${sub.split(' ')[1]})`);
373
494
  if (/^auth token\b/.test(sub))
374
- return step('Prints the GitHub token');
495
+ return step(captured ? `Stores the GitHub token in variable ${captured}` : 'Prints the GitHub token');
375
496
  return step(`Runs \`gh ${sub}\``, false);
376
497
  }
377
498
  case 'npm':
@@ -531,7 +652,10 @@ function describeOne(segmentIn) {
531
652
  default:
532
653
  if (/^\$/.test(programToken))
533
654
  return step(pipe.length ? `Uses ${shortValue(programToken, 40)}${tail}` : `Reads variable ${shortValue(programToken, 40)}`, false);
534
- return step(`Runs \`${shortValue([program, rest[1]].filter(t => t && !t.startsWith('-')).join(' '), 50)}\``, false);
655
+ {
656
+ const shown = shortValue([program, rest[1]].filter(t => t && !t.startsWith('-')).join(' '), 50);
657
+ return step(shown ? `Runs \`${shown}\`` : 'Runs a command this dialog cannot describe', false);
658
+ }
535
659
  }
536
660
  }
537
661
  /** Split one step on top-level `|` (quote-aware). */
@@ -584,17 +708,59 @@ function resolveStorageVariables(steps) {
584
708
  return { ...step, text };
585
709
  });
586
710
  }
711
+ /**
712
+ * A token stored in a variable, then used later on the line: name the host(s)
713
+ * that receive it and whether each is the token's issuer. Hosts are literal
714
+ * URLs in the using step, or a variable assigned a literal URL earlier.
715
+ */
716
+ function noteTokenDestinations(steps) {
717
+ const urlVars = new Map();
718
+ for (const step of steps) {
719
+ const assign = /^\$?([\w:]+)\s*=\s*["']?https?:\/\/([^/\s"'`$]+)/i.exec(step.segment);
720
+ if (assign)
721
+ urlVars.set(assign[1].toLowerCase(), assign[2].toLowerCase());
722
+ }
723
+ return steps.map((step, index) => {
724
+ if (!step.captures)
725
+ return step;
726
+ const { variable, issuer } = step.captures;
727
+ const ref = new RegExp(`\\$\\{?${variable.replace(/[^\w:]/g, '')}\\}?(?![\\w])`, 'i');
728
+ const hosts = new Set();
729
+ for (const later of steps.slice(index + 1)) {
730
+ if (!ref.test(later.segment))
731
+ continue;
732
+ for (const m of later.segment.matchAll(/https?:\/\/([^/\s"'`<>]+)/gi))
733
+ if (!/[$({]/.test(m[1]))
734
+ hosts.add(m[1].toLowerCase().replace(/:\d+$/, ''));
735
+ for (const m of later.segment.matchAll(/\$\{?([\w:]+)\}?/g)) {
736
+ const host = urlVars.get(m[1].toLowerCase());
737
+ if (host)
738
+ hosts.add(host);
739
+ }
740
+ }
741
+ if (!hosts.size)
742
+ return step;
743
+ const described = [...hosts].sort().map(host => {
744
+ if (!issuer)
745
+ return host;
746
+ const own = issuer.domains.some(d => host === d || host.endsWith(`.${d}`));
747
+ return own ? `${host} (${issuer.name}, the token's issuer)` : `${host}, which is not ${issuer.name}`;
748
+ });
749
+ return { ...step, text: shortValue(`${step.text}, then sends it to ${described.join('; ')}`, MAX_LINE) };
750
+ });
751
+ }
587
752
  /** One plain-words line per step, in order. */
588
753
  function describeShellCommand(command) {
589
754
  const steps = splitCommandSteps(command);
590
- const out = resolveStorageVariables(steps.slice(0, MAX_STEPS).map(segment => {
755
+ const described = steps.map(segment => {
591
756
  try {
592
757
  return describeOne(segment);
593
758
  }
594
759
  catch {
595
760
  return { segment, text: 'Runs a command the grammar does not recognise', known: false };
596
761
  }
597
- }));
762
+ });
763
+ const out = resolveStorageVariables(noteTokenDestinations(described).slice(0, MAX_STEPS));
598
764
  if (steps.length > MAX_STEPS)
599
765
  out.push({ segment: '', text: `… and ${steps.length - MAX_STEPS} more step${steps.length - MAX_STEPS === 1 ? '' : 's'}`, known: false });
600
766
  return out;
@@ -216,6 +216,8 @@ function agentFileRemediation(tags) {
216
216
  fixes.push('remove inline secret/password/token references and use a managed secret store');
217
217
  if (tags.includes('jailbreak_hint'))
218
218
  fixes.push('remove jailbreak/bypass instructions and add hierarchy language that system and security policies win');
219
+ if (tags.includes('hooks_disabled'))
220
+ fixes.push('remove "disableAllHooks": true (or a managed "allowManagedHooksOnly": true) so Claude Code runs the Full Court Defense hook again');
219
221
  if (tags.includes('no_runtime_hook'))
220
222
  fixes.push('install the Full Court Defense hook/gateway so risky tool calls are checked before execution');
221
223
  if (fixes.length === 0)
@@ -266,7 +268,7 @@ function evaluateAgentGate(args) {
266
268
  }
267
269
  for (const file of agentFiles.filter(item => item.riskTags.length > 0)) {
268
270
  findings.push(finding({
269
- severity: file.riskTags.includes('allows_shell') || file.riskTags.includes('broad_filesystem') ? 'high' : 'medium',
271
+ severity: file.riskTags.includes('allows_shell') || file.riskTags.includes('broad_filesystem') || file.riskTags.includes('hooks_disabled') ? 'high' : 'medium',
270
272
  category: 'agent-file',
271
273
  title: `Risky agent instruction: ${file.title}`,
272
274
  detail: `${file.filePath} contains agent risk tags: ${file.riskTags.join(', ')}.`,
@@ -526,6 +526,9 @@ async function runDaemon(args, config) {
526
526
  // of an OLDER version is the upgrade itself, not something to alarm on.
527
527
  log(`Previous daemon (v${postmortem.version}) was replaced by this upgrade to v${current} — the updater's forced stop is expected, not a crash. Post-mortem recorded for the timeline.`);
528
528
  }
529
+ else if (postmortem.cause === 'machine_restart') {
530
+ log(`Previous daemon (v${postmortem.version || '?'}) stopped when the machine restarted (booted ${postmortem.machineBootedAt || 'after its last heartbeat'}; last alive ${postmortem.lastAliveAt || 'unknown'}). Expected, not a crash. Post-mortem recorded for the timeline.`);
531
+ }
529
532
  else {
530
533
  log(`Previous daemon (v${postmortem.version || '?'}) died UNCLEANLY — last alive ${postmortem.lastAliveAt || 'unknown'}, no shutdown recorded. Likely killed externally (EDR/AV, OOM, or forced termination). Post-mortem recorded; reporting upstream.`);
531
534
  }
@@ -1097,7 +1100,7 @@ async function runDaemon(args, config) {
1097
1100
  // whose enrolled fingerprint drifted can set FCD_MACHINE_ACTION_ANY_MACHINE_OK=1
1098
1101
  // locally while they re-enroll.
1099
1102
  const localIdentity = (0, machineIdentity_1.getMachineIdentity)();
1100
- const verdict = (0, machineActionVerify_1.verifyMachineAction)(action, { localMachineId: localIdentity.machineId });
1103
+ const verdict = (0, machineActionVerify_1.verifyMachineAction)(action, { localMachineId: localIdentity.machineId, apiUrl: creds.apiUrl });
1101
1104
  if (!verdict.ok) {
1102
1105
  log(`Remote action REJECTED: ${action.type} (${action.id}) — ${verdict.reason}`);
1103
1106
  await reportMachineAction(action.id, 'failed', { error: `Action verification failed: ${verdict.reason}` }, action.signature?.signature);
@@ -1505,6 +1508,8 @@ async function runDaemon(args, config) {
1505
1508
  lastAliveAt: unreportedCrash.lastAliveAt,
1506
1509
  detectedAt: unreportedCrash.detectedAt,
1507
1510
  detectedBy: unreportedCrash.detectedBy,
1511
+ cause: unreportedCrash.cause,
1512
+ machineBootedAt: unreportedCrash.machineBootedAt,
1508
1513
  }
1509
1514
  : undefined,
1510
1515
  });
@@ -1593,7 +1598,7 @@ async function runDaemon(args, config) {
1593
1598
  // to full local evaluation whenever this server is down — never a blocker.
1594
1599
  let verdictServer;
1595
1600
  try {
1596
- verdictServer = (0, verdictIpc_1.startVerdictServer)((hookArgs, stdin) => (0, hook_1.evaluateHookRequest)(hookArgs, stdin, (0, config_1.loadConfig)()), log);
1601
+ verdictServer = (0, verdictIpc_1.startVerdictServer)((hookArgs, stdin) => (0, hook_1.evaluateHookRequest)(hookArgs, stdin, (0, config_1.loadConfig)(), { viaDaemon: true }), log);
1597
1602
  }
1598
1603
  catch (error) {
1599
1604
  log(`Verdict IPC: failed to start (${error.message}) — hooks will evaluate locally.`);
@@ -1,5 +1,5 @@
1
1
  export type DeterministicDirection = 'request' | 'response';
2
- export type DeterministicCategory = 'sensitive_file' | 'metadata_ssrf' | 'credential_command' | 'destructive_command' | 'destructive_sql' | 'infra_destroy' | 'reverse_shell' | 'secret_exfiltration' | 'honeypot';
2
+ export type DeterministicCategory = 'sensitive_file' | 'metadata_ssrf' | 'credential_command' | 'destructive_command' | 'destructive_sql' | 'infra_destroy' | 'reverse_shell' | 'secret_exfiltration' | 'honeypot' | 'agent_guard_tamper';
3
3
  /**
4
4
  * Per-rule enforcement action (org-configured, shipped in the Local Safety
5
5
  * snapshot). Absent everywhere = 'block' — the only behavior older CLIs and