fullcourtdefense-cli 1.34.14 → 1.34.15

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();
@@ -7443,9 +7459,13 @@ function evaluateActionPolicies(policies, toolName, operation, context = {}, dec
7443
7459
  const ruleMatches = (rule) => candidateOperations.some((candidate) => operationMatchesRule(candidate, rule.operations, matchText, toolCaps)) && (!rule.constraints?.length || rule.constraints.every((c) => evaluateConstraint(c, context)));
7444
7460
  const replacedApprovalPolicies = /* @__PURE__ */ new Set();
7445
7461
  const appliedApprovalExceptions = /* @__PURE__ */ new Set();
7462
+ const roleExceptions = [];
7463
+ const appliedRoleExceptions = /* @__PURE__ */ new Set();
7464
+ let grantingRoleException;
7446
7465
  for (const policy of policies) {
7447
7466
  if (!policyApplies(policy) || policy.stage === "monitor") continue;
7448
7467
  for (const rule of policy.rules) {
7468
+ if (rule.roleException && !roleExceptionError(rule) && ruleMatches(rule)) roleExceptions.push({ policy, rule });
7449
7469
  if (rule.verdict !== "require_approval" || rule.approval?.scope !== "developer" || !ruleMatches(rule)) continue;
7450
7470
  if (approvalExceptionError(rule) || !Array.isArray(rule.approval.replacesPolicyIds)) continue;
7451
7471
  for (const id of rule.approval.replacesPolicyIds) {
@@ -7463,6 +7483,15 @@ function evaluateActionPolicies(policies, toolName, operation, context = {}, dec
7463
7483
  const isMonitorStage = policy.stage === "monitor";
7464
7484
  for (const rule of policy.rules) {
7465
7485
  if (!ruleMatches(rule)) continue;
7486
+ if (!isMonitorStage && policy.resourceType === "machine_role" && policy.enforcement !== "hard" && rule.roleVerb && (rule.verdict === "block" || rule.verdict === "require_approval")) {
7487
+ const roleId = policy.id.startsWith("machine-role:") ? policy.id.split(":")[1] : "";
7488
+ const exception = roleExceptions.find((item) => item.rule.roleException.roleIds.includes(roleId) && item.rule.roleException.verbs.includes(rule.roleVerb));
7489
+ if (exception) {
7490
+ grantingRoleException = exception.policy;
7491
+ appliedRoleExceptions.add(`${exception.policy.name} (${exception.policy.id}) relaxes ${policy.name}: ${rule.roleVerb}`);
7492
+ continue;
7493
+ }
7494
+ }
7466
7495
  if (!isMonitorStage && rule.verdict === "require_approval" && rule.approval?.scope !== "developer" && policy.enforcement !== "hard" && replacedApprovalPolicies.has(policy.id)) {
7467
7496
  appliedApprovalExceptions.add(`${policy.name} (${policy.id})`);
7468
7497
  continue;
@@ -7540,6 +7569,13 @@ function evaluateActionPolicies(policies, toolName, operation, context = {}, dec
7540
7569
  if (monitorMatches.length > 0) {
7541
7570
  worstResult = { ...worstResult, monitorMatches };
7542
7571
  }
7572
+ if (appliedRoleExceptions.size) {
7573
+ worstResult = {
7574
+ ...worstResult,
7575
+ ...worstResult.verdict === "allow" && grantingRoleException ? { policyId: grantingRoleException.id, policyName: grantingRoleException.name, resourceType: grantingRoleException.resourceType } : {},
7576
+ reason: `${worstResult.reason || worstResult.verdict}; explicit role exception: ${[...appliedRoleExceptions].sort().join("; ")}`
7577
+ };
7578
+ }
7543
7579
  if (skippedOrgPolicies.length > 0) {
7544
7580
  worstResult = { ...worstResult, skippedOrgPolicies };
7545
7581
  }
@@ -7692,6 +7728,7 @@ function inventoryToolMatchesActionPolicy(toolName, toolActions, policies) {
7692
7728
  normalizeMachineKind,
7693
7729
  policyTargetsMachineKind,
7694
7730
  resolveApprovalOptions,
7731
+ roleExceptionError,
7695
7732
  ruleNeedsContentFact,
7696
7733
  toolCapabilities
7697
7734
  });
package/dist/version.json CHANGED
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.34.14"
2
+ "version": "1.34.15"
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fullcourtdefense-cli",
3
- "version": "1.34.14",
3
+ "version": "1.34.15",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {