fullcourtdefense-cli 1.34.21 → 1.34.25

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 (91) hide show
  1. package/README.md +1 -0
  2. package/dist/actionIdentity.d.ts +1 -0
  3. package/dist/actionIdentity.js +2 -0
  4. package/dist/actionPolicyEngine.d.ts +5 -0
  5. package/dist/actionPolicyEngine.js +273 -30
  6. package/dist/agentTerminal.d.ts +1 -3
  7. package/dist/agentTerminal.js +14 -29
  8. package/dist/approvalPrompt.d.ts +7 -0
  9. package/dist/approvalPrompt.js +46 -0
  10. package/dist/askDialog.d.ts +87 -0
  11. package/dist/askDialog.js +209 -0
  12. package/dist/blockExplanation.js +18 -6
  13. package/dist/boundaryVerification.d.ts +20 -0
  14. package/dist/boundaryVerification.js +147 -0
  15. package/dist/commands/daemon.js +198 -70
  16. package/dist/commands/deterministicGuard.d.ts +2 -0
  17. package/dist/commands/deterministicGuard.js +161 -9
  18. package/dist/commands/hook.js +81 -29
  19. package/dist/commands/installClaudeHook.js +11 -5
  20. package/dist/commands/installCursorHook.js +5 -4
  21. package/dist/commands/mcpGateway.d.ts +2 -0
  22. package/dist/commands/mcpGateway.js +163 -56
  23. package/dist/commands/onboard.js +6 -5
  24. package/dist/commands/onboardingSummary.d.ts +5 -0
  25. package/dist/commands/onboardingSummary.js +11 -0
  26. package/dist/commands/protectedRun.d.ts +14 -0
  27. package/dist/commands/protectedRun.js +207 -0
  28. package/dist/containment-runtime/index.mjs +30118 -0
  29. package/dist/containment-runtime/licenses/_anthropic-ai_sandbox-runtime.txt +201 -0
  30. package/dist/containment-runtime/licenses/_pondwader_socks5-server.txt +20 -0
  31. package/dist/containment-runtime/licenses/commander.txt +22 -0
  32. package/dist/containment-runtime/licenses/node-forge.txt +331 -0
  33. package/dist/containment-runtime/licenses/zod.txt +21 -0
  34. package/dist/containment-runtime/runtime-version.json +1 -0
  35. package/dist/containment-runtime/vendor/java-proxy-agent/build.ts +95 -0
  36. package/dist/containment-runtime/vendor/java-proxy-agent/srt-proxy-agent.jar +0 -0
  37. package/dist/containment-runtime/vendor/seccomp/arm64/apply-seccomp +0 -0
  38. package/dist/containment-runtime/vendor/seccomp/build.ts +69 -0
  39. package/dist/containment-runtime/vendor/seccomp/x64/apply-seccomp +0 -0
  40. package/dist/containment-runtime/vendor/srt-win/arm64/srt-win.exe +0 -0
  41. package/dist/containment-runtime/vendor/srt-win/build.ts +21 -0
  42. package/dist/containment-runtime/vendor/srt-win/x64/srt-win.exe +0 -0
  43. package/dist/containmentLease.d.ts +5 -0
  44. package/dist/containmentLease.js +72 -0
  45. package/dist/detectionUpdates.d.ts +23 -3
  46. package/dist/detectionUpdates.js +45 -14
  47. package/dist/detectorArtifact.d.ts +17 -0
  48. package/dist/detectorArtifact.js +35 -0
  49. package/dist/detectorBaseline.d.ts +2 -0
  50. package/dist/detectorBaseline.js +5 -0
  51. package/dist/detectorModule.d.ts +9 -0
  52. package/dist/detectorModule.js +84 -0
  53. package/dist/detectorRuntime.d.ts +21 -0
  54. package/dist/detectorRuntime.js +49 -0
  55. package/dist/detectorUpdates.d.ts +21 -0
  56. package/dist/detectorUpdates.js +86 -0
  57. package/dist/devConfirm.d.ts +97 -15
  58. package/dist/devConfirm.js +447 -117
  59. package/dist/distress.d.ts +2 -0
  60. package/dist/distress.js +2 -0
  61. package/dist/frictionRollout.js +103 -0
  62. package/dist/gatewayRuntime.d.ts +19 -0
  63. package/dist/gatewayRuntime.js +93 -0
  64. package/dist/hookSlim.js +17 -18
  65. package/dist/index.js +35 -0
  66. package/dist/localDetectionUpdates.d.ts +3 -1
  67. package/dist/localDetectionUpdates.js +8 -1
  68. package/dist/localSafetySnapshot.d.ts +9 -5
  69. package/dist/localSafetySnapshot.js +42 -15
  70. package/dist/mcpToolReview.d.ts +17 -0
  71. package/dist/mcpToolReview.js +127 -0
  72. package/dist/mcpToolTrust.d.ts +22 -0
  73. package/dist/mcpToolTrust.js +66 -0
  74. package/dist/nativeHook.d.ts +4 -0
  75. package/dist/nativeHook.js +31 -17
  76. package/dist/policyRefresh.d.ts +3 -0
  77. package/dist/policyRefresh.js +27 -0
  78. package/dist/protectionMaintenance.d.ts +10 -0
  79. package/dist/protectionMaintenance.js +64 -0
  80. package/dist/protectionProfile.d.ts +26 -0
  81. package/dist/protectionProfile.js +63 -0
  82. package/dist/runtimeConfig.d.ts +2 -0
  83. package/dist/runtimeConfig.js +65 -26
  84. package/dist/selfTest.d.ts +2 -0
  85. package/dist/selfTest.js +9 -1
  86. package/dist/sessionLimits.d.ts +2 -0
  87. package/dist/sessionLimits.js +24 -0
  88. package/dist/telemetry.d.ts +5 -0
  89. package/dist/telemetry.js +8 -0
  90. package/dist/version.json +1 -1
  91. package/package.json +10 -6
@@ -71,9 +71,13 @@ function readCacheFile() {
71
71
  }
72
72
  function writeCacheFile(data) {
73
73
  try {
74
- fs.writeFileSync(CACHE_PATH, JSON.stringify(data), { encoding: 'utf-8', mode: 0o600 });
74
+ // Keep the last complete policy set if writing the replacement is interrupted.
75
+ writeFileAtomic(CACHE_PATH, JSON.stringify(data), 0o600);
76
+ return true;
77
+ }
78
+ catch {
79
+ return false;
75
80
  }
76
- catch { /* cache is best-effort */ }
77
81
  }
78
82
  function isMode(value) {
79
83
  return value === 'block' || value === 'monitor' || value === 'shadow';
@@ -185,31 +189,37 @@ function cachedToEffective(cached) {
185
189
  return { ...bundle, suspended, source: 'cache', serverValidatedAt: fetchedAt };
186
190
  }
187
191
  /** Keep only a well-formed machine-role block (limits must be numbers or null; legacy keys normalized). */
188
- function sanitizeMachineRole(value) {
189
- if (!value || typeof value !== 'object')
190
- return undefined;
192
+ function sanitizeMachineRole(value, previous) {
193
+ if (value === undefined && !previous)
194
+ return undefined; // legacy server compatibility
195
+ if (!value || typeof value !== 'object' || Array.isArray(value))
196
+ throw new Error('Invalid or missing machine role');
191
197
  const role = value;
192
- if (typeof role.roleId !== 'string' || typeof role.name !== 'string')
193
- return undefined;
194
- const rawLimits = (role.limits && typeof role.limits === 'object' ? role.limits : {});
198
+ if (typeof role.roleId !== 'string' || !role.roleId || typeof role.name !== 'string'
199
+ || (role.stage !== undefined && role.stage !== 'monitor' && role.stage !== 'enforce')) {
200
+ throw new Error('Invalid machine role configuration');
201
+ }
195
202
  return {
196
203
  roleId: role.roleId,
197
204
  name: role.name,
198
205
  summary: typeof role.summary === 'string' ? role.summary : undefined,
199
206
  stage: role.stage === 'monitor' ? 'monitor' : 'enforce',
200
- limits: (0, sessionLimits_1.normalizeSessionLimits)(rawLimits),
207
+ limits: (0, sessionLimits_1.parseDeliveredSessionLimits)(role.limits, previous?.limits),
201
208
  };
202
209
  }
203
- /** Keep only well-formed policies — a malformed server payload must never poison the offline cache. */
210
+ /** Reject incomplete policy sets before any cache or suspension marker is changed. */
204
211
  function sanitizeBundlePolicies(value) {
205
- if (!Array.isArray(value))
206
- return undefined;
207
- return value
208
- .filter((policy) => Boolean(policy && typeof policy === 'object'
209
- && typeof policy.id === 'string'
210
- && typeof policy.name === 'string'
211
- && Array.isArray(policy.rules)))
212
- .slice(0, 300);
212
+ if (value === undefined)
213
+ return undefined; // pre-action-policy server compatibility
214
+ if (!Array.isArray(value) || value.length > 300 || value.some(policy => !policy || typeof policy !== 'object' || typeof policy.id !== 'string' || !policy.id
215
+ || typeof policy.name !== 'string' || !Array.isArray(policy.rules)
216
+ || (policy.enforcement !== undefined && !['standard', 'hard'].includes(policy.enforcement))
217
+ || policy.rules.some((rule) => !rule || !Array.isArray(rule.operations)
218
+ || !rule.operations.length || rule.operations.some((op) => typeof op !== 'string' || !op)
219
+ || !['allow', 'block', 'require_approval', 'log'].includes(rule.verdict)))) {
220
+ throw new Error('Invalid or incomplete action policy bundle');
221
+ }
222
+ return value;
213
223
  }
214
224
  /**
215
225
  * Resolve the effective runtime bundle for a Shield, using the local cache when
@@ -248,6 +258,8 @@ async function getRuntimeBundle(input) {
248
258
  }
249
259
  catch { /* best-effort */ }
250
260
  }
261
+ let responseReceived = false;
262
+ let cacheWriteFailed = false;
251
263
  try {
252
264
  const headers = { 'Content-Type': 'application/json' };
253
265
  if (input.shieldKey)
@@ -262,6 +274,7 @@ async function getRuntimeBundle(input) {
262
274
  if (input.machineId)
263
275
  params.set('machineId', input.machineId);
264
276
  const resp = await fetch(`${input.apiUrl}/api/cli/bundle?${params.toString()}`, { method: 'GET', headers, signal: AbortSignal.timeout(input.timeoutMs ?? REFRESH_TIMEOUT_MS) });
277
+ responseReceived = true;
265
278
  // 304 Not Modified — cache is still valid; refresh its timestamp.
266
279
  // Re-assert the marker when the cache says suspended, but NEVER clear it
267
280
  // here: a 304 validates the version string, not the (locally editable)
@@ -270,9 +283,13 @@ async function getRuntimeBundle(input) {
270
283
  if (resp.status === 304 && cached) {
271
284
  const validated = { ...cached, fetchedAt: Date.now() };
272
285
  cache[input.shieldId] = validated;
273
- writeCacheFile(cache);
286
+ const persisted = writeCacheFile(cache);
274
287
  if (cached.suspended === true)
275
288
  assertSuspendMarker(input.shieldId);
289
+ if (!persisted && input.requireServerValidation) {
290
+ cacheWriteFailed = true;
291
+ throw new Error('Runtime policy cache could not be saved');
292
+ }
276
293
  return cachedToEffective(validated);
277
294
  }
278
295
  if (resp.status === 401 || resp.status === 403) {
@@ -280,9 +297,23 @@ async function getRuntimeBundle(input) {
280
297
  // not a network one. The two need opposite fixes; code them apart.
281
298
  (0, distress_1.reportDistress)('bundle', distress_1.DISTRESS.AUTH_BROKEN, `bundle fetch HTTP ${resp.status}${input.shieldKey ? '' : ' (no shield key available)'}`);
282
299
  }
300
+ else if (!resp.ok) {
301
+ (0, distress_1.reportDistress)('bundle', distress_1.DISTRESS.BACKEND_REJECTED, `bundle fetch HTTP ${resp.status}`);
302
+ }
283
303
  if (resp.ok) {
284
304
  const body = await resp.json().catch(() => ({}));
285
- if (body.success && body.data && isMode(body.data.mode)) {
305
+ if (body.success === true && body.data && isMode(body.data.mode) && typeof body.data.version === 'string' && body.data.version.trim()) {
306
+ if (cached?.actionPolicies !== undefined && body.data.actionPolicies === undefined) {
307
+ throw new Error('Action policies missing from a previously supported bundle');
308
+ }
309
+ if ((body.data.suspended !== undefined && typeof body.data.suspended !== 'boolean')
310
+ || (body.data.suspended === undefined && (cached?.suspended === true || hasValidSuspendMarker()))) {
311
+ throw new Error('Invalid or missing suspension status');
312
+ }
313
+ if ((body.data.failClosed !== undefined && typeof body.data.failClosed !== 'boolean')
314
+ || (cached?.failClosed !== undefined && body.data.failClosed === undefined)) {
315
+ throw new Error('Invalid or missing offline enforcement setting');
316
+ }
286
317
  const entry = {
287
318
  mode: body.data.mode,
288
319
  version: body.data.version,
@@ -307,27 +338,35 @@ async function getRuntimeBundle(input) {
307
338
  ? { enabled: body.data.honeypot.enabled === true }
308
339
  : undefined,
309
340
  machineAction: body.data.machineAction,
341
+ mcpToolTrust: body.data.mcpToolTrust,
342
+ protectionProfile: body.data.protectionProfile,
310
343
  actionPolicies: sanitizeBundlePolicies(body.data.actionPolicies),
311
- machineRole: sanitizeMachineRole(body.data.machineRole),
344
+ machineRole: sanitizeMachineRole(body.data.machineRole, cached?.machineRole),
312
345
  fetchedAt: Date.now(),
313
346
  };
314
347
  cache[input.shieldId] = entry;
315
- writeCacheFile(cache);
348
+ const persisted = writeCacheFile(cache);
316
349
  // Sync the tamper-resistant marker from the ONLY trustworthy source —
317
350
  // a live server response. Suspended asserts it; not-suspended is the
318
351
  // sole path that clears it.
319
352
  if (entry.suspended)
320
353
  assertSuspendMarker(input.shieldId);
321
- else
354
+ else if (persisted)
322
355
  clearSuspendMarker(input.shieldId);
356
+ if (!persisted && input.requireServerValidation) {
357
+ cacheWriteFailed = true;
358
+ throw new Error('Runtime policy cache could not be saved');
359
+ }
323
360
  const { fetchedAt, revalidateAttemptAt: _attemptAt, ...bundle } = entry;
324
- return { ...bundle, source: 'server', serverValidatedAt: fetchedAt };
361
+ return { ...bundle, suspended: bundle.suspended === true || hasValidSuspendMarker(), source: 'server', serverValidatedAt: fetchedAt };
325
362
  }
363
+ (0, distress_1.reportDistress)('bundle', distress_1.DISTRESS.BACKEND_REJECTED, 'Bundle response did not contain a valid mode and version');
326
364
  }
327
365
  }
328
366
  catch (error) {
329
- // Fetch threw — DNS/proxy/timeout, i.e. the control plane is unreachable.
330
- (0, distress_1.reportDistress)('bundle', distress_1.DISTRESS.NETWORK_DOWN, error instanceof Error ? error.message : String(error));
367
+ // Separate transport failure, invalid server data and local disk failure:
368
+ // each requires a different repair, and reaches fleet diagnostics as such.
369
+ (0, distress_1.reportDistress)('bundle', cacheWriteFailed ? distress_1.DISTRESS.POLICY_CACHE_WRITE_FAILED : responseReceived ? distress_1.DISTRESS.BACKEND_REJECTED : distress_1.DISTRESS.NETWORK_DOWN, error instanceof Error ? error.message : String(error));
331
370
  /* fall through to cache / default */
332
371
  }
333
372
  if (input.requireServerValidation) {
@@ -1,4 +1,5 @@
1
1
  import { type ResolvedCliCredentials } from './config';
2
+ import { type BoundaryVerification } from './boundaryVerification';
2
3
  /**
3
4
  * Deep self-test — the upgraded `health_check` remote action.
4
5
  *
@@ -33,6 +34,7 @@ export interface SelfTestReport {
33
34
  ok: boolean;
34
35
  checks: SelfTestCheck[];
35
36
  ranAt: string;
37
+ boundaryVerification?: BoundaryVerification;
36
38
  }
37
39
  export declare function runDeepSelfTest(input: {
38
40
  creds: ResolvedCliCredentials;
package/dist/selfTest.js CHANGED
@@ -10,6 +10,7 @@ const runtimeConfig_1 = require("./runtimeConfig");
10
10
  const integrity_1 = require("./integrity");
11
11
  const selfUpdate_1 = require("./selfUpdate");
12
12
  const windowsTaskState_1 = require("./windowsTaskState");
13
+ const boundaryVerification_1 = require("./boundaryVerification");
13
14
  /** Windows scheduled-task probe: exists + (when readable) its last result code. */
14
15
  function queryScheduledTask(taskName) {
15
16
  const query = (0, child_process_1.spawnSync)('schtasks', ['/Query', '/TN', taskName, '/V', '/FO', 'LIST'], {
@@ -175,7 +176,14 @@ async function runDeepSelfTest(input) {
175
176
  // PowerShell blocked on a hardened bank machine, or one of the redundant
176
177
  // credential stores unavailable while auth demonstrably works) used to fail
177
178
  // the whole report, training operators to ignore red self-tests entirely.
178
- return { ok: checks.every(check => check.ok || check.optional === true), checks, ranAt: new Date().toISOString() };
179
+ let boundaryVerification;
180
+ try {
181
+ boundaryVerification = await (0, boundaryVerification_1.runBoundaryVerification)();
182
+ }
183
+ catch {
184
+ log('Boundary verification unavailable — checker could not complete.');
185
+ }
186
+ return { ok: checks.every(check => check.ok || check.optional === true), checks, ranAt: new Date().toISOString(), boundaryVerification };
179
187
  }
180
188
  /** FCD per-user tasks that could flash a console window on every trigger. */
181
189
  const WINDOWED_TASK_CANDIDATES = ['FullCourtDefense Daemon', 'FullCourtDefense Daemon Watchdog', 'FullCourtDefense Auto-Protect', 'FullCourtDefenseDesktopDiscover'];
@@ -11,6 +11,8 @@ export interface SessionRoleLimits {
11
11
  }
12
12
  /** Normalize a raw bundle limits object (new or legacy keys) into the split shape. */
13
13
  export declare function normalizeSessionLimits(raw: Record<string, unknown> | undefined | null): SessionRoleLimits;
14
+ /** Validate a delivered limit set before replacing cached enforcement state. */
15
+ export declare function parseDeliveredSessionLimits(raw: unknown, previous?: SessionRoleLimits): SessionRoleLimits;
14
16
  export interface SessionUsage {
15
17
  sessionId: string;
16
18
  dbReads: number;
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.RESPONSE_VOLUME_LIMITS = void 0;
37
37
  exports.normalizeSessionLimits = normalizeSessionLimits;
38
+ exports.parseDeliveredSessionLimits = parseDeliveredSessionLimits;
38
39
  exports.getSessionUsage = getSessionUsage;
39
40
  exports.recordSessionOp = recordSessionOp;
40
41
  exports.recordSessionResponse = recordSessionResponse;
@@ -117,6 +118,29 @@ function normalizeSessionLimits(raw) {
117
118
  }
118
119
  return limits;
119
120
  }
121
+ /** Validate a delivered limit set before replacing cached enforcement state. */
122
+ function parseDeliveredSessionLimits(raw, previous) {
123
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
124
+ throw new Error('Invalid machine role limits');
125
+ const values = raw;
126
+ for (const key of [...LIMIT_KEYS, ...Object.keys(LEGACY_LIMIT_KEY_MAP)]) {
127
+ const value = values[key];
128
+ if (value !== undefined && value !== null
129
+ && !(typeof value === 'number' && Number.isFinite(value) && value >= 0)) {
130
+ throw new Error(`Invalid machine role limit: ${key}`);
131
+ }
132
+ }
133
+ const limits = normalizeSessionLimits(values);
134
+ for (const key of LIMIT_KEYS) {
135
+ // A missing field is not a request to remove a previously finite limit.
136
+ // Explicit null is the supported Unlimited value. Legacy numeric keys
137
+ // remain valid because normalization maps them to their successors first.
138
+ if (typeof previous?.[key] === 'number' && limits[key] === null && values[key] !== null) {
139
+ throw new Error(`Machine role limit missing from bundle: ${key}`);
140
+ }
141
+ }
142
+ return limits;
143
+ }
120
144
  const SESSION_FILE_TTL_MS = 48 * 60 * 60 * 1000;
121
145
  function sessionsDir() {
122
146
  return path.join(os.homedir(), '.fullcourtdefense', 'sessions');
@@ -1,5 +1,10 @@
1
1
  import { type ActionIdentity } from './actionIdentity';
2
2
  export interface SpoolEvent extends ActionIdentity {
3
+ cliVersion?: string;
4
+ detectorRevision?: number;
5
+ detectorSha256?: string;
6
+ /** Loaded module state when recording, not proof this module decided the action. */
7
+ detectorActive?: boolean;
3
8
  agentClient?: string;
4
9
  eventId: string;
5
10
  type: 'verdict';
package/dist/telemetry.js CHANGED
@@ -52,6 +52,8 @@ const path = __importStar(require("path"));
52
52
  const machineIdentity_1 = require("./machineIdentity");
53
53
  const actionIdentity_1 = require("./actionIdentity");
54
54
  const localDetectionUpdates_1 = require("./localDetectionUpdates");
55
+ const cliVersion_1 = require("./cliVersion");
56
+ const installedCliVersion = (0, cliVersion_1.cliVersion)();
55
57
  /**
56
58
  * Local-first telemetry: every enforcement decision is appended to an on-disk
57
59
  * spool (instant, offline-safe) and flushed to the backend in batches. Critical
@@ -194,7 +196,12 @@ function spoolEvent(event) {
194
196
  // took. Absent when a caller spools outside a hook evaluation (guards,
195
197
  // onboard) — the backend treats missing fields as "no sample".
196
198
  const timing = verdictTiming.getStore();
199
+ const detector = localDetectionUpdates_1.localDetectorUpdates.getDetectorStatus();
197
200
  const full = {
201
+ cliVersion: installedCliVersion,
202
+ detectorRevision: detector.revision,
203
+ detectorSha256: detector.sha256,
204
+ detectorActive: detector.active,
198
205
  ...(0, actionIdentity_1.captureActionIdentity)(),
199
206
  ...timing?.identity,
200
207
  eventId: crypto.randomUUID(),
@@ -366,6 +373,7 @@ async function flushSpoolLocked(input) {
366
373
  ? {
367
374
  agentVersion: input.agentVersion,
368
375
  detectionUpdates: localDetectionUpdates_1.localDetectionUpdates.getStatus(),
376
+ detectorUpdates: localDetectionUpdates_1.localDetectorUpdates.getDetectorStatus(),
369
377
  integrityOk: input.integrityOk,
370
378
  integrityReasons: input.integrityReasons,
371
379
  integrityCheckedAt: input.integrityCheckedAt,
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.34.21"
2
+ "version": "1.34.25"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.34.21",
3
+ "version": "1.34.25",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -13,10 +13,13 @@
13
13
  "README.md"
14
14
  ],
15
15
  "scripts": {
16
+ "test:action-benchmark-report": "node scripts/test-action-benchmark-report.js && node scripts/test-action-benchmark-isolation.js",
17
+ "test:boundary-verification": "npm run build && node scripts/test-boundary-verification.js",
18
+ "test:detector-updates": "npm run build && node scripts/test-detector-artifacts.js && node scripts/test-detector-publication.js && node scripts/test-detection-updates.js && node scripts/test-detection-cache.js",
16
19
  "test:native-hooks": "npm run build && node scripts/test-codex-activation.js && node scripts/test-native-hook-launch.js && node scripts/test-native-hook-edges.js && node scripts/test-client-telemetry.js && node scripts/test-native-hook-clients.js",
17
20
  "test:endpoint-approvals": "npm run build && node scripts/audit-endpoint-mcp-shell.js",
18
21
  "test:endpoint-clients": "npm run build && node scripts/test-endpoint-client-matrix.js && node scripts/test-copilot-hook-install.js",
19
- "build": "tsc && node scripts/bundle-policy-engine.js && node scripts/copy-attack-corpus.js",
22
+ "build": "tsc && node scripts/bundle-policy-engine.js && node scripts/copy-attack-corpus.js && node scripts/bundle-containment-runtime.js",
20
23
  "test:deterministic-guard": "npm run build && node scripts/test-deterministic-guard.js",
21
24
  "test:drive-delete-guard": "npm run build && node scripts/test-drive-delete-guard.js",
22
25
  "test:msi-stop-filter": "node scripts/test-msi-stop-filter.js",
@@ -44,7 +47,7 @@
44
47
  "test:user-objective-labels": "npm run build && node scripts/test-user-objective-labels.js",
45
48
  "test:remote-mcp-gateway": "npm run build && node scripts/test-remote-mcp-gateway.js",
46
49
  "test:e2e-onboarding-personas": "npm run build && node scripts/test-e2e-onboarding-personas.js",
47
- "test:onboarding-transaction": "npm run build && node scripts/test-onboarding-transaction.js",
50
+ "test:onboarding-transaction": "npm run build && node scripts/test-onboarding-transaction.js && node scripts/test-onboarding-summary.js",
48
51
  "test:posture-blast": "npm run build && node scripts/test-posture-blast-radius.js",
49
52
  "test:ping-e2e": "npm run build && node scripts/test-ping-e2e.js",
50
53
  "test:daemon-e2e": "npm run build && node scripts/test-daemon-e2e.js",
@@ -58,7 +61,7 @@
58
61
  "test:scan-root-compaction": "npm run build && node scripts/test-scan-root-compaction.js",
59
62
  "test:dpapi-config": "npm run build && node scripts/test-dpapi-config.js",
60
63
  "test:distress-ledger": "npm run build && node scripts/test-distress-ledger.js",
61
- "test:poll-loop": "npm run build && node scripts/test-poll-loop-resilience.js && node scripts/test-background-bundle.js",
64
+ "test:poll-loop": "npm run build && node scripts/test-poll-loop-resilience.js && node scripts/test-background-bundle.js && node scripts/test-policy-refresh.js",
62
65
  "test:slim-hook": "npm run build && node scripts/test-slim-hook.js",
63
66
  "test:killswitch": "npm run build && node scripts/test-killswitch-integrity.js",
64
67
  "test:restart-guarantees": "npm run build && node scripts/test-daemon-restart-guarantees.js",
@@ -80,7 +83,7 @@
80
83
  "test:failopen-killswitch": "npm run build && node scripts/test-failopen-killswitch.js",
81
84
  "test:enroll-cache-reset": "npm run build && node scripts/test-enroll-cache-reset.js",
82
85
  "test:offline-policy": "npm run build && node scripts/test-offline-policy-enforcement.js",
83
- "test:developer-confirmation": "npm run build && node scripts/test-developer-confirmation.js",
86
+ "test:developer-confirmation": "npm run build && node scripts/test-developer-confirmation.js && node scripts/test-approval-scope.js && node scripts/test-approval-prompt.js",
84
87
  "test:local-safety-ask": "npm run build && node scripts/test-local-safety-ask.js",
85
88
  "test:terminal-mcp-ask": "npm run build && node scripts/test-terminal-mcp-ask.js",
86
89
  "test:demo-actions": "npm run build && node scripts/test-demo-actions.js",
@@ -96,7 +99,7 @@
96
99
  "test:ide-fp-corpus:hook": "npm run build && node scripts/test-ide-fp-corpus.js --hook",
97
100
  "test:msi-payload-deps": "npm run build && node scripts/test-msi-payload-deps.js",
98
101
  "build:msi": "powershell -NoProfile -ExecutionPolicy Bypass -File installer/windows/Build-Msi.ps1",
99
- "prepublishOnly": "npm run build && node scripts/check-detection-baseline.js && node scripts/test-detection-updates.js && node scripts/test-detection-cache.js && node scripts/test-msi-payload-deps.js && node scripts/test-shell-parity.js && node scripts/test-terminal-mcp-ask.js"
102
+ "prepublishOnly": "npm run build && node scripts/check-detection-baseline.js && node scripts/check-detector-baseline.js && node scripts/test-detector-artifacts.js && node scripts/test-detector-publication.js && node scripts/test-detection-updates.js && node scripts/test-detection-cache.js && node scripts/test-msi-payload-deps.js && node scripts/test-shell-parity.js && node scripts/test-terminal-mcp-ask.js"
100
103
  },
101
104
  "keywords": [
102
105
  "llm",
@@ -112,6 +115,7 @@
112
115
  ],
113
116
  "license": "MIT",
114
117
  "devDependencies": {
118
+ "@anthropic-ai/sandbox-runtime": "0.0.76",
115
119
  "@types/node": "^25.3.0",
116
120
  "acorn": "8.18.0",
117
121
  "esbuild": "0.28.2",