|
| 1 | +// Base catch-all permit. Without it, cedarpy's default-deny would turn |
| 2 | +// every non-matching Cedar evaluation on this tier into a DENY decision, |
| 3 | +// making the soft tier indistinguishable from hard-deny. With it, Cedar |
| 4 | +// returns ALLOW (no matching forbid) and our engine's STEP 3 sees only |
| 5 | +// the genuine forbid hits as REQUIRE_APPROVAL. |
| 6 | +@rule_id("base_permit") |
| 7 | +permit (principal, action, resource); |
| 8 | + |
| 9 | +// Built-in soft-deny policy set for Cedar HITL engine. |
| 10 | +// |
| 11 | +// Soft-deny is the HUMAN-IN-THE-LOOP surface: matching rules pause the |
| 12 | +// tool call, write an approval request to DynamoDB, and await a human |
| 13 | +// response via `bgagent approve` / `bgagent deny`. See |
| 14 | +// docs/design/CEDAR_HITL_GATES.md §§2, 6, 15.4. |
| 15 | +// |
| 16 | +// Every rule in this file MUST carry: |
| 17 | +// @tier("soft") |
| 18 | +// @rule_id("...") — stable ID for --pre-approve rule:X |
| 19 | +// @approval_timeout_s — integer seconds >= 30 (<120 emits WARN per IMPL-25) |
| 20 | +// @severity — "low" | "medium" | "high" |
| 21 | +// @category — optional free-form UX grouping |
| 22 | +// |
| 23 | +// Blueprints may OPT OUT of specific rules here via |
| 24 | +// `security.cedarPolicies.disable: [rule_id]`. They may NOT disable any |
| 25 | +// rule in hard_deny.cedar (blueprint loader rejects those at task start). |
| 26 | + |
| 27 | +// Gate any git --force / -f push. 300s default approval window, medium severity. |
| 28 | +// Covers both long-form (--force) and short-form (-f) variants, including |
| 29 | +// the bare `git push -f` invocation with no branch argument. |
| 30 | +@tier("soft") |
| 31 | +@rule_id("force_push_any") |
| 32 | +@approval_timeout_s("300") |
| 33 | +@severity("medium") |
| 34 | +@category("destructive") |
| 35 | +forbid (principal, action == Agent::Action::"execute_bash", resource) |
| 36 | +when { context.command like "*git push --force*" |
| 37 | + || context.command like "*git push -f *" |
| 38 | + || context.command like "*git push -f" }; |
| 39 | + |
| 40 | +// Force-push to main/prod specifically — longer window, higher severity. |
| 41 | +// Multi-match with force_push_any is expected: the engine's annotation |
| 42 | +// merging picks min(300, 600)=300s and max(medium, high)=high. |
| 43 | +@tier("soft") |
| 44 | +@rule_id("force_push_main") |
| 45 | +@approval_timeout_s("600") |
| 46 | +@severity("high") |
| 47 | +@category("destructive") |
| 48 | +forbid (principal, action == Agent::Action::"execute_bash", resource) |
| 49 | +when { context.command like "*git push --force origin main*" |
| 50 | + || context.command like "*git push --force origin prod*" |
| 51 | + || context.command like "*git push -f origin main*" |
| 52 | + || context.command like "*git push -f origin prod*" }; |
| 53 | + |
| 54 | +// Non-force pushes to protected branches — catches the case where an |
| 55 | +// agent bypasses PR workflow by pushing directly. |
| 56 | +@tier("soft") |
| 57 | +@rule_id("push_to_protected_branch") |
| 58 | +@approval_timeout_s("300") |
| 59 | +@severity("medium") |
| 60 | +@category("destructive") |
| 61 | +forbid (principal, action == Agent::Action::"execute_bash", resource) |
| 62 | +when { context.command like "*git push origin main*" |
| 63 | + || context.command like "*git push origin master*" |
| 64 | + || context.command like "*git push origin prod*" |
| 65 | + || context.command like "*git push origin release/*" }; |
| 66 | + |
| 67 | +// Writes to `.env` files typically contain secrets. 600s window, high severity. |
| 68 | +@tier("soft") |
| 69 | +@rule_id("write_env_files") |
| 70 | +@approval_timeout_s("600") |
| 71 | +@severity("high") |
| 72 | +@category("filesystem") |
| 73 | +forbid (principal, action == Agent::Action::"write_file", resource) |
| 74 | +when { context.file_path like "*.env" }; |
| 75 | + |
| 76 | +// Writes to any path containing "credentials" — SSH keys, AWS creds, |
| 77 | +// service-account JSON, etc. 300s window, high severity. |
| 78 | +@tier("soft") |
| 79 | +@rule_id("write_credentials") |
| 80 | +@approval_timeout_s("300") |
| 81 | +@severity("high") |
| 82 | +@category("auth") |
| 83 | +forbid (principal, action == Agent::Action::"write_file", resource) |
| 84 | +when { context.file_path like "*credentials*" }; |
0 commit comments