fullcourtdefense-cli 1.35.8 → 1.35.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actionIdentity.js +2 -1
- package/dist/actionPolicyEngine.d.ts +1 -0
- package/dist/actionPolicyEngine.js +203 -14
- package/dist/approvalPrompt.js +2 -2
- package/dist/askDialog.js +6 -6
- package/dist/blockExplanation.js +2 -0
- package/dist/callerEnv.d.ts +43 -0
- package/dist/callerEnv.js +107 -0
- package/dist/commandNarrative.d.ts +8 -0
- package/dist/commandNarrative.js +179 -13
- package/dist/commands/agentCi.js +3 -1
- package/dist/commands/daemon.js +7 -2
- package/dist/commands/deterministicGuard.d.ts +1 -1
- package/dist/commands/deterministicGuard.js +319 -28
- 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 +58 -1
- package/dist/commands/hook.js +223 -37
- 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/taintLedger.js +3 -1
- 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 +76 -13
- package/dist/devConfirm.js +352 -144
- package/dist/envDiagnostics.js +4 -2
- package/dist/hookSlim.d.ts +1 -1
- package/dist/hookSlim.js +26 -5
- 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/nativeHook.d.ts +2 -1
- package/dist/nativeHook.js +109 -3
- package/dist/notify.d.ts +7 -0
- package/dist/notify.js +65 -0
- package/dist/shellQuoteMask.d.ts +25 -0
- package/dist/shellQuoteMask.js +319 -0
- package/dist/telemetry.d.ts +4 -0
- package/dist/telemetry.js +6 -0
- package/dist/verdictIpc.js +21 -2
- package/dist/verdictIpcClient.d.ts +43 -13
- package/dist/verdictIpcClient.js +66 -27
- package/dist/version.json +1 -1
- package/package.json +8 -3
|
@@ -20,6 +20,14 @@ export interface CommandStep {
|
|
|
20
20
|
text: string;
|
|
21
21
|
/** The grammar recognised the program and its verb. */
|
|
22
22
|
known: boolean;
|
|
23
|
+
/** A credential stored in a variable by this step (`$tok = gcloud auth print-access-token`). */
|
|
24
|
+
captures?: {
|
|
25
|
+
variable: string;
|
|
26
|
+
issuer?: {
|
|
27
|
+
name: string;
|
|
28
|
+
domains: string[];
|
|
29
|
+
};
|
|
30
|
+
};
|
|
23
31
|
}
|
|
24
32
|
/** Quote-aware split on `;`, `&&`, `||`, newlines. Pipes stay inside a step. */
|
|
25
33
|
export declare function splitCommandSteps(command: string): string[];
|
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 = [];
|
|
@@ -110,12 +116,12 @@ const GCLOUD_SERVICE_NAMES = {
|
|
|
110
116
|
secrets: 'Secret Manager', iam: 'IAM', projects: 'project', functions: 'Cloud Functions', container: 'GKE', logging: 'Cloud Logging',
|
|
111
117
|
artifacts: 'Artifact Registry', scheduler: 'Cloud Scheduler', tasks: 'Cloud Tasks', kms: 'Cloud KMS', builds: 'Cloud Build', auth: 'auth', config: 'config',
|
|
112
118
|
};
|
|
113
|
-
const GCLOUD_READ = /^(?:describe|list|ls|get-iam-policy|get-value|get-ancestors|read|check
|
|
119
|
+
const GCLOUD_READ = /^(?:describe|list|ls|get-iam-policy|get-value|get-ancestors|read|check)$/;
|
|
114
120
|
const GCLOUD_WRITE = /^(?:create|update|patch|replace|enable|disable|deploy|apply|start|stop|restart|resume|suspend|scale|promote|migrate|rollback|add-[a-z-]+|remove-[a-z-]+|set-[a-z-]+|unset|clear|activate|revoke|copy)$/;
|
|
115
121
|
function humanVerb(verb) {
|
|
116
122
|
const map = {
|
|
117
123
|
describe: 'Reads details of', list: 'Lists', ls: 'Lists', 'get-iam-policy': 'Reads the access policy of', 'get-value': 'Reads the setting',
|
|
118
|
-
read: 'Reads', check: 'Checks',
|
|
124
|
+
read: 'Reads', check: 'Checks',
|
|
119
125
|
create: 'Creates', update: 'Updates', patch: 'Updates', replace: 'Replaces', enable: 'Enables', disable: 'Disables', deploy: 'Deploys',
|
|
120
126
|
apply: 'Applies', start: 'Starts', stop: 'Stops', restart: 'Restarts', resume: 'Resumes', suspend: 'Suspends', scale: 'Scales',
|
|
121
127
|
promote: 'Promotes', migrate: 'Migrates', rollback: 'Rolls back', unset: 'Unsets', clear: 'Clears', activate: 'Activates', revoke: 'Revokes', copy: 'Copies',
|
|
@@ -135,12 +141,43 @@ function humanVerb(verb) {
|
|
|
135
141
|
return `Sets ${verb.slice(4).replace(/-/g, ' ')} on`;
|
|
136
142
|
return `Runs \`${verb}\` on`;
|
|
137
143
|
}
|
|
138
|
-
|
|
144
|
+
/** `gcloud secrets versions <verb>`: `access` reads the secret VALUE, `list` lists versions, `add` stores a new value. */
|
|
145
|
+
function describeSecretVersions(toks, verbAt, where) {
|
|
146
|
+
const verb = toks[verbAt] || '';
|
|
147
|
+
const ops = operandsAfter(toks, verbAt + 1);
|
|
148
|
+
const flagSecret = flagValue(toks, '--secret');
|
|
149
|
+
const secret = (name) => (name ? `secret ${shortValue(name, 60)}` : 'a secret');
|
|
150
|
+
if (verb === 'access') {
|
|
151
|
+
return `Reads the value of ${secret(flagSecret)} (version ${shortValue(ops[0] || 'latest', 20)}) from Secret Manager${where}`;
|
|
152
|
+
}
|
|
153
|
+
if (verb === 'list')
|
|
154
|
+
return `Lists the versions of ${secret(ops[0])} in Secret Manager${where}`;
|
|
155
|
+
if (verb === 'add') {
|
|
156
|
+
const dataFile = flagValue(toks, '--data-file');
|
|
157
|
+
return `Adds a new version to ${secret(ops[0])} in Secret Manager${dataFile ? `, value from ${dataFile === '-' ? 'standard input' : shortValue(dataFile, 60)}` : ''}${where}`;
|
|
158
|
+
}
|
|
159
|
+
const version = ops[0] ? `version ${shortValue(ops[0], 20)} of ` : '';
|
|
160
|
+
if (verb === 'describe')
|
|
161
|
+
return `Reads details of ${version}${secret(flagSecret)} in Secret Manager${where}`;
|
|
162
|
+
const state = { destroy: 'Destroys', disable: 'Disables', enable: 'Enables' };
|
|
163
|
+
if (state[verb])
|
|
164
|
+
return `${state[verb]} ${version}${secret(flagSecret)} in Secret Manager${where}`;
|
|
165
|
+
return `Runs \`gcloud secrets versions ${verb}\`${where}`;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* `captured` = the variable the command's output is assigned to (`$tok = gcloud …`,
|
|
169
|
+
* `TOKEN=$(gcloud …)`): the output never reaches the terminal.
|
|
170
|
+
*/
|
|
171
|
+
function describeGcloud(toks, captured) {
|
|
139
172
|
let i = 1;
|
|
140
173
|
while (i < toks.length && toks[i].startsWith('-'))
|
|
141
174
|
i++;
|
|
142
175
|
if (/^(?:alpha|beta)$/.test(toks[i] || ''))
|
|
143
176
|
i++;
|
|
177
|
+
if (toks[i] === 'secrets' && toks[i + 1] === 'versions') {
|
|
178
|
+
const project = flagValue(toks, '--project');
|
|
179
|
+
return describeSecretVersions(toks, i + 2, project ? ` in project ${project}` : '');
|
|
180
|
+
}
|
|
144
181
|
const groups = [];
|
|
145
182
|
let verb;
|
|
146
183
|
for (; i < toks.length; i++) {
|
|
@@ -183,8 +220,10 @@ function describeGcloud(toks) {
|
|
|
183
220
|
return `Reads the contents of ${isStorageUri(first) ? `an object in storage bucket ${bucketOf(first)}` : shortValue(first, 60)}`;
|
|
184
221
|
if (verb === 'rm')
|
|
185
222
|
return `Deletes ${named(first)}`;
|
|
186
|
-
if (/^print-(?:access|identity)-token$/.test(verb))
|
|
187
|
-
|
|
223
|
+
if (/^print-(?:access|identity)-token$/.test(verb)) {
|
|
224
|
+
const kind = verb === 'print-identity-token' ? 'identity' : 'access';
|
|
225
|
+
return captured ? `Stores a cloud ${kind} token in variable ${captured}` : `Prints a cloud ${kind} token to the terminal`;
|
|
226
|
+
}
|
|
188
227
|
if (verb === 'login')
|
|
189
228
|
return 'Signs in to Google Cloud';
|
|
190
229
|
if (verb === 'add-iam-policy-binding' || verb === 'remove-iam-policy-binding') {
|
|
@@ -234,7 +273,80 @@ function describePipeTail(stages) {
|
|
|
234
273
|
return ', and writes the output to a file';
|
|
235
274
|
return '';
|
|
236
275
|
}
|
|
237
|
-
|
|
276
|
+
/**
|
|
277
|
+
* The whole statement is one quoted string (`"len=$($t.Length)"`, `'done'`): its text
|
|
278
|
+
* with each `$( … )` sub-expression shown as `…`. Undefined when it is anything else.
|
|
279
|
+
*/
|
|
280
|
+
function bareStringText(head) {
|
|
281
|
+
const value = head.trim();
|
|
282
|
+
const q = value[0];
|
|
283
|
+
if ((q !== '"' && q !== "'") || value.length < 2 || value[value.length - 1] !== q)
|
|
284
|
+
return undefined;
|
|
285
|
+
let depth = 0;
|
|
286
|
+
let text = '';
|
|
287
|
+
for (let k = 1; k < value.length - 1; k++) {
|
|
288
|
+
const ch = value[k];
|
|
289
|
+
if (q === '"' && ch === '`') {
|
|
290
|
+
text += value[++k] ?? '';
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (q === '"' && ch === '$' && value[k + 1] === '(') {
|
|
294
|
+
if (depth++ === 0)
|
|
295
|
+
text += '…';
|
|
296
|
+
k++;
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (depth > 0) {
|
|
300
|
+
if (ch === '(')
|
|
301
|
+
depth++;
|
|
302
|
+
else if (ch === ')')
|
|
303
|
+
depth--;
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (ch === q) {
|
|
307
|
+
if (q === "'" && value[k + 1] === "'") {
|
|
308
|
+
text += "'";
|
|
309
|
+
k++;
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
text += ch;
|
|
315
|
+
}
|
|
316
|
+
return depth === 0 ? text.trim() : undefined;
|
|
317
|
+
}
|
|
318
|
+
/** `((gcloud … $p) -join '').Trim()` -> `gcloud … $p`: the command inside nested groups (quote-aware). */
|
|
319
|
+
function peelGroups(text) {
|
|
320
|
+
let value = text.trim();
|
|
321
|
+
for (let guard = 0; guard < 8 && value.startsWith('('); guard++) {
|
|
322
|
+
let depth = 0;
|
|
323
|
+
let end = -1;
|
|
324
|
+
let quote = '';
|
|
325
|
+
for (let k = 0; k < value.length; k++) {
|
|
326
|
+
const ch = value[k];
|
|
327
|
+
if (quote) {
|
|
328
|
+
if (ch === quote)
|
|
329
|
+
quote = '';
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (ch === '"' || ch === "'") {
|
|
333
|
+
quote = ch;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (ch === '(')
|
|
337
|
+
depth++;
|
|
338
|
+
else if (ch === ')' && --depth === 0) {
|
|
339
|
+
end = k;
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (end < 0)
|
|
344
|
+
break;
|
|
345
|
+
value = value.slice(1, end).trim();
|
|
346
|
+
}
|
|
347
|
+
return value;
|
|
348
|
+
}
|
|
349
|
+
function describeOne(segmentIn, captured) {
|
|
238
350
|
const segment = segmentIn.trim();
|
|
239
351
|
const [head, ...pipe] = splitPipe(segment);
|
|
240
352
|
const tail = describePipeTail(pipe);
|
|
@@ -270,10 +382,19 @@ function describeOne(segmentIn) {
|
|
|
270
382
|
}
|
|
271
383
|
return step(`Sets variable ${name}${interpolates ? ' from other values' : ` to ${shortValue(bare, 60)}`}`);
|
|
272
384
|
}
|
|
273
|
-
const
|
|
274
|
-
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
|
+
}
|
|
275
392
|
return { segment, text: shortValue(`Stores in ${name}: ${innerStep.text.charAt(0).toLowerCase()}${innerStep.text.slice(1)}${tail}`, MAX_LINE), known: innerStep.known };
|
|
276
393
|
}
|
|
394
|
+
// A bare string statement: PowerShell writes it to the output (`"len=$($t.Length)"`).
|
|
395
|
+
const bare = bareStringText(head);
|
|
396
|
+
if (bare !== undefined)
|
|
397
|
+
return step(bare ? `Prints text: ${shortValue(bare, 80)}` : 'Prints an empty line');
|
|
277
398
|
const toks = tokens(head);
|
|
278
399
|
if (toks.length === 0)
|
|
279
400
|
return step('(empty)', false);
|
|
@@ -308,7 +429,7 @@ function describeOne(segmentIn) {
|
|
|
308
429
|
case 'exit':
|
|
309
430
|
case 'return': return step('Ends the script');
|
|
310
431
|
case 'gcloud': {
|
|
311
|
-
const text = describeGcloud(rest);
|
|
432
|
+
const text = describeGcloud(rest, captured);
|
|
312
433
|
return text ? step(`${text}${redirectNote}`) : step(`Runs \`gcloud\``, false);
|
|
313
434
|
}
|
|
314
435
|
case 'gsutil': {
|
|
@@ -371,7 +492,7 @@ function describeOne(segmentIn) {
|
|
|
371
492
|
if (/\b(?:create|edit|merge|close|delete|comment)\b/.test(sub))
|
|
372
493
|
return step(`Changes GitHub ${sub.split(' ')[0]} (${sub.split(' ')[1]})`);
|
|
373
494
|
if (/^auth token\b/.test(sub))
|
|
374
|
-
return step('Prints the GitHub token');
|
|
495
|
+
return step(captured ? `Stores the GitHub token in variable ${captured}` : 'Prints the GitHub token');
|
|
375
496
|
return step(`Runs \`gh ${sub}\``, false);
|
|
376
497
|
}
|
|
377
498
|
case 'npm':
|
|
@@ -531,7 +652,10 @@ function describeOne(segmentIn) {
|
|
|
531
652
|
default:
|
|
532
653
|
if (/^\$/.test(programToken))
|
|
533
654
|
return step(pipe.length ? `Uses ${shortValue(programToken, 40)}${tail}` : `Reads variable ${shortValue(programToken, 40)}`, false);
|
|
534
|
-
|
|
655
|
+
{
|
|
656
|
+
const shown = shortValue([program, rest[1]].filter(t => t && !t.startsWith('-')).join(' '), 50);
|
|
657
|
+
return step(shown ? `Runs \`${shown}\`` : 'Runs a command this dialog cannot describe', false);
|
|
658
|
+
}
|
|
535
659
|
}
|
|
536
660
|
}
|
|
537
661
|
/** Split one step on top-level `|` (quote-aware). */
|
|
@@ -584,17 +708,59 @@ function resolveStorageVariables(steps) {
|
|
|
584
708
|
return { ...step, text };
|
|
585
709
|
});
|
|
586
710
|
}
|
|
711
|
+
/**
|
|
712
|
+
* A token stored in a variable, then used later on the line: name the host(s)
|
|
713
|
+
* that receive it and whether each is the token's issuer. Hosts are literal
|
|
714
|
+
* URLs in the using step, or a variable assigned a literal URL earlier.
|
|
715
|
+
*/
|
|
716
|
+
function noteTokenDestinations(steps) {
|
|
717
|
+
const urlVars = new Map();
|
|
718
|
+
for (const step of steps) {
|
|
719
|
+
const assign = /^\$?([\w:]+)\s*=\s*["']?https?:\/\/([^/\s"'`$]+)/i.exec(step.segment);
|
|
720
|
+
if (assign)
|
|
721
|
+
urlVars.set(assign[1].toLowerCase(), assign[2].toLowerCase());
|
|
722
|
+
}
|
|
723
|
+
return steps.map((step, index) => {
|
|
724
|
+
if (!step.captures)
|
|
725
|
+
return step;
|
|
726
|
+
const { variable, issuer } = step.captures;
|
|
727
|
+
const ref = new RegExp(`\\$\\{?${variable.replace(/[^\w:]/g, '')}\\}?(?![\\w])`, 'i');
|
|
728
|
+
const hosts = new Set();
|
|
729
|
+
for (const later of steps.slice(index + 1)) {
|
|
730
|
+
if (!ref.test(later.segment))
|
|
731
|
+
continue;
|
|
732
|
+
for (const m of later.segment.matchAll(/https?:\/\/([^/\s"'`<>]+)/gi))
|
|
733
|
+
if (!/[$({]/.test(m[1]))
|
|
734
|
+
hosts.add(m[1].toLowerCase().replace(/:\d+$/, ''));
|
|
735
|
+
for (const m of later.segment.matchAll(/\$\{?([\w:]+)\}?/g)) {
|
|
736
|
+
const host = urlVars.get(m[1].toLowerCase());
|
|
737
|
+
if (host)
|
|
738
|
+
hosts.add(host);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
if (!hosts.size)
|
|
742
|
+
return step;
|
|
743
|
+
const described = [...hosts].sort().map(host => {
|
|
744
|
+
if (!issuer)
|
|
745
|
+
return host;
|
|
746
|
+
const own = issuer.domains.some(d => host === d || host.endsWith(`.${d}`));
|
|
747
|
+
return own ? `${host} (${issuer.name}, the token's issuer)` : `${host}, which is not ${issuer.name}`;
|
|
748
|
+
});
|
|
749
|
+
return { ...step, text: shortValue(`${step.text}, then sends it to ${described.join('; ')}`, MAX_LINE) };
|
|
750
|
+
});
|
|
751
|
+
}
|
|
587
752
|
/** One plain-words line per step, in order. */
|
|
588
753
|
function describeShellCommand(command) {
|
|
589
754
|
const steps = splitCommandSteps(command);
|
|
590
|
-
const
|
|
755
|
+
const described = steps.map(segment => {
|
|
591
756
|
try {
|
|
592
757
|
return describeOne(segment);
|
|
593
758
|
}
|
|
594
759
|
catch {
|
|
595
760
|
return { segment, text: 'Runs a command the grammar does not recognise', known: false };
|
|
596
761
|
}
|
|
597
|
-
})
|
|
762
|
+
});
|
|
763
|
+
const out = resolveStorageVariables(noteTokenDestinations(described).slice(0, MAX_STEPS));
|
|
598
764
|
if (steps.length > MAX_STEPS)
|
|
599
765
|
out.push({ segment: '', text: `… and ${steps.length - MAX_STEPS} more step${steps.length - MAX_STEPS === 1 ? '' : 's'}`, known: false });
|
|
600
766
|
return out;
|
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
|
});
|
|
@@ -1593,7 +1598,7 @@ async function runDaemon(args, config) {
|
|
|
1593
1598
|
// to full local evaluation whenever this server is down — never a blocker.
|
|
1594
1599
|
let verdictServer;
|
|
1595
1600
|
try {
|
|
1596
|
-
verdictServer = (0, verdictIpc_1.startVerdictServer)((hookArgs, stdin) => (0, hook_1.evaluateHookRequest)(hookArgs, stdin, (0, config_1.loadConfig)()), log);
|
|
1601
|
+
verdictServer = (0, verdictIpc_1.startVerdictServer)((hookArgs, stdin) => (0, hook_1.evaluateHookRequest)(hookArgs, stdin, (0, config_1.loadConfig)(), { viaDaemon: true }), log);
|
|
1597
1602
|
}
|
|
1598
1603
|
catch (error) {
|
|
1599
1604
|
log(`Verdict IPC: failed to start (${error.message}) — hooks will evaluate locally.`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type DeterministicDirection = 'request' | 'response';
|
|
2
|
-
export type DeterministicCategory = 'sensitive_file' | 'metadata_ssrf' | 'credential_command' | 'destructive_command' | 'destructive_sql' | 'infra_destroy' | 'reverse_shell' | 'secret_exfiltration' | 'honeypot';
|
|
2
|
+
export type DeterministicCategory = 'sensitive_file' | 'metadata_ssrf' | 'credential_command' | 'destructive_command' | 'destructive_sql' | 'infra_destroy' | 'reverse_shell' | 'secret_exfiltration' | 'honeypot' | 'agent_guard_tamper';
|
|
3
3
|
/**
|
|
4
4
|
* Per-rule enforcement action (org-configured, shipped in the Local Safety
|
|
5
5
|
* snapshot). Absent everywhere = 'block' — the only behavior older CLIs and
|