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
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ToolReviewLease = void 0;
37
+ exports.verifiedToolTrust = verifiedToolTrust;
38
+ exports.readCompleteToolCatalog = readCompleteToolCatalog;
39
+ const machineActionVerify_1 = require("./machineActionVerify");
40
+ const mcpToolTrust_1 = require("./mcpToolTrust");
41
+ const crypto_1 = require("crypto");
42
+ const fs = __importStar(require("fs"));
43
+ const os = __importStar(require("os"));
44
+ const path = __importStar(require("path"));
45
+ /** Retain the signed requirement even when the general bundle ages out offline. */
46
+ class ToolReviewLease {
47
+ machineId;
48
+ shieldId;
49
+ file;
50
+ remembered;
51
+ loaded = false;
52
+ constructor(machineId, shieldId, apiUrl, directory = path.join(os.homedir(), '.fullcourtdefense', 'tool-review')) {
53
+ this.machineId = machineId;
54
+ this.shieldId = shieldId;
55
+ this.file = path.join(directory, (0, crypto_1.createHash)('sha256').update(JSON.stringify([machineId, shieldId, apiUrl])).digest('hex') + '.json');
56
+ }
57
+ settings(candidate) {
58
+ if (candidate)
59
+ verifiedToolTrust(candidate, this.machineId, this.shieldId);
60
+ if (!this.loaded) {
61
+ try {
62
+ const raw = fs.readFileSync(this.file, 'utf8');
63
+ if (raw.length > 64000)
64
+ throw new Error('Tool review cache is invalid.');
65
+ this.remembered = JSON.parse(raw);
66
+ }
67
+ catch (error) {
68
+ if (error.code !== 'ENOENT' && !candidate)
69
+ throw new Error('Tool review cache is unreadable. Reconnect before calling tools.');
70
+ }
71
+ this.loaded = true;
72
+ }
73
+ if (candidate) {
74
+ const serialized = JSON.stringify(candidate);
75
+ if (serialized !== JSON.stringify(this.remembered)) {
76
+ fs.mkdirSync(path.dirname(this.file), { recursive: true, mode: 0o700 });
77
+ const temp = `${this.file}.${(0, crypto_1.randomUUID)()}.tmp`;
78
+ try {
79
+ fs.writeFileSync(temp, serialized, { flag: 'wx', mode: 0o600 });
80
+ fs.renameSync(temp, this.file);
81
+ }
82
+ finally {
83
+ try {
84
+ fs.unlinkSync(temp);
85
+ }
86
+ catch { /* renamed or unavailable */ }
87
+ }
88
+ this.remembered = candidate;
89
+ }
90
+ }
91
+ return this.remembered ? verifiedToolTrust(this.remembered, this.machineId, this.shieldId) : undefined;
92
+ }
93
+ }
94
+ exports.ToolReviewLease = ToolReviewLease;
95
+ function verifiedToolTrust(envelope, machineId, shieldId) {
96
+ const created = Date.parse(envelope.createdAt), expires = Date.parse(envelope.expiresAt);
97
+ if (envelope.type !== 'mcp_tool_trust' || !envelope.organizationId || !envelope.signature || envelope.machineId !== machineId || !Number.isFinite(created) || !Number.isFinite(expires) || created > Date.now() + 60000 || expires <= Date.now() || expires - created > 86400000)
98
+ throw new Error('Tool review settings are missing, expired or invalid. Reconnect before calling tools.');
99
+ const verdict = (0, machineActionVerify_1.verifyMachineAction)(envelope, { localMachineId: machineId });
100
+ if (!verdict.ok || verdict.reason)
101
+ throw new Error('Tool review settings failed signature verification.');
102
+ const settings = JSON.parse(envelope.reason);
103
+ if (!shieldId || settings.shieldId !== shieldId)
104
+ throw new Error('Tool review settings belong to another enrollment.');
105
+ return (0, mcpToolTrust_1.parseToolTrustSettings)(settings);
106
+ }
107
+ /** Preserve every declared field and exhaust pagination before fingerprinting. */
108
+ async function readCompleteToolCatalog(request) {
109
+ const tools = [];
110
+ const cursors = new Set();
111
+ let cursor;
112
+ for (let page = 0; page < 20; page++) {
113
+ const response = await request(cursor ? { cursor } : {});
114
+ if (!response || !Array.isArray(response.tools))
115
+ throw new Error('MCP server returned an invalid tool list.');
116
+ tools.push(...response.tools);
117
+ if (tools.length > 1000)
118
+ throw new Error('MCP catalog exceeds the supported review size.');
119
+ if (response.nextCursor === undefined)
120
+ return tools;
121
+ if (typeof response.nextCursor !== 'string' || !response.nextCursor || cursors.has(response.nextCursor))
122
+ throw new Error('Invalid MCP catalog pagination.');
123
+ cursor = response.nextCursor;
124
+ cursors.add(cursor);
125
+ }
126
+ throw new Error('MCP catalog pagination exceeded the supported limit.');
127
+ }
@@ -0,0 +1,22 @@
1
+ /** Pure, byte-identical CLI contract. Definitions are evidence, never instructions. */
2
+ export interface ToolTrustSettings {
3
+ version: 1;
4
+ enabled: boolean;
5
+ approved: Array<{
6
+ serverId: string;
7
+ catalogHash: string;
8
+ }>;
9
+ }
10
+ export interface ToolCatalog {
11
+ serverId: string;
12
+ serverName: string;
13
+ catalogHash: string;
14
+ tools: Array<Record<string, unknown> & {
15
+ name: string;
16
+ }>;
17
+ }
18
+ export declare function canonicalToolJson(value: unknown): string;
19
+ export declare function toolDigest(value: unknown): string;
20
+ export declare function parseToolCatalog(value: unknown): ToolCatalog;
21
+ export declare function parseToolTrustSettings(value: unknown): ToolTrustSettings;
22
+ export declare function toolCatalogAllowed(settings: ToolTrustSettings, catalog: ToolCatalog, name: string): boolean;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.canonicalToolJson = canonicalToolJson;
4
+ exports.toolDigest = toolDigest;
5
+ exports.parseToolCatalog = parseToolCatalog;
6
+ exports.parseToolTrustSettings = parseToolTrustSettings;
7
+ exports.toolCatalogAllowed = toolCatalogAllowed;
8
+ const crypto_1 = require("crypto");
9
+ function canonicalToolJson(value) {
10
+ let nodes = 0;
11
+ const visit = (item, depth) => {
12
+ if (++nodes > 30000 || depth > 32)
13
+ throw new Error('Tool definition is too complex.');
14
+ if (item === null || typeof item === 'string' || typeof item === 'boolean')
15
+ return item;
16
+ if (typeof item === 'number' && Number.isFinite(item))
17
+ return item;
18
+ if (Array.isArray(item))
19
+ return item.map(v => visit(v, depth + 1));
20
+ if (!item || typeof item !== 'object' || ![Object.prototype, null].includes(Object.getPrototypeOf(item)))
21
+ throw new Error('Invalid tool definition.');
22
+ const output = Object.create(null);
23
+ for (const key of Object.keys(item).sort()) {
24
+ const descriptor = Object.getOwnPropertyDescriptor(item, key);
25
+ if (!('value' in descriptor))
26
+ throw new Error('Invalid tool definition.');
27
+ output[key] = visit(descriptor.value, depth + 1);
28
+ }
29
+ return output;
30
+ };
31
+ const result = JSON.stringify(visit(value, 0));
32
+ if (Buffer.byteLength(result) > 512000)
33
+ throw new Error('Tool catalog is too large.');
34
+ return result;
35
+ }
36
+ function toolDigest(value) { return (0, crypto_1.createHash)('sha256').update(canonicalToolJson(value)).digest('hex'); }
37
+ const hash = (v) => typeof v === 'string' && /^[a-f0-9]{64}$/.test(v);
38
+ function parseToolCatalog(value) {
39
+ const p = value;
40
+ if (!p || !hash(p.serverId) || typeof p.serverName !== 'string' || !p.serverName.trim() || p.serverName.length > 160 || /[\x00-\x1f]/.test(p.serverName) || !Array.isArray(p.tools) || p.tools.length > 1000)
41
+ throw new Error('Invalid tool catalog.');
42
+ const tools = JSON.parse(canonicalToolJson(p.tools));
43
+ const names = new Set();
44
+ for (const tool of tools) {
45
+ if (!tool || typeof tool !== 'object' || typeof tool.name !== 'string' || !tool.name || tool.name.length > 200 || names.has(tool.name))
46
+ throw new Error('Invalid or duplicate tool name.');
47
+ names.add(tool.name);
48
+ }
49
+ tools.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
50
+ return { serverId: p.serverId, serverName: p.serverName, tools, catalogHash: toolDigest(tools) };
51
+ }
52
+ function parseToolTrustSettings(value) {
53
+ const p = value;
54
+ if (!p || p.version !== 1 || typeof p.enabled !== 'boolean' || !Array.isArray(p.approved) || p.approved.length > 100)
55
+ throw new Error('Invalid tool review settings.');
56
+ const seen = new Set();
57
+ for (const entry of p.approved) {
58
+ if (!entry || !hash(entry.serverId) || !hash(entry.catalogHash) || seen.has(entry.serverId))
59
+ throw new Error('Invalid tool approval.');
60
+ seen.add(entry.serverId);
61
+ }
62
+ return { version: 1, enabled: p.enabled, approved: p.approved.map(({ serverId, catalogHash }) => ({ serverId, catalogHash })) };
63
+ }
64
+ function toolCatalogAllowed(settings, catalog, name) {
65
+ return !settings.enabled || (catalog.tools.some(tool => tool.name === name) && settings.approved.some(entry => entry.serverId === catalog.serverId && entry.catalogHash === catalog.catalogHash));
66
+ }
@@ -22,6 +22,10 @@ export declare function nativeErrorVerdict(client: NativeClient, reason: string)
22
22
  * cannot approve by writing `yes` to hook stdin. Re-exported for existing callers.
23
23
  */
24
24
  export { confirmNativeAction };
25
+ /**
26
+ * `confirm` is a test seam only (yes/no). Production leaves it undefined and gets the
27
+ * shared three-button FullCourtDefense dialog through askDialog.ts.
28
+ */
25
29
  export declare function evaluateNativeHook(client: NativeClient, raw: Payload, args: HookArgs, config: BotGuardConfig, confirm?: (reason: string) => Promise<boolean>): Promise<{
26
30
  stdout: string;
27
31
  stderr: string;
@@ -13,8 +13,11 @@ const runtimeConfig_1 = require("./runtimeConfig");
13
13
  const hookIo_1 = require("./hookIo");
14
14
  const devConfirm_1 = require("./devConfirm");
15
15
  Object.defineProperty(exports, "confirmNativeAction", { enumerable: true, get: function () { return devConfirm_1.confirmNativeAction; } });
16
+ const askDialog_1 = require("./askDialog");
16
17
  const telemetry_1 = require("./telemetry");
17
18
  const codexMcpIdentity_1 = require("./codexMcpIdentity");
19
+ /** Inside the 110 s process deadline below, with room for the verdict itself. */
20
+ const NATIVE_DIALOG_TIMEOUT_MS = 90_000;
18
21
  exports.NATIVE_CLIENTS = ['codex', 'gemini_cli', 'windsurf', 'kiro'];
19
22
  function patchToolCalls(raw) {
20
23
  const patch = typeof raw.tool_input === 'string' ? raw.tool_input : String(raw.tool_input?.command || raw.tool_input?.patch || '');
@@ -112,7 +115,11 @@ function nativeErrorVerdict(client, reason) {
112
115
  return { stdout: JSON.stringify({ decision: 'block', reason }), stderr: '', exitCode: 0 };
113
116
  return nativeVerdict(client, true, reason);
114
117
  }
115
- async function evaluateNativeHook(client, raw, args, config, confirm = devConfirm_1.confirmNativeAction) {
118
+ /**
119
+ * `confirm` is a test seam only (yes/no). Production leaves it undefined and gets the
120
+ * shared three-button FullCourtDefense dialog through askDialog.ts.
121
+ */
122
+ async function evaluateNativeHook(client, raw, args, config, confirm) {
116
123
  if (client === 'codex' && raw.tool_name === 'apply_patch') {
117
124
  try {
118
125
  // Evaluate every file operation before Codex executes the atomic patch.
@@ -141,7 +148,18 @@ async function evaluateNativeHook(client, raw, args, config, confirm = devConfir
141
148
  return nativeErrorVerdict(client, `FullCourtDefense could not parse the hook: ${String(error)}`);
142
149
  }
143
150
  return (0, telemetry_1.markVerdictTiming)({ startedAt: Date.now(), path: 'local', agentClient: client }, async () => {
144
- const result = await (0, hook_1.evaluateHookRequest)({ ...args, agentClient: client }, JSON.stringify(payload), config);
151
+ const evaluated = await (0, hook_1.evaluateHookRequest)({ ...args, agentClient: client }, JSON.stringify(payload), config);
152
+ // The three-button FullCourtDefense dialog (askDialog.ts) — the same one Cursor
153
+ // and Claude Code get. A test-injected `confirm` keeps the legacy yes/no shape.
154
+ const result = await (0, askDialog_1.resolveAskWithDialog)(evaluated, {
155
+ clientLabel: client,
156
+ dialog: confirm ? async (reason, timeoutMs) => {
157
+ const started = Date.now();
158
+ const ok = await confirm(reason);
159
+ return { outcome: ok ? 'allow' : 'deny', latencyMs: Date.now() - started };
160
+ } : undefined,
161
+ ...(confirm ? {} : { timeoutMs: NATIVE_DIALOG_TIMEOUT_MS }),
162
+ });
145
163
  let decision;
146
164
  try {
147
165
  decision = result.stdout ? JSON.parse(result.stdout) : {};
@@ -153,26 +171,22 @@ async function evaluateNativeHook(client, raw, args, config, confirm = devConfir
153
171
  return nativeErrorVerdict(client, 'Invalid FullCourtDefense verdict shape; action denied.');
154
172
  const permission = decision.hookSpecificOutput?.permissionDecision || decision.permissionDecision || decision.permission;
155
173
  let denied = result.exitCode !== 0 || permission === 'deny' || decision.decision === 'block' || decision.continue === false;
156
- const reason = decision.hookSpecificOutput?.permissionDecisionReason || decision.permissionDecisionReason || decision.reason || decision.user_message || result.stderr || 'FullCourtDefense policy decision.';
174
+ let reason = decision.hookSpecificOutput?.permissionDecisionReason || decision.permissionDecisionReason || decision.reason || decision.user_message || result.stderr || 'FullCourtDefense policy decision.';
157
175
  if (permission === 'ask' && !denied) {
176
+ // No dialog could be shown (headless, binary missing) or the org routes asks to
177
+ // the IDE — these clients have no ask card of their own, so this is a deny.
158
178
  const mapped = (0, hook_1.normalizeClaudePayload)(payload);
159
179
  const call = mapped && mapped.event !== 'ignore' ? (0, hook_1.buildToolCall)(mapped.event, mapped.payload) : null;
160
- // No remembered/pending handoff remains when an external dialog is used.
180
+ // No pending handoff remains: nothing will settle it.
161
181
  if (call && mapped)
162
182
  (0, devConfirm_1.rejectDeveloperConfirmation)(mapped.event, call.toolName, (0, devConfirm_1.actionDigest)(call.toolArgs));
163
- let allowed = false;
164
- try {
165
- allowed = (0, devConfirm_1.isHumanPresent)() && await confirm(`${client}: ${payload.tool_name}\n${reason}`);
166
- }
167
- catch { /* deny */ }
168
- denied = !allowed;
169
- // Hand the click to the MCP gateway that will see this exact call next, so the
170
- // developer is not asked twice for one action (single-use, 2-minute, exact digest).
171
- if (allowed && call && mapped?.event === 'mcp')
172
- (0, devConfirm_1.recordConfirmedAction)(call.toolName, (0, devConfirm_1.actionDigest)(call.toolArgs));
173
- (0, telemetry_1.spoolEvent)({ decision: allowed ? 'allow' : 'block', toolName: call?.toolName || payload.tool_name,
174
- reason: allowed ? 'Developer approved once in the FullCourtDefense dialog.' : 'Developer declined, confirmation unavailable, or timed out.',
175
- approvalScope: 'developer', ...(allowed ? { approvedBy: 'developer' } : {}) });
183
+ denied = true;
184
+ const why = (0, devConfirm_1.isHumanPresent)()
185
+ ? 'Developer confirmation could not be shown (no dialog available on this machine).'
186
+ : 'Developer confirmation requires a person at the keyboard; none is present.';
187
+ (0, telemetry_1.spoolEvent)({ decision: 'block', toolName: call?.toolName || payload.tool_name, reason: why, approvalScope: 'developer' });
188
+ // The ask text ends in a question the client cannot put to anyone; say why it was not run.
189
+ reason = `${reason}\nFullCourtDefense: ${why} The action was not run.`;
176
190
  }
177
191
  // Prompt blocks use the documented exit-code contract, not a PreToolUse object.
178
192
  if (payload.hook_event_name === 'UserPromptSubmit' && denied)
@@ -0,0 +1,3 @@
1
+ import { type FetchBundleInput, type EffectiveBundle } from './runtimeConfig';
2
+ /** A remote refresh succeeds only after both policy caches have a verified replacement. */
3
+ export declare function refreshMachinePolicies(input: FetchBundleInput): Promise<EffectiveBundle>;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.refreshMachinePolicies = refreshMachinePolicies;
4
+ const runtimeConfig_1 = require("./runtimeConfig");
5
+ const localSafetySnapshot_1 = require("./localSafetySnapshot");
6
+ const distress_1 = require("./distress");
7
+ /** A remote refresh succeeds only after both policy caches have a verified replacement. */
8
+ async function refreshMachinePolicies(input) {
9
+ const bundle = await (0, runtimeConfig_1.getRuntimeBundle)({ ...input, force: true, requireServerValidation: true });
10
+ if (!bundle.policyHash)
11
+ throw new Error('The policy bundle did not include a hash for shell policy verification');
12
+ const identity = (0, localSafetySnapshot_1.hookSnapshotIdentity)();
13
+ const snapshot = await (0, localSafetySnapshot_1.loadLocalSafetySnapshot)({
14
+ apiUrl: input.apiUrl, shieldId: input.shieldId, shieldKey: input.shieldKey,
15
+ ...identity, expectedPolicyHash: bundle.policyHash, timeoutMs: input.timeoutMs,
16
+ requireServerValidation: true,
17
+ });
18
+ // Keep the new cache readable throughout invalidation. Older gateway keys
19
+ // then resolve the fresh snapshot instead of a removed or stale replacement.
20
+ if (!snapshot)
21
+ throw new Error('Shell policy refresh did not return a verified snapshot');
22
+ if (!(0, localSafetySnapshot_1.clearAllLocalSafetySnapshotCaches)(snapshot.policyHash)) {
23
+ throw new Error('Policy refresh could not remove stale shell policy caches; check file permissions and retry');
24
+ }
25
+ (0, distress_1.clearDistress)(distress_1.DISTRESS.POLICY_CACHE_WRITE_FAILED, 'shell_policy');
26
+ return bundle;
27
+ }
@@ -0,0 +1,10 @@
1
+ export type ConfigurationSnapshot = Map<string, string>;
2
+ /** Content, absence and unreadability are distinct. Never retain config values. */
3
+ export declare function captureProtectionConfiguration(files: Iterable<string>): ConfigurationSnapshot;
4
+ export declare function changedProtectionConfiguration(before: ConfigurationSnapshot, after: ConfigurationSnapshot): string[];
5
+ export type ProtectionNotice = 'attention' | 'restored';
6
+ /** Routine success is silent. One warning per unresolved incident, then recovery. */
7
+ export declare function protectionNoticeTransition(attentionShown: boolean, healthy: boolean, quiet: boolean): {
8
+ attentionShown: boolean;
9
+ notice?: ProtectionNotice;
10
+ };
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.captureProtectionConfiguration = captureProtectionConfiguration;
37
+ exports.changedProtectionConfiguration = changedProtectionConfiguration;
38
+ exports.protectionNoticeTransition = protectionNoticeTransition;
39
+ const crypto = __importStar(require("crypto"));
40
+ const fs = __importStar(require("fs"));
41
+ /** Content, absence and unreadability are distinct. Never retain config values. */
42
+ function captureProtectionConfiguration(files) {
43
+ const result = new Map();
44
+ for (const file of files) {
45
+ try {
46
+ result.set(file, crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex'));
47
+ }
48
+ catch (error) {
49
+ result.set(file, `unreadable:${error.code || 'unknown'}`);
50
+ }
51
+ }
52
+ return result;
53
+ }
54
+ function changedProtectionConfiguration(before, after) {
55
+ return [...after].filter(([file, fingerprint]) => before.get(file) !== fingerprint).map(([file]) => file);
56
+ }
57
+ /** Routine success is silent. One warning per unresolved incident, then recovery. */
58
+ function protectionNoticeTransition(attentionShown, healthy, quiet) {
59
+ if (healthy)
60
+ return { attentionShown: false, notice: attentionShown && !quiet ? 'restored' : undefined };
61
+ if (quiet || attentionShown)
62
+ return { attentionShown };
63
+ return { attentionShown: true, notice: 'attention' };
64
+ }
@@ -0,0 +1,26 @@
1
+ /** Pure contract. Vendored into the CLI; the sync test prevents drift. */
2
+ export interface ProtectionProfile {
3
+ version: 1;
4
+ name: string;
5
+ platform: 'win32' | 'linux' | 'darwin';
6
+ workspace: string;
7
+ writableFolders: string[];
8
+ blockedFolders: string[];
9
+ allowedSites: string[];
10
+ blockedSites: string[];
11
+ }
12
+ export declare function parseProtectionProfile(value: unknown): ProtectionProfile;
13
+ export declare function protectionRuntimeSettings(profile: ProtectionProfile): {
14
+ filesystem: {
15
+ allowRead: string[];
16
+ denyRead: string[];
17
+ allowWrite: string[];
18
+ denyWrite: string[];
19
+ };
20
+ network: {
21
+ allowedDomains: string[];
22
+ deniedDomains: string[];
23
+ };
24
+ enableWeakerNestedSandbox: boolean;
25
+ enableWeakerNetworkIsolation: boolean;
26
+ };
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseProtectionProfile = parseProtectionProfile;
4
+ exports.protectionRuntimeSettings = protectionRuntimeSettings;
5
+ function parseProtectionProfile(value) {
6
+ if (!value || typeof value !== 'object' || Array.isArray(value))
7
+ throw new Error('A protection profile is required.');
8
+ const p = value;
9
+ const keys = ['version', 'name', 'platform', 'workspace', 'writableFolders', 'blockedFolders', 'allowedSites', 'blockedSites'];
10
+ if (Object.keys(p).some(key => !keys.includes(key)))
11
+ throw new Error('Unknown profile setting.');
12
+ if (p.version !== 1 || !['win32', 'linux', 'darwin'].includes(String(p.platform)))
13
+ throw new Error('Choose the computer operating system.');
14
+ if (typeof p.name !== 'string' || !p.name.trim() || p.name.length > 80 || /[\x00-\x1f]/.test(p.name))
15
+ throw new Error('Enter a profile name (1–80 characters).');
16
+ const folder = (v) => {
17
+ if (typeof v !== 'string' || v.length > 500 || /[\x00-\x1f*?<>"|$`%]/.test(v))
18
+ throw new Error('Use a full local folder path without wildcards or variables.');
19
+ const windows = p.platform === 'win32';
20
+ if (windows ? !/^[A-Za-z]:[\\/]/.test(v) || v.slice(2).includes(':') : !v.startsWith('/') || v.startsWith('//'))
21
+ throw new Error('Use a full local folder path for this computer.');
22
+ const normalized = windows ? v.replace(/\\/g, '/') : v;
23
+ if (normalized.split('/').some(part => part === '.' || part === '..'))
24
+ throw new Error('Folder paths cannot contain . or .. segments.');
25
+ if (windows && normalized.split('/').slice(1).some(part => /[. ]$/.test(part)))
26
+ throw new Error('Windows folder names cannot end with a dot or space.');
27
+ if (/^[A-Za-z]:\/$/.test(normalized))
28
+ return normalized;
29
+ return normalized.replace(/\/+$/, '') || '/';
30
+ };
31
+ const site = (v) => {
32
+ if (typeof v !== 'string' || v.length > 253 || !/^(?:\*\.)?[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?$/.test(v))
33
+ throw new Error('Enter a hostname such as example.com or *.example.com, without a URL or port.');
34
+ const host = v.replace(/^\*\./, '');
35
+ if (host.split('.').some(part => !part || part.length > 63 || part.startsWith('-') || part.endsWith('-')))
36
+ throw new Error('Enter a valid hostname.');
37
+ return v.toLowerCase();
38
+ };
39
+ const list = (key, parse) => {
40
+ const values = p[key];
41
+ if (!Array.isArray(values) || values.length > 50)
42
+ throw new Error(`Choose at most 50 entries for ${key}.`);
43
+ return [...new Set(values.map(parse))].sort();
44
+ };
45
+ const result = { version: 1, name: p.name.trim(), platform: p.platform, workspace: folder(p.workspace),
46
+ writableFolders: list('writableFolders', folder), blockedFolders: list('blockedFolders', folder), allowedSites: list('allowedSites', site), blockedSites: list('blockedSites', site) };
47
+ const compare = (v) => result.platform === 'win32' ? v.toLowerCase() : v;
48
+ const within = (child, parent) => compare(child) === compare(parent) || compare(child).startsWith(compare(parent).replace(/\/$/, '') + '/');
49
+ if (result.blockedFolders.some(blocked => within(result.workspace, blocked)))
50
+ throw new Error('The working folder cannot be inside a blocked folder.');
51
+ if (result.writableFolders.some(allowed => result.blockedFolders.some(blocked => within(allowed, blocked))))
52
+ throw new Error('A writable folder cannot be inside a blocked folder.');
53
+ return result;
54
+ }
55
+ function protectionRuntimeSettings(profile) {
56
+ const p = parseProtectionProfile(profile);
57
+ const native = (paths) => p.platform === 'win32' ? paths.map(value => value.replace(/\//g, '\\')) : paths;
58
+ return {
59
+ filesystem: { allowRead: [], denyRead: native(p.blockedFolders), allowWrite: native(p.writableFolders), denyWrite: native(p.blockedFolders) },
60
+ network: { allowedDomains: p.allowedSites, deniedDomains: p.blockedSites },
61
+ enableWeakerNestedSandbox: false, enableWeakerNetworkIsolation: false,
62
+ };
63
+ }
@@ -16,6 +16,8 @@ import type { EngineActionPolicy } from './actionPolicyEngine';
16
16
  */
17
17
  export type ShieldMode = 'block' | 'monitor' | 'shadow';
18
18
  export interface RuntimeBundle {
19
+ mcpToolTrust?: import('./machineActionVerify').VerifiableMachineAction | null;
20
+ protectionProfile?: import('./machineActionVerify').VerifiableMachineAction | null;
19
21
  mode: ShieldMode;
20
22
  version: string;
21
23
  policyHash?: string;