fullcourtdefense-cli 1.34.14 → 1.34.16
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.
|
@@ -37,6 +37,13 @@ export interface ResolvedActionPolicyApproval {
|
|
|
37
37
|
onTimeout: ActionPolicyApprovalOnTimeout;
|
|
38
38
|
}
|
|
39
39
|
export interface ActionPolicyRule {
|
|
40
|
+
/** Explicit, narrowly scoped permission to relax named role actions only. */
|
|
41
|
+
roleException?: {
|
|
42
|
+
roleIds: string[];
|
|
43
|
+
verbs: string[];
|
|
44
|
+
};
|
|
45
|
+
/** Set only by the role compiler; never accepted from policy authors. */
|
|
46
|
+
roleVerb?: string;
|
|
40
47
|
operations: string[];
|
|
41
48
|
verdict: ActionPolicyVerdict;
|
|
42
49
|
constraints?: ActionPolicyConstraint[];
|
|
@@ -45,6 +52,7 @@ export interface ActionPolicyRule {
|
|
|
45
52
|
}
|
|
46
53
|
/** Reject ambiguous/broad approval exceptions at authoring time; runtime also fails closed on them. */
|
|
47
54
|
export declare function approvalExceptionError(rule: ActionPolicyRule): string | undefined;
|
|
55
|
+
export declare function roleExceptionError(rule: ActionPolicyRule): string | undefined;
|
|
48
56
|
/**
|
|
49
57
|
* The three kinds of machine a fleet policy can be written for:
|
|
50
58
|
* - `workstation` — laptops / desktops a person uses (default for any machine that does not say otherwise)
|
|
@@ -35,6 +35,7 @@ __export(actionPolicyEngine_exports, {
|
|
|
35
35
|
normalizeMachineKind: () => normalizeMachineKind,
|
|
36
36
|
policyTargetsMachineKind: () => policyTargetsMachineKind,
|
|
37
37
|
resolveApprovalOptions: () => resolveApprovalOptions,
|
|
38
|
+
roleExceptionError: () => roleExceptionError,
|
|
38
39
|
ruleNeedsContentFact: () => ruleNeedsContentFact,
|
|
39
40
|
toolCapabilities: () => toolCapabilities
|
|
40
41
|
});
|
|
@@ -6205,6 +6206,8 @@ function provenPowerShellQueryUrls(command) {
|
|
|
6205
6206
|
}
|
|
6206
6207
|
}
|
|
6207
6208
|
function approvalExceptionError(rule) {
|
|
6209
|
+
const roleError = roleExceptionError(rule);
|
|
6210
|
+
if (roleError) return roleError;
|
|
6208
6211
|
const ids = rule.approval?.replacesPolicyIds;
|
|
6209
6212
|
if (ids === void 0) return void 0;
|
|
6210
6213
|
if (!Array.isArray(ids) || ids.length > 50 || ids.some((id) => typeof id !== "string" || !id.trim())) {
|
|
@@ -6219,6 +6222,19 @@ function approvalExceptionError(rule) {
|
|
|
6219
6222
|
}
|
|
6220
6223
|
return void 0;
|
|
6221
6224
|
}
|
|
6225
|
+
function roleExceptionError(rule) {
|
|
6226
|
+
const exception = rule.roleException;
|
|
6227
|
+
if (exception === void 0) return void 0;
|
|
6228
|
+
if (!exception || rule.verdict !== "allow") return "A role exception must use Allow.";
|
|
6229
|
+
const valid = (values, permitted) => Array.isArray(values) && values.length > 0 && values.length <= permitted.length && values.every((value) => typeof value === "string" && permitted.includes(value));
|
|
6230
|
+
if (!valid(exception.roleIds, ["read_only", "standard", "power", "unrestricted"]) || !valid(exception.verbs, ["read", "write", "delete", "drop", "deploy", "export", "send", "payment"])) {
|
|
6231
|
+
return "Select valid roles and role actions for the exception.";
|
|
6232
|
+
}
|
|
6233
|
+
if (!["agentName", "toolName"].every((field) => rule.constraints?.some((c) => c.field === field && c.operator === "equals" && typeof c.value === "string" && c.value.trim() && !/[?*]/.test(c.value)))) {
|
|
6234
|
+
return "A role exception must identify an exact agent and exact tool.";
|
|
6235
|
+
}
|
|
6236
|
+
return void 0;
|
|
6237
|
+
}
|
|
6222
6238
|
var MACHINE_KINDS = ["workstation", "ci", "workload"];
|
|
6223
6239
|
function normalizeMachineKind(value) {
|
|
6224
6240
|
const kind = (value || "").trim().toLowerCase();
|
|
@@ -7316,6 +7332,11 @@ function classifyShellWords(masked, raw, lead) {
|
|
|
7316
7332
|
return "read";
|
|
7317
7333
|
}
|
|
7318
7334
|
if (lead === "git" && /\bgit\s+(reset\s+--hard|clean\s+-[a-z]*f[a-z]*)\b/.test(value)) return "write";
|
|
7335
|
+
if (lead === "git" && /^git\s+ls-remote(?=\s|$)/.test(value)) {
|
|
7336
|
+
const options = (raw.match(/"[^"\r\n]*"|'[^'\r\n]*'|[^\s]+/g) || []).map((token) => token.replace(/^["']|["']$/g, "")).filter((token) => token.startsWith("--")).map((token) => token.split("=", 1)[0]);
|
|
7337
|
+
if (options.some((option) => !/^--(?:quiet|heads|tags|refs|get-url|exit-code|symref|sort)$/i.test(option)) || /\bext::|[$`]/i.test(raw)) return "SHELL";
|
|
7338
|
+
return "read";
|
|
7339
|
+
}
|
|
7319
7340
|
if (lead === "git" && /\bgit\s+(?:status|diff|log|show|blame|clone|fetch|pull|ls-files|remote|branch(?!\s+-[dD])|describe|rev-parse|stash\s+(?:list|show)|tag$)\b/.test(value)) return "read";
|
|
7320
7341
|
if (lead === "gh" && /\bgh\s+(?:pr|issue|repo|run|release|workflow)\s+(?:view|list|status|checks|diff|download)\b/.test(value)) return "read";
|
|
7321
7342
|
if (lead === "gh" && /\bgh\s+auth\s+status\b/.test(value) && !/--show-token|\s-t\b/.test(value)) return "read";
|
|
@@ -7443,9 +7464,13 @@ function evaluateActionPolicies(policies, toolName, operation, context = {}, dec
|
|
|
7443
7464
|
const ruleMatches = (rule) => candidateOperations.some((candidate) => operationMatchesRule(candidate, rule.operations, matchText, toolCaps)) && (!rule.constraints?.length || rule.constraints.every((c) => evaluateConstraint(c, context)));
|
|
7444
7465
|
const replacedApprovalPolicies = /* @__PURE__ */ new Set();
|
|
7445
7466
|
const appliedApprovalExceptions = /* @__PURE__ */ new Set();
|
|
7467
|
+
const roleExceptions = [];
|
|
7468
|
+
const appliedRoleExceptions = /* @__PURE__ */ new Set();
|
|
7469
|
+
let grantingRoleException;
|
|
7446
7470
|
for (const policy of policies) {
|
|
7447
7471
|
if (!policyApplies(policy) || policy.stage === "monitor") continue;
|
|
7448
7472
|
for (const rule of policy.rules) {
|
|
7473
|
+
if (rule.roleException && !roleExceptionError(rule) && ruleMatches(rule)) roleExceptions.push({ policy, rule });
|
|
7449
7474
|
if (rule.verdict !== "require_approval" || rule.approval?.scope !== "developer" || !ruleMatches(rule)) continue;
|
|
7450
7475
|
if (approvalExceptionError(rule) || !Array.isArray(rule.approval.replacesPolicyIds)) continue;
|
|
7451
7476
|
for (const id of rule.approval.replacesPolicyIds) {
|
|
@@ -7463,6 +7488,15 @@ function evaluateActionPolicies(policies, toolName, operation, context = {}, dec
|
|
|
7463
7488
|
const isMonitorStage = policy.stage === "monitor";
|
|
7464
7489
|
for (const rule of policy.rules) {
|
|
7465
7490
|
if (!ruleMatches(rule)) continue;
|
|
7491
|
+
if (!isMonitorStage && policy.resourceType === "machine_role" && policy.enforcement !== "hard" && rule.roleVerb && (rule.verdict === "block" || rule.verdict === "require_approval")) {
|
|
7492
|
+
const roleId = policy.id.startsWith("machine-role:") ? policy.id.split(":")[1] : "";
|
|
7493
|
+
const exception = roleExceptions.find((item) => item.rule.roleException.roleIds.includes(roleId) && item.rule.roleException.verbs.includes(rule.roleVerb));
|
|
7494
|
+
if (exception) {
|
|
7495
|
+
grantingRoleException = exception.policy;
|
|
7496
|
+
appliedRoleExceptions.add(`${exception.policy.name} (${exception.policy.id}) relaxes ${policy.name}: ${rule.roleVerb}`);
|
|
7497
|
+
continue;
|
|
7498
|
+
}
|
|
7499
|
+
}
|
|
7466
7500
|
if (!isMonitorStage && rule.verdict === "require_approval" && rule.approval?.scope !== "developer" && policy.enforcement !== "hard" && replacedApprovalPolicies.has(policy.id)) {
|
|
7467
7501
|
appliedApprovalExceptions.add(`${policy.name} (${policy.id})`);
|
|
7468
7502
|
continue;
|
|
@@ -7540,6 +7574,13 @@ function evaluateActionPolicies(policies, toolName, operation, context = {}, dec
|
|
|
7540
7574
|
if (monitorMatches.length > 0) {
|
|
7541
7575
|
worstResult = { ...worstResult, monitorMatches };
|
|
7542
7576
|
}
|
|
7577
|
+
if (appliedRoleExceptions.size) {
|
|
7578
|
+
worstResult = {
|
|
7579
|
+
...worstResult,
|
|
7580
|
+
...worstResult.verdict === "allow" && grantingRoleException ? { policyId: grantingRoleException.id, policyName: grantingRoleException.name, resourceType: grantingRoleException.resourceType } : {},
|
|
7581
|
+
reason: `${worstResult.reason || worstResult.verdict}; explicit role exception: ${[...appliedRoleExceptions].sort().join("; ")}`
|
|
7582
|
+
};
|
|
7583
|
+
}
|
|
7543
7584
|
if (skippedOrgPolicies.length > 0) {
|
|
7544
7585
|
worstResult = { ...worstResult, skippedOrgPolicies };
|
|
7545
7586
|
}
|
|
@@ -7692,6 +7733,7 @@ function inventoryToolMatchesActionPolicy(toolName, toolActions, policies) {
|
|
|
7692
7733
|
normalizeMachineKind,
|
|
7693
7734
|
policyTargetsMachineKind,
|
|
7694
7735
|
resolveApprovalOptions,
|
|
7736
|
+
roleExceptionError,
|
|
7695
7737
|
ruleNeedsContentFact,
|
|
7696
7738
|
toolCapabilities
|
|
7697
7739
|
});
|
package/dist/version.json
CHANGED