fullcourtdefense-cli 1.35.9 → 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.
- package/dist/askDialog.js +1 -2
- package/dist/callerEnv.d.ts +10 -0
- package/dist/callerEnv.js +18 -1
- package/dist/commandNarrative.d.ts +8 -0
- package/dist/commandNarrative.js +69 -10
- package/dist/commands/agentCi.js +3 -1
- package/dist/commands/daemon.js +6 -1
- package/dist/commands/deterministicGuard.d.ts +1 -1
- package/dist/commands/deterministicGuard.js +313 -27
- package/dist/commands/discover.js +23 -0
- package/dist/commands/discoverAgentFiles.d.ts +11 -0
- package/dist/commands/discoverAgentFiles.js +107 -0
- package/dist/commands/discoverExtensions.d.ts +64 -0
- package/dist/commands/discoverExtensions.js +386 -0
- package/dist/commands/hook.d.ts +28 -4
- package/dist/commands/hook.js +61 -16
- package/dist/commands/local-scan.js +1 -0
- package/dist/commands/mcpGateway.js +273 -54
- package/dist/commands/protectedRun.d.ts +1 -1
- package/dist/commands/protectedRun.js +3 -3
- package/dist/commands/watchdog.js +2 -0
- package/dist/credentialGrant.d.ts +43 -0
- package/dist/credentialGrant.js +504 -0
- package/dist/daemonForensics.d.ts +27 -0
- package/dist/daemonForensics.js +47 -0
- package/dist/detectorBaseline.d.ts +2 -2
- package/dist/detectorBaseline.js +2 -2
- package/dist/devConfirm.d.ts +48 -1
- package/dist/devConfirm.js +233 -111
- package/dist/envDiagnostics.js +4 -2
- package/dist/hookSlim.js +4 -3
- package/dist/ideExtensionRisk.d.ts +110 -0
- package/dist/ideExtensionRisk.js +172 -0
- package/dist/machineActionVerify.d.ts +34 -8
- package/dist/machineActionVerify.js +76 -16
- package/dist/mcpProtocol.d.ts +124 -0
- package/dist/mcpProtocol.js +302 -0
- package/dist/mcpToolReview.d.ts +2 -1
- package/dist/mcpToolReview.js +6 -4
- package/dist/notify.d.ts +7 -0
- package/dist/notify.js +61 -0
- package/dist/shellQuoteMask.d.ts +25 -0
- package/dist/shellQuoteMask.js +319 -0
- package/dist/telemetry.d.ts +2 -0
- package/dist/verdictIpc.js +16 -0
- package/dist/verdictIpcClient.d.ts +32 -12
- package/dist/verdictIpcClient.js +55 -26
- package/dist/version.json +1 -1
- package/package.json +7 -2
package/dist/askDialog.js
CHANGED
|
@@ -141,8 +141,7 @@ async function resolveAskWithDialog(verdict, options = {}) {
|
|
|
141
141
|
return { ...clean, resolution: 'ide' };
|
|
142
142
|
const grantHash = envelope.ruleHash || envelope.policyHash;
|
|
143
143
|
const grantLimits = envelope.grantLimits === null ? undefined : (envelope.grantLimits || { ttlMs: devConfirm_1.GRANT_TTL_MS });
|
|
144
|
-
const allowAlways = envelope.remember !== 'none' && Boolean(grantHash) && Boolean(grantLimits)
|
|
145
|
-
&& (envelope.scope.event !== 'shell' || (envelope.scope.programs || []).length > 0);
|
|
144
|
+
const allowAlways = envelope.remember !== 'none' && Boolean(grantHash) && Boolean(grantLimits) && (0, devConfirm_1.grantOffered)(envelope.scope);
|
|
146
145
|
const scopeLabel = allowAlways ? (0, devConfirm_1.describeGrantScope)(envelope.scope, { policyId: envelope.policyId, policyName: envelope.policyName }) : undefined;
|
|
147
146
|
const limitsLabel = allowAlways && grantLimits ? (0, devConfirm_1.describeGrantLimits)(grantLimits) : undefined;
|
|
148
147
|
const dialog = options.dialog || devConfirm_1.confirmNativeActionDetailed;
|
package/dist/callerEnv.d.ts
CHANGED
|
@@ -23,6 +23,16 @@ export declare function callerEnv(): NodeJS.ProcessEnv;
|
|
|
23
23
|
* from the daemon. No snapshot (older client) = this process's env, as before.
|
|
24
24
|
*/
|
|
25
25
|
export declare function withCallerEnv<T>(snapshot: unknown, fn: () => T): T;
|
|
26
|
+
/**
|
|
27
|
+
* How long the IDE lets an approval-capable hook run before it kills the hook
|
|
28
|
+
* (and runs the action): Claude Code / Copilot / VS Code and Cursor: 90s.
|
|
29
|
+
* FCD_HOOK_IDE_TIMEOUT_MS narrows it for drills.
|
|
30
|
+
*/
|
|
31
|
+
export declare const IDE_HOOK_TIMEOUT_MS = 90000;
|
|
32
|
+
export declare const IDE_TIMEOUT_MARGIN_MS = 15000;
|
|
33
|
+
export declare function ideHookTimeoutMs(env?: NodeJS.ProcessEnv): number;
|
|
34
|
+
/** Absolute time every wait inside a hook must end by, leaving the margin before the IDE's timeout. */
|
|
35
|
+
export declare function ideWaitCutoff(startedAt: number, ideTimeoutMs?: number): number;
|
|
26
36
|
/** CI / pipeline environments — no human at a keyboard. Shared by hooks and discovery. */
|
|
27
37
|
export declare function isCiEnvironment(env?: NodeJS.ProcessEnv): boolean;
|
|
28
38
|
/**
|
package/dist/callerEnv.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CALLER_ENV_KEYS = void 0;
|
|
3
|
+
exports.IDE_TIMEOUT_MARGIN_MS = exports.IDE_HOOK_TIMEOUT_MS = exports.CALLER_ENV_KEYS = void 0;
|
|
4
4
|
exports.snapshotCallerEnv = snapshotCallerEnv;
|
|
5
5
|
exports.callerEnv = callerEnv;
|
|
6
6
|
exports.withCallerEnv = withCallerEnv;
|
|
7
|
+
exports.ideHookTimeoutMs = ideHookTimeoutMs;
|
|
8
|
+
exports.ideWaitCutoff = ideWaitCutoff;
|
|
7
9
|
exports.isCiEnvironment = isCiEnvironment;
|
|
8
10
|
exports.isHumanPresent = isHumanPresent;
|
|
9
11
|
const async_hooks_1 = require("async_hooks");
|
|
@@ -66,6 +68,21 @@ function withCallerEnv(snapshot, fn) {
|
|
|
66
68
|
}
|
|
67
69
|
return requestEnv.run(env, fn);
|
|
68
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* How long the IDE lets an approval-capable hook run before it kills the hook
|
|
73
|
+
* (and runs the action): Claude Code / Copilot / VS Code and Cursor: 90s.
|
|
74
|
+
* FCD_HOOK_IDE_TIMEOUT_MS narrows it for drills.
|
|
75
|
+
*/
|
|
76
|
+
exports.IDE_HOOK_TIMEOUT_MS = 90_000;
|
|
77
|
+
exports.IDE_TIMEOUT_MARGIN_MS = 15_000;
|
|
78
|
+
function ideHookTimeoutMs(env = callerEnv()) {
|
|
79
|
+
const override = Number(env.FCD_HOOK_IDE_TIMEOUT_MS);
|
|
80
|
+
return override > 0 ? override : exports.IDE_HOOK_TIMEOUT_MS;
|
|
81
|
+
}
|
|
82
|
+
/** Absolute time every wait inside a hook must end by, leaving the margin before the IDE's timeout. */
|
|
83
|
+
function ideWaitCutoff(startedAt, ideTimeoutMs = ideHookTimeoutMs()) {
|
|
84
|
+
return startedAt + ideTimeoutMs - Math.min(exports.IDE_TIMEOUT_MARGIN_MS, ideTimeoutMs / 4);
|
|
85
|
+
}
|
|
69
86
|
/** CI / pipeline environments — no human at a keyboard. Shared by hooks and discovery. */
|
|
70
87
|
function isCiEnvironment(env = callerEnv()) {
|
|
71
88
|
return env.GITHUB_ACTIONS === 'true'
|
|
@@ -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[];
|
package/dist/commandNarrative.js
CHANGED
|
@@ -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 = [];
|
|
@@ -158,7 +164,11 @@ function describeSecretVersions(toks, verbAt, where) {
|
|
|
158
164
|
return `${state[verb]} ${version}${secret(flagSecret)} in Secret Manager${where}`;
|
|
159
165
|
return `Runs \`gcloud secrets versions ${verb}\`${where}`;
|
|
160
166
|
}
|
|
161
|
-
|
|
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) {
|
|
162
172
|
let i = 1;
|
|
163
173
|
while (i < toks.length && toks[i].startsWith('-'))
|
|
164
174
|
i++;
|
|
@@ -210,8 +220,10 @@ function describeGcloud(toks) {
|
|
|
210
220
|
return `Reads the contents of ${isStorageUri(first) ? `an object in storage bucket ${bucketOf(first)}` : shortValue(first, 60)}`;
|
|
211
221
|
if (verb === 'rm')
|
|
212
222
|
return `Deletes ${named(first)}`;
|
|
213
|
-
if (/^print-(?:access|identity)-token$/.test(verb))
|
|
214
|
-
|
|
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
|
+
}
|
|
215
227
|
if (verb === 'login')
|
|
216
228
|
return 'Signs in to Google Cloud';
|
|
217
229
|
if (verb === 'add-iam-policy-binding' || verb === 'remove-iam-policy-binding') {
|
|
@@ -334,7 +346,7 @@ function peelGroups(text) {
|
|
|
334
346
|
}
|
|
335
347
|
return value;
|
|
336
348
|
}
|
|
337
|
-
function describeOne(segmentIn) {
|
|
349
|
+
function describeOne(segmentIn, captured) {
|
|
338
350
|
const segment = segmentIn.trim();
|
|
339
351
|
const [head, ...pipe] = splitPipe(segment);
|
|
340
352
|
const tail = describePipeTail(pipe);
|
|
@@ -370,8 +382,13 @@ function describeOne(segmentIn) {
|
|
|
370
382
|
}
|
|
371
383
|
return step(`Sets variable ${name}${interpolates ? ' from other values' : ` to ${shortValue(bare, 60)}`}`);
|
|
372
384
|
}
|
|
373
|
-
const
|
|
374
|
-
const
|
|
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
|
+
}
|
|
375
392
|
return { segment, text: shortValue(`Stores in ${name}: ${innerStep.text.charAt(0).toLowerCase()}${innerStep.text.slice(1)}${tail}`, MAX_LINE), known: innerStep.known };
|
|
376
393
|
}
|
|
377
394
|
// A bare string statement: PowerShell writes it to the output (`"len=$($t.Length)"`).
|
|
@@ -412,7 +429,7 @@ function describeOne(segmentIn) {
|
|
|
412
429
|
case 'exit':
|
|
413
430
|
case 'return': return step('Ends the script');
|
|
414
431
|
case 'gcloud': {
|
|
415
|
-
const text = describeGcloud(rest);
|
|
432
|
+
const text = describeGcloud(rest, captured);
|
|
416
433
|
return text ? step(`${text}${redirectNote}`) : step(`Runs \`gcloud\``, false);
|
|
417
434
|
}
|
|
418
435
|
case 'gsutil': {
|
|
@@ -475,7 +492,7 @@ function describeOne(segmentIn) {
|
|
|
475
492
|
if (/\b(?:create|edit|merge|close|delete|comment)\b/.test(sub))
|
|
476
493
|
return step(`Changes GitHub ${sub.split(' ')[0]} (${sub.split(' ')[1]})`);
|
|
477
494
|
if (/^auth token\b/.test(sub))
|
|
478
|
-
return step('Prints the GitHub token');
|
|
495
|
+
return step(captured ? `Stores the GitHub token in variable ${captured}` : 'Prints the GitHub token');
|
|
479
496
|
return step(`Runs \`gh ${sub}\``, false);
|
|
480
497
|
}
|
|
481
498
|
case 'npm':
|
|
@@ -691,17 +708,59 @@ function resolveStorageVariables(steps) {
|
|
|
691
708
|
return { ...step, text };
|
|
692
709
|
});
|
|
693
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
|
+
}
|
|
694
752
|
/** One plain-words line per step, in order. */
|
|
695
753
|
function describeShellCommand(command) {
|
|
696
754
|
const steps = splitCommandSteps(command);
|
|
697
|
-
const
|
|
755
|
+
const described = steps.map(segment => {
|
|
698
756
|
try {
|
|
699
757
|
return describeOne(segment);
|
|
700
758
|
}
|
|
701
759
|
catch {
|
|
702
760
|
return { segment, text: 'Runs a command the grammar does not recognise', known: false };
|
|
703
761
|
}
|
|
704
|
-
})
|
|
762
|
+
});
|
|
763
|
+
const out = resolveStorageVariables(noteTokenDestinations(described).slice(0, MAX_STEPS));
|
|
705
764
|
if (steps.length > MAX_STEPS)
|
|
706
765
|
out.push({ segment: '', text: `… and ${steps.length - MAX_STEPS} more step${steps.length - MAX_STEPS === 1 ? '' : 's'}`, known: false });
|
|
707
766
|
return out;
|
package/dist/commands/agentCi.js
CHANGED
|
@@ -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(', ')}.`,
|
package/dist/commands/daemon.js
CHANGED
|
@@ -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
|
});
|
|
@@ -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
|