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
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "1.34.14"
2
+ "version": "1.34.16"
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.16",
4
4
  "description": "Full Court Defense CLI — security scanning for AI agents from your terminal",
5
5
  "main": "dist/index.js",
6
6
  "bin": {