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,9 @@
1
+ import type { DetectorArtifact } from './detectorArtifact';
2
+ import { type DetectorFacts } from './detectorRuntime';
3
+ /** Authenticated first-party code only. This is not a hostile-code sandbox:
4
+ * signatures/review are the trust boundary. Separate V8 heap, bounded output
5
+ * and wall time contain ordinary detector bugs. Tool text remains JSON data.
6
+ */
7
+ export declare function compileDetector(artifact: DetectorArtifact): ((tool: string, args: Record<string, any>) => DetectorFacts) & {
8
+ dispose(): void;
9
+ };
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compileDetector = compileDetector;
4
+ const worker_threads_1 = require("worker_threads");
5
+ const detectorRuntime_1 = require("./detectorRuntime");
6
+ const detectionData_1 = require("./detectionData");
7
+ /** Authenticated first-party code only. This is not a hostile-code sandbox:
8
+ * signatures/review are the trust boundary. Separate V8 heap, bounded output
9
+ * and wall time contain ordinary detector bugs. Tool text remains JSON data.
10
+ */
11
+ function compileDetector(artifact) {
12
+ const readyBuffer = new SharedArrayBuffer(8);
13
+ const ready = new Int32Array(readyBuffer);
14
+ const worker = new worker_threads_1.Worker(`
15
+ const {workerData,parentPort}=require('worker_threads');
16
+ const {Script,createContext}=require('vm');
17
+ const ready=new Int32Array(workerData.ready);
18
+ let context,evaluate;
19
+ try {
20
+ context=createContext(Object.create(null),{codeGeneration:{strings:false,wasm:false}});
21
+ // Expose parsed scalar fields, not Node's host URL constructor or native
22
+ // exceptions. The wrapper and all values visible to code belong to the VM.
23
+ const makeUrl=new Script('(parse => class URL { constructor(input, base) { const fields=JSON.parse(parse(String(input), base===undefined?undefined:String(base))); if(fields.error)throw new TypeError("Invalid URL"); Object.assign(this,fields); } toString(){return this.href;} })').runInContext(context,{timeout:1000});
24
+ context.URL=makeUrl((input,base)=>{
25
+ try {
26
+ if(typeof input!=='string'||input.length>256*1024||(base!==undefined&&(typeof base!=='string'||base.length>256*1024)))return '{"error":true}';
27
+ const value=new URL(input,base),fields={};
28
+ for(const key of ['href','origin','protocol','username','password','host','hostname','port','pathname','search','hash'])fields[key]=value[key];
29
+ return JSON.stringify(fields);
30
+ } catch { return '{"error":true}'; }
31
+ });
32
+ new Script(workerData.code).runInContext(context,{timeout:1000});
33
+ new Script('if(typeof FcdDetector?.inferToolContext!=="function")throw new Error("Missing export")').runInContext(context,{timeout:1000});
34
+ evaluate=new Script('JSON.stringify(FcdDetector.inferToolContext(...JSON.parse(inputJson)))');
35
+ Atomics.store(ready,0,1);
36
+ } catch { Atomics.store(ready,0,-1); }
37
+ Atomics.notify(ready,0);
38
+ parentPort.on('message',({input,buffer})=>{
39
+ const state=new Int32Array(buffer,0,2),output=new Uint8Array(buffer,8);
40
+ try {
41
+ context.inputJson=input;
42
+ const text=evaluate.runInContext(context,{timeout:250});
43
+ if(typeof text!=='string')throw new Error('Invalid output');
44
+ const bytes=Buffer.from(text);
45
+ if(bytes.length>output.length)throw new Error('Output too large');
46
+ output.set(bytes); Atomics.store(state,1,bytes.length); Atomics.store(state,0,1);
47
+ } catch { Atomics.store(state,0,-1); }
48
+ finally { if(context)delete context.inputJson; Atomics.notify(state,0); }
49
+ });
50
+ `, { eval: true, workerData: { code: artifact.code, ready: readyBuffer },
51
+ resourceLimits: { maxOldGenerationSizeMb: 48, maxYoungGenerationSizeMb: 8, stackSizeMb: 4 } });
52
+ let stopped = false;
53
+ const dispose = () => { if (!stopped) {
54
+ stopped = true;
55
+ void worker.terminate();
56
+ } };
57
+ worker.on('error', () => { stopped = true; });
58
+ worker.on('exit', () => { stopped = true; });
59
+ worker.unref();
60
+ Atomics.wait(ready, 0, 0, 4000);
61
+ if (Atomics.load(ready, 0) !== 1) {
62
+ dispose();
63
+ throw new Error('Detector initialization failed');
64
+ }
65
+ const invoke = (tool, args) => {
66
+ if (stopped)
67
+ throw new detectorRuntime_1.DetectorWorkerUnavailable('Detector worker unavailable');
68
+ const input = JSON.stringify([tool, args, (0, detectionData_1.getDetectionData)()]);
69
+ if (Buffer.byteLength(input) > 256 * 1024)
70
+ throw new Error('Detector input exceeds bound');
71
+ const buffer = new SharedArrayBuffer(512 * 1024 + 8);
72
+ const state = new Int32Array(buffer, 0, 2);
73
+ worker.postMessage({ input, buffer });
74
+ // VM deadlines count wall time, including time descheduled by Windows.
75
+ // Allow scheduling headroom while retaining a hard half-second fallback.
76
+ Atomics.wait(state, 0, 0, 500);
77
+ if (Atomics.load(state, 0) !== 1) {
78
+ dispose();
79
+ throw new detectorRuntime_1.DetectorWorkerUnavailable('Detector timed out or failed');
80
+ }
81
+ return JSON.parse(Buffer.from(new Uint8Array(buffer, 8, Atomics.load(state, 1))).toString('utf8'));
82
+ };
83
+ return Object.assign(invoke, { dispose });
84
+ }
@@ -0,0 +1,21 @@
1
+ /** In-memory detector contract. No downloads, file reads or model calls here. */
2
+ export interface DetectorFacts {
3
+ operation: string;
4
+ context: Record<string, string>;
5
+ }
6
+ /** A terminated worker cannot recover on the next action. */
7
+ export declare class DetectorWorkerUnavailable extends Error {
8
+ }
9
+ type Detector = ((tool: string, args: Record<string, any>) => DetectorFacts) & {
10
+ dispose?(): void;
11
+ };
12
+ export declare function detectorRuntimeStatus(): {
13
+ revision: number;
14
+ failures: number;
15
+ active: boolean;
16
+ };
17
+ export declare function installDetector(next: Detector, nextRevision: number): void;
18
+ export declare function updatedDetectorFacts(tool: string, args: Record<string, any>): DetectorFacts | undefined;
19
+ /** Shared validation for smoke tests and every actual inference result. */
20
+ export declare function validateDetectorFacts(result: DetectorFacts): DetectorFacts;
21
+ export {};
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DetectorWorkerUnavailable = void 0;
4
+ exports.detectorRuntimeStatus = detectorRuntimeStatus;
5
+ exports.installDetector = installDetector;
6
+ exports.updatedDetectorFacts = updatedDetectorFacts;
7
+ exports.validateDetectorFacts = validateDetectorFacts;
8
+ /** A terminated worker cannot recover on the next action. */
9
+ class DetectorWorkerUnavailable extends Error {
10
+ }
11
+ exports.DetectorWorkerUnavailable = DetectorWorkerUnavailable;
12
+ let detector;
13
+ let revision = 0;
14
+ let failures = 0;
15
+ function detectorRuntimeStatus() { return { revision, failures, active: Boolean(detector) }; }
16
+ function installDetector(next, nextRevision) {
17
+ if (!Number.isSafeInteger(nextRevision) || nextRevision <= revision)
18
+ throw new Error('Detector revision must increase');
19
+ detector?.dispose?.();
20
+ detector = next;
21
+ revision = nextRevision;
22
+ failures = 0;
23
+ }
24
+ function updatedDetectorFacts(tool, args) {
25
+ if (!detector)
26
+ return undefined;
27
+ try {
28
+ return validateDetectorFacts(detector(tool, args));
29
+ }
30
+ catch (error) {
31
+ failures++;
32
+ if (error instanceof DetectorWorkerUnavailable || failures >= 3) {
33
+ detector?.dispose?.();
34
+ detector = undefined;
35
+ }
36
+ return undefined;
37
+ }
38
+ }
39
+ /** Shared validation for smoke tests and every actual inference result. */
40
+ function validateDetectorFacts(result) {
41
+ if (!result || typeof result.operation !== 'string' || !result.operation || result.operation.length > 100
42
+ || !result.context || typeof result.context !== 'object' || Array.isArray(result.context))
43
+ throw new Error('Invalid detector result');
44
+ const entries = Object.entries(result.context);
45
+ if (entries.length > 200 || entries.some(([key, value]) => key.length > 160 || typeof value !== 'string' || value.length > 100_000
46
+ || /^(?:__proto__|constructor|prototype|verdict|decision|approval)$/i.test(key)))
47
+ throw new Error('Invalid detector facts');
48
+ return { operation: result.operation, context: Object.fromEntries(entries) };
49
+ }
@@ -0,0 +1,21 @@
1
+ import { DetectionUpdates } from './detectionUpdates';
2
+ import { type DetectorArtifact } from './detectorArtifact';
3
+ type Snapshot = DetectorArtifact | {
4
+ revision: 0;
5
+ };
6
+ export declare class DetectorUpdates extends DetectionUpdates<Snapshot> {
7
+ constructor(cachePath: string, publicKey?: string, channel?: 'stable' | 'staging', machineId?: string);
8
+ getDetectorStatus(): {
9
+ active: boolean;
10
+ failures: number;
11
+ baselineRejected: boolean;
12
+ sha256: string | undefined;
13
+ sourceCommit: string | undefined;
14
+ revision: number;
15
+ bundledRevision: number;
16
+ source: "bundled" | "update";
17
+ lastCheckedAt?: string;
18
+ state: "bundled" | "current" | "unavailable" | "rejected" | "not_targeted";
19
+ };
20
+ }
21
+ export {};
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DetectorUpdates = void 0;
4
+ const detectionUpdates_1 = require("./detectionUpdates");
5
+ const detectorArtifact_1 = require("./detectorArtifact");
6
+ const detectorModule_1 = require("./detectorModule");
7
+ const detectorRuntime_1 = require("./detectorRuntime");
8
+ const detectorBaseline_1 = require("./detectorBaseline");
9
+ let current = { revision: 0 };
10
+ let baselineRejected = false;
11
+ /** Compile once: the smoke-tested worker is the worker adopted after persistence. */
12
+ function prepareDetector(data) {
13
+ if (!('code' in data))
14
+ throw new Error('Missing detector code');
15
+ const infer = (0, detectorModule_1.compileDetector)(data);
16
+ let adopted = false;
17
+ try {
18
+ (0, detectorRuntime_1.validateDetectorFacts)(infer('read_file', { path: 'sample.txt' }));
19
+ }
20
+ catch (error) {
21
+ infer.dispose();
22
+ throw error;
23
+ }
24
+ return {
25
+ activate() {
26
+ if (data.revision <= current.revision)
27
+ return false;
28
+ (0, detectorRuntime_1.installDetector)(infer, data.revision);
29
+ adopted = true;
30
+ current = Object.freeze({ ...data });
31
+ return true;
32
+ },
33
+ dispose() { if (!adopted)
34
+ infer.dispose(); },
35
+ };
36
+ }
37
+ if (detectorBaseline_1.BUNDLED_DETECTOR_ENVELOPE) {
38
+ try {
39
+ const issuedAt = JSON.parse(JSON.parse(detectorBaseline_1.BUNDLED_DETECTOR_ENVELOPE).payload).issuedAt;
40
+ const bundled = (0, detectorArtifact_1.verifyDetectorArtifact)(detectorBaseline_1.BUNDLED_DETECTOR_ENVELOPE, Math.min(Date.now(), issuedAt));
41
+ if (bundled.machineIds !== null)
42
+ throw new Error('CLI baseline must be approved for the full fleet');
43
+ const prepared = prepareDetector(bundled);
44
+ try {
45
+ prepared.activate();
46
+ }
47
+ finally {
48
+ prepared.dispose();
49
+ }
50
+ }
51
+ catch {
52
+ baselineRejected = true;
53
+ } // Preserve compiled-in inference and CLI startup.
54
+ }
55
+ const bundledRevision = current.revision;
56
+ const adapter = {
57
+ url: 'https://storage.googleapis.com/fullcourtdefense-cli-releases/detectors/v1/stable.json',
58
+ maxBytes: detectorArtifact_1.MAX_DETECTOR_BYTES, bundledRevision,
59
+ current: () => current,
60
+ verify: detectorArtifact_1.verifyDetectorArtifact,
61
+ prepare: prepareDetector,
62
+ activate: data => {
63
+ if (data.revision <= current.revision)
64
+ return false;
65
+ const prepared = prepareDetector(data);
66
+ try {
67
+ return prepared.activate();
68
+ }
69
+ finally {
70
+ prepared.dispose();
71
+ }
72
+ },
73
+ };
74
+ class DetectorUpdates extends detectionUpdates_1.DetectionUpdates {
75
+ constructor(cachePath, publicKey, channel = 'stable', machineId) {
76
+ super(cachePath, publicKey, channel, { ...adapter,
77
+ eligible: data => !('machineIds' in data) || data.machineIds === null || Boolean(machineId && data.machineIds.includes(machineId)) });
78
+ }
79
+ getDetectorStatus() {
80
+ const { active, failures } = (0, detectorRuntime_1.detectorRuntimeStatus)();
81
+ return { ...this.getStatus(), active, failures, baselineRejected,
82
+ sha256: 'sha256' in current ? current.sha256 : undefined,
83
+ sourceCommit: 'sourceCommit' in current ? current.sourceCommit : undefined };
84
+ }
85
+ }
86
+ exports.DetectorUpdates = DetectorUpdates;
@@ -23,11 +23,25 @@ export declare function confirmNativeAction(reason: string, timeoutMs?: number):
23
23
  * How the OS dialog ended. `unavailable` = no dialog could be shown at all (no human,
24
24
  * no display, dialog binary missing / failed to start) — the caller may try another
25
25
  * channel. `deny` covers Deny, closing the window and the dialog's own give-up.
26
+ * `allow_always` = the person pressed "Always allow"; the CALLER decides what that
27
+ * covers (a scoped grant, see `recordDeveloperGrant`) — the dialog itself grants nothing.
26
28
  */
27
- export type NativeDialogOutcome = 'allow' | 'deny' | 'timeout' | 'unavailable';
29
+ export type NativeDialogOutcome = 'allow' | 'allow_always' | 'deny' | 'timeout' | 'unavailable';
30
+ /** Button labels — one vocabulary on every platform and in every test. */
31
+ export declare const DIALOG_BUTTON_ALWAYS = "Always allow";
32
+ export declare const DIALOG_BUTTON_ONCE = "Allow once";
33
+ export declare const DIALOG_BUTTON_DENY = "Deny";
34
+ /** Marker inside the Windows dialog script; tests recognize the spawn by it. */
35
+ export declare const DIALOG_SCRIPT_MARKER = "FullCourtDefense.Dialog";
28
36
  /** True when this process can put a dialog in front of a person on this machine. */
29
37
  export declare function nativeDialogAvailable(): boolean;
30
- export declare function confirmNativeActionDetailed(reason: string, timeoutMs?: number): Promise<{
38
+ export interface NativeDialogOptions {
39
+ /** Offer the third button. Off when the rule forbids memory (`remember: none`). */
40
+ allowAlways?: boolean;
41
+ /** What "Always allow" would cover, shown under the button so the click is informed. */
42
+ alwaysScopeLabel?: string;
43
+ }
44
+ export declare function confirmNativeActionDetailed(reason: string, timeoutMs?: number, options?: NativeDialogOptions): Promise<{
31
45
  outcome: NativeDialogOutcome;
32
46
  latencyMs: number;
33
47
  }>;
@@ -41,6 +55,8 @@ export interface PendingConfirmation {
41
55
  argsDigest: string;
42
56
  policyId?: string;
43
57
  policyName?: string;
58
+ policyHash?: string;
59
+ authorizationContext?: string;
44
60
  matchedRule?: string;
45
61
  sessionId?: string;
46
62
  remember: ConfirmRemember;
@@ -49,11 +65,7 @@ export interface PendingConfirmation {
49
65
  permissionHandoff?: boolean;
50
66
  }
51
67
  export interface RememberedConfirmation {
52
- /**
53
- * sha256(toolName|rule) — remembered per tool + the policy rule that asked (falls back to the
54
- * inferred operation when the rule is anonymous). NOT per exact command: `remember: session`
55
- * means "this rule, this tool, this session", exactly what the org chose.
56
- */
68
+ /** Exact action, policy snapshot and execution context; never a broad tool grant. */
57
69
  key: string;
58
70
  toolName: string;
59
71
  operation: string;
@@ -70,17 +82,57 @@ export interface RememberedConfirmation {
70
82
  * later consumes it instead of asking a second time. Single-use, short-lived, exact-match.
71
83
  */
72
84
  export interface ConfirmedAction {
73
- /** sha256(bareToolName|argsDigest). */
85
+ /** sha256(qualifiedToolIdentity|argsDigest). */
74
86
  key: string;
75
87
  toolName: string;
76
88
  argsDigest: string;
77
89
  confirmedAt: string;
78
90
  expiresAt: string;
79
91
  }
92
+ /**
93
+ * WHAT an "Always allow" covers. Structural, parsed from the action — never the
94
+ * command text or a pattern over it: the programs a shell line runs (all of them,
95
+ * so `git push && gh pr create` and `gh pr view` are different sets), the MCP
96
+ * server + tool, the operation the engine derived, and the destination host when
97
+ * the action reaches one. A grant for `gh` writing to `github.com` never covers
98
+ * `curl` writing to `github.com`, nor `gh` writing to `gitlab.com`.
99
+ */
100
+ export interface GrantScope {
101
+ /** Hook event: shell | mcp | file | read | prompt. */
102
+ event: string;
103
+ /** Shell: the IDE tool (Bash / shell). MCP: `mcp__<server>__<tool>`. Files: the tool. */
104
+ toolName: string;
105
+ /** Shell only: sorted, de-duplicated leading programs of every segment. */
106
+ programs?: string[];
107
+ /** Engine-derived operation (read / write / SHELL / DELETE / …). */
108
+ operation: string;
109
+ /** Host the action reaches (engine `destination.domain`), when any. */
110
+ destinationDomain?: string;
111
+ }
112
+ /**
113
+ * A developer's standing "Always allow" for one scope under one rule. Bound to the
114
+ * policy hash: when the org changes the rule, every grant under it is void and the
115
+ * developer is asked again. Expires on its own; listed and revocable on the machine.
116
+ */
117
+ export interface DeveloperGrant {
118
+ /** sha256(scope + rule identity). */
119
+ key: string;
120
+ scope: GrantScope;
121
+ policyId?: string;
122
+ policyName?: string;
123
+ matchedRule?: string;
124
+ policyHash: string;
125
+ grantedAt: string;
126
+ expiresAt: string;
127
+ lastUsedAt?: string;
128
+ uses: number;
129
+ }
80
130
  /** A raised confirmation that received no post-execution signal in time counts as not approved. */
81
131
  export declare const CONFIRM_PENDING_TTL_MS: number;
82
132
  /** The hook's dialog answer only carries over to the gateway call it was given for. */
83
133
  export declare const CONFIRMED_ACTION_TTL_MS: number;
134
+ /** "Always" is long, not forever: a standing grant lapses after 30 days unless renewed by use of the dialog. */
135
+ export declare const GRANT_TTL_MS: number;
84
136
  /** CI / pipeline environments — no human at a keyboard. Shared by hooks and discovery. */
85
137
  export declare function isCiEnvironment(env?: NodeJS.ProcessEnv): boolean;
86
138
  /**
@@ -89,11 +141,7 @@ export declare function isCiEnvironment(env?: NodeJS.ProcessEnv): boolean;
89
141
  * developer-scoped approval must go to the org queue or block, never self-resolve.
90
142
  */
91
143
  export declare function isHumanPresent(env?: NodeJS.ProcessEnv): boolean;
92
- /**
93
- * Canonical digest of what the agent is about to do. Shell: the trimmed command. MCP/file: the
94
- * tool args with keys sorted, so the pre- and post-hook payloads (which the IDE serializes
95
- * independently) hash the same.
96
- */
144
+ /** Canonical JSON action, including cwd and every argument. Invalid inputs cannot reuse consent. */
97
145
  export declare function actionDigest(toolArgs: Record<string, unknown>): string;
98
146
  export declare function pendingKey(event: string, toolName: string, argsDigest: string): string;
99
147
  /**
@@ -112,7 +160,10 @@ export declare function consumeConfirmedAction(toolName: string, argsDigest: str
112
160
  export interface RememberScope {
113
161
  toolName: string;
114
162
  operation: string;
163
+ argsDigest: string;
115
164
  policyId?: string;
165
+ policyHash?: string;
166
+ authorizationContext?: string;
116
167
  matchedRule?: string;
117
168
  }
118
169
  /** Record that the IDE was asked to confirm this action. Returns the marker (for logging). */
@@ -136,7 +187,38 @@ export declare function rejectDeveloperConfirmation(event: string, toolName: str
136
187
  */
137
188
  export declare function sweepExpiredConfirmations(now?: Date, ttlMs?: number): PendingConfirmation[];
138
189
  /**
139
- * Is this tool + rule covered by a still-valid developer confirmation? Session memory only
140
- * matches the same IDE session id; 1h memory matches any session on this machine.
190
+ * The programs a shell line runs — one per segment, the wrapper (`sudo`, `env`),
191
+ * the `$var =` assignment, `KEY=value` prefixes and a leading `(` peeled off; the
192
+ * path and `.exe` dropped; lower-cased. Sorted and unique. A segment with no
193
+ * program (pure block structure like `} catch {`) contributes nothing.
194
+ */
195
+ export declare function shellPrograms(command: string): string[];
196
+ /**
197
+ * Does a live grant cover this action under this exact rule? Shell programs match by
198
+ * INCLUSION: a grant for `{gh, git}` covers a line that only runs `gh`, never a line
199
+ * that also runs `curl`. Everything else must be equal. Touch counts a use.
200
+ */
201
+ export declare function findDeveloperGrant(scope: GrantScope, rule: {
202
+ policyId?: string;
203
+ matchedRule?: string;
204
+ policyHash?: string;
205
+ }, now?: Date): DeveloperGrant | undefined;
206
+ /** The developer pressed "Always allow": store the grant (replacing any prior one for the same key). */
207
+ export declare function recordDeveloperGrant(scope: GrantScope, rule: {
208
+ policyId?: string;
209
+ policyName?: string;
210
+ matchedRule?: string;
211
+ policyHash: string;
212
+ }, now?: Date): DeveloperGrant | undefined;
213
+ export declare function listDeveloperGrants(now?: Date): DeveloperGrant[];
214
+ /** Remove one grant by key, or every grant when `key` is omitted. Returns how many were removed. */
215
+ export declare function revokeDeveloperGrants(key?: string): number;
216
+ /** One line a person can read on the button and in the grant list: what "Always allow" covers. */
217
+ export declare function describeGrantScope(scope: GrantScope): string;
218
+ /** The scope a grant would have for this action — from engine facts, never from text. */
219
+ export declare function grantScopeFor(event: string, toolName: string, toolArgs: Record<string, unknown> | undefined, operation: string, context: Record<string, unknown> | undefined): GrantScope;
220
+ /**
221
+ * Is this exact action covered in the same session and policy context?
222
+ * The 1h choice is a shorter expiry, never authorization for another session.
141
223
  */
142
224
  export declare function isConfirmationRemembered(scope: RememberScope, sessionId: string | undefined, now?: Date): RememberedConfirmation | undefined;