fullcourtdefense-cli 1.35.6 → 1.35.7
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/commands/hook.js +3 -1
- package/dist/devConfirm.d.ts +32 -1
- package/dist/devConfirm.js +26 -2
- package/dist/version.json +1 -1
- package/package.json +1 -1
package/dist/commands/hook.js
CHANGED
|
@@ -820,7 +820,9 @@ function prepareDeveloperConfirmation(event, call, payload, source) {
|
|
|
820
820
|
// this exact rule (same rule hash). Rules that forbid memory never reach here.
|
|
821
821
|
// The caps in force come from the rule and the org ceiling in the cached bundle;
|
|
822
822
|
// an org that switched "Always allow" off (`null`) honours no grant at all.
|
|
823
|
-
|
|
823
|
+
// The grant binds the facts THIS rule fired on: a credential-command rule is
|
|
824
|
+
// about the program, an egress / SSRF / exfil rule is about the host too.
|
|
825
|
+
const grantScope = (0, devConfirm_1.grantScopeFor)(event, call.toolName, call.toolArgs, operation, context, (0, devConfirm_1.grantRuleFactsFor)(source));
|
|
824
826
|
const grantLimits = (0, devConfirm_1.resolveGrantLimits)(source.grantCaps, (0, runtimeConfig_1.getCachedDeveloperGrantPolicy)());
|
|
825
827
|
const grant = source.remember === 'none' ? undefined : (0, devConfirm_1.findDeveloperGrant)(grantScope, { policyId: source.policyId, matchedRule: source.matchedRule, policyHash: memoryHash }, grantLimits);
|
|
826
828
|
if (grant) {
|
package/dist/devConfirm.d.ts
CHANGED
|
@@ -334,7 +334,38 @@ export declare function listDeveloperGrants(now?: Date): DeveloperGrant[];
|
|
|
334
334
|
export declare function revokeDeveloperGrants(key?: string): number;
|
|
335
335
|
/** One line a person can read on the button and in the grant list: what "Always allow" covers. */
|
|
336
336
|
export declare function describeGrantScope(scope: GrantScope): string;
|
|
337
|
-
|
|
337
|
+
/**
|
|
338
|
+
* Which line-level facts the RULE that asked actually depends on. A grant binds
|
|
339
|
+
* the facts the rule fired on and nothing else: `gcloud_access_token_cmd` fired
|
|
340
|
+
* on `gcloud auth print-access-token`, so where a later `Invoke-RestMethod` in
|
|
341
|
+
* the same line posts (`logging.` vs `monitoring.googleapis.com`) and whether
|
|
342
|
+
* the engine classed the line `write` or `SHELL` must not partition the grant —
|
|
343
|
+
* that is what made every Always allow ask again (this machine, 2026-09-14..20).
|
|
344
|
+
* A rule that IS about the host (egress policy, SSRF, exfiltration, reverse
|
|
345
|
+
* shell) keeps the host in the grant so `evil.example` never rides on it.
|
|
346
|
+
*/
|
|
347
|
+
export interface GrantRuleFacts {
|
|
348
|
+
bindsDestination: boolean;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Derive `GrantRuleFacts` from the source of the ask. Unknown = bound (the
|
|
352
|
+
* narrower grant), so a policy the cache no longer holds never widens anything.
|
|
353
|
+
*/
|
|
354
|
+
export declare function grantRuleFactsFor(source: {
|
|
355
|
+
localFinding?: {
|
|
356
|
+
category?: string;
|
|
357
|
+
};
|
|
358
|
+
policyId?: string;
|
|
359
|
+
policies?: Array<{
|
|
360
|
+
id: string;
|
|
361
|
+
rules?: Array<{
|
|
362
|
+
constraints?: Array<{
|
|
363
|
+
field: string;
|
|
364
|
+
}>;
|
|
365
|
+
}>;
|
|
366
|
+
}>;
|
|
367
|
+
}): GrantRuleFacts;
|
|
368
|
+
export declare function grantScopeFor(event: string, toolName: string, toolArgs: Record<string, unknown> | undefined, operation: string, context: Record<string, unknown> | undefined, facts?: GrantRuleFacts): GrantScope;
|
|
338
369
|
/**
|
|
339
370
|
* Is this exact action covered in the same session and policy context?
|
|
340
371
|
* The 1h choice is a shorter expiry, never authorization for another session.
|
package/dist/devConfirm.js
CHANGED
|
@@ -61,6 +61,7 @@ exports.recordDeveloperGrant = recordDeveloperGrant;
|
|
|
61
61
|
exports.listDeveloperGrants = listDeveloperGrants;
|
|
62
62
|
exports.revokeDeveloperGrants = revokeDeveloperGrants;
|
|
63
63
|
exports.describeGrantScope = describeGrantScope;
|
|
64
|
+
exports.grantRuleFactsFor = grantRuleFactsFor;
|
|
64
65
|
exports.grantScopeFor = grantScopeFor;
|
|
65
66
|
exports.isConfirmationRemembered = isConfirmationRemembered;
|
|
66
67
|
/**
|
|
@@ -1200,7 +1201,25 @@ function scriptGrantKeys(command, cwd) {
|
|
|
1200
1201
|
});
|
|
1201
1202
|
return [...new Set(keys)].sort();
|
|
1202
1203
|
}
|
|
1203
|
-
|
|
1204
|
+
/** Local Safety categories whose finding is the destination host itself. */
|
|
1205
|
+
const DESTINATION_BOUND_CATEGORIES = new Set(['metadata_ssrf', 'secret_exfiltration', 'reverse_shell']);
|
|
1206
|
+
/**
|
|
1207
|
+
* Derive `GrantRuleFacts` from the source of the ask. Unknown = bound (the
|
|
1208
|
+
* narrower grant), so a policy the cache no longer holds never widens anything.
|
|
1209
|
+
*/
|
|
1210
|
+
function grantRuleFactsFor(source) {
|
|
1211
|
+
if (source.localFinding) {
|
|
1212
|
+
return { bindsDestination: DESTINATION_BOUND_CATEGORIES.has(String(source.localFinding.category || '')) };
|
|
1213
|
+
}
|
|
1214
|
+
const policy = source.policyId ? source.policies?.find(p => p.id === source.policyId) : undefined;
|
|
1215
|
+
if (!policy)
|
|
1216
|
+
return { bindsDestination: true };
|
|
1217
|
+
const bindsDestination = (policy.rules || []).some(rule => (rule.constraints || []).some(c => typeof c.field === 'string' && c.field.startsWith('destination.')));
|
|
1218
|
+
return { bindsDestination };
|
|
1219
|
+
}
|
|
1220
|
+
/** Grants made without rule facts (MCP gateway, older callers) keep the narrow, host-bound shape. */
|
|
1221
|
+
const DEFAULT_GRANT_RULE_FACTS = { bindsDestination: true };
|
|
1222
|
+
function grantScopeFor(event, toolName, toolArgs, operation, context, facts = DEFAULT_GRANT_RULE_FACTS) {
|
|
1204
1223
|
const scope = { event, toolName, operation: String(operation || '').trim() || 'unknown' };
|
|
1205
1224
|
if (event === 'shell') {
|
|
1206
1225
|
const command = typeof toolArgs?.command === 'string' ? toolArgs.command : typeof toolArgs?.cmd === 'string' ? toolArgs.cmd : '';
|
|
@@ -1209,9 +1228,14 @@ function grantScopeFor(event, toolName, toolArgs, operation, context) {
|
|
|
1209
1228
|
const scripts = scriptGrantKeys(command, cwd);
|
|
1210
1229
|
if (scripts.length)
|
|
1211
1230
|
scope.scripts = scripts;
|
|
1231
|
+
// A shell line's identity is its programs. The engine verb (`write` for a
|
|
1232
|
+
// POST, `SHELL` otherwise) describes the line, not the app the rule asked
|
|
1233
|
+
// about, and it changed the key between two `gcloud … ; Invoke-RestMethod`
|
|
1234
|
+
// lines that fired the very same rule. One verb for every shell grant.
|
|
1235
|
+
scope.operation = 'SHELL';
|
|
1212
1236
|
}
|
|
1213
1237
|
const domain = context?.['destination.domain'];
|
|
1214
|
-
if (typeof domain === 'string' && domain.trim())
|
|
1238
|
+
if (facts.bindsDestination && typeof domain === 'string' && domain.trim())
|
|
1215
1239
|
scope.destinationDomain = domain.trim().toLowerCase();
|
|
1216
1240
|
return scope;
|
|
1217
1241
|
}
|
package/dist/version.json
CHANGED