Policy Engine Security Patterns
Policy Language Design
Required Properties
Every policy language should specify:
- Default action: What happens when no rule matches? Must be explicit (fail-closed).
- Evaluation order: First-match-wins, all-rules, priority-based?
- Rule semantics: AND (all fields must match) vs OR (any field matches)
- Version field: Required, with explicit rejection of unknown versions
- Field validation: Which values are valid for each field? Parse-time vs runtime validation?
Common Policy Engine Vulnerabilities
Fail-Open Defaults
- Missing
defaultfield silently allows everything - Defense:
defaultMUST be required; missing = parse error
Version Confusion
- Unknown policy version silently evaluated with v1 semantics
- Defense: Unknown version = hard parse error (exit code 10)
Rule Ordering Exploitation
- In first-match-wins: broad allow before specific deny bypasses restrictions
- In last-match-wins: deny rules can be overridden by later allows
- Defense: Document evaluation order explicitly with examples
AND vs OR Confusion
- When a rule has multiple fields (
signer_org+max_age_days):- AND: both must match for the rule to apply
- OR: either triggers the rule
- Defense: Explicitly document AND semantics within rules, OR across rules
Field Injection
- Extra/unknown fields in policy rules silently ignored
- Attacker adds fields that look restrictive but are actually no-ops
- Defense: Reject unknown fields at parse time
Type Coercion
- YAML auto-converts:
max_age_days: yes->max_age_days: true - Defense: Mandate YAML 1.2 where
yes/noare strings, validate field types
max_age_days Security
Self-Asserted Timestamp Bypass
rekor_timestampin sidecar is self-asserted- If evaluated without Rekor verification, attacker can backdate
- Defense:
max_age_daysMUST trigger strict Rekor verification automatically
Offline Mode Conflict
max_age_daysrequires network access to verifyrekor_timestamp- Defense:
--offline+max_age_days= exit code 10 (unless--offline-age-skip)
Zero/Negative Values
max_age_days: 0ormax_age_days: -1could cause logic errors- Defense: Must be positive integer; 0 and negative rejected at parse time
signer vs signer_org Semantics
Scope Distinction
signer: exact URL match (e.g.,https://github.com/user)signer_org: matches first path segment of Actions SANs only- Individuals (single path segment) CANNOT match
signer_org
Why This Matters
- Prevents individual users from matching org-level trust rules
- CI/Actions SANs have org as first path segment:
/{org}/{repo}/... - Individual SANs have username as only path segment:
/{username}
require_signer_id_match
Evaluation Order
- Applied AFTER all verification steps
- Applied BEFORE policy rules
- Produces exit code 1 (not 3/POLICY_FAIL)
Edge Cases
- What if
skill_idhost differs from signer URL host? - What if owner segment extraction fails?
- How are GitHub Actions workflow paths parsed for owner?
Policy File Security
Unsigned Policy Problem
- Policy file is not signed or integrity-protected
- Attacker with filesystem access can modify policy
- Defense: Document this clearly; recommend VCS protection
Policy File Location Trust
- Policy file path is user-specified (
--policy <file>) - Or auto-discovered in project directory
- TOCTOU: policy file could change between discovery and evaluation
- Defense: Read and parse once at startup
Policy Composition
- Multiple policy files? Merge semantics?
- In v0.1: single policy file only
- Defense: Reject multiple policy sources or document merge behavior
Review Checklist
- Is
defaultaction required? What happens when omitted? - Is
versionrequired? What happens with unknown version? - Is rule evaluation order (first-match-wins) documented?
- Are multi-field rule semantics (AND) documented?
- Are unknown/extra fields rejected at parse time?
- Does
max_age_daysauto-enable strict Rekor verification? - Is
max_age_dayswith--offlinehandled (exit 10)? - Are
max_age_daysbounds validated (positive integer)? - Is
signer_orgmatching limited to Actions SANs? - Is
require_signer_id_matchevaluation order specified? - Is the unsigned policy file documented as a security risk?
- Is the
signerfield format validated (full HTTPS URL required)?