Skip to content

fix(security): enforce the privileged-event guard for caller-supplied refs - #62

Merged
rldyourmnd merged 2 commits into
mainfrom
fix/privileged-ref-guard
Aug 2, 2026
Merged

fix(security): enforce the privileged-event guard for caller-supplied refs#62
rldyourmnd merged 2 commits into
mainfrom
fix/privileged-ref-guard

Conversation

@rldyourmnd

Copy link
Copy Markdown
Contributor

Summary

Two changes, both closing gaps between what this repository claims and what it enforces.

  1. checkout_ref had no enforced safety rule. private-static.yml and cross-platform-smoke.yml are the only reusables that let the caller pick the checked-out commit. That is correct on pull_request (read-only token, no secrets) and unsafe on pull_request_target / workflow_run, where the job holds the caller's write token and secrets. The rule existed only as a sentence in the catalog risk notes and in three dismissed code-scanning alerts (chore: ignore local .serena/ Serena MCP config #1fix(release-supply-chain): migrate SBOM attestation to actions/attest #3) that resurrected as fix(workflows): enforce private-free action boundary #4feat(release-supply-chain)!: close release artifact boundary #6 when the files were next edited — a triage loop, not a control.

  2. Eight files described a merge method the repository rejects. Corrected to match the declared ruleset and live settings.

Type of change

  • Hardening / security posture improvement
  • Docs only (second commit)

Threat-model note

A reusable workflow inherits the caller's github context, so github.event_name is the event that triggered the calling run (contexts reference). Both workflows now open with a guard step that fails closed when a privileged event (pull_request_target, workflow_run, issue_comment, issues, discussion, discussion_comment) supplies a non-empty checkout_ref.

Properties, chosen to avoid the usual ways such a control fails open:

  • first step in the job — nothing runs before the refusal;
  • no opt-out input — a boolean that disables a security control is the control failing open (same doctrine as the unconditional Harden-Runner rule);
  • env indirectionCALLER_EVENT / CHECKOUT_REF, no ${{ }} inside run:;
  • shell: bash — identical behaviour on the Linux/macOS/Windows matrix, and no dependency on python3 being on PATH before setup-python;
  • empty-ref case untouched — a privileged caller that supplies no checkout_ref still legitimately checks out the base repository.

This reduces attack surface and introduces none: the guard only refuses combinations that were already documented as unsupported.

Permissions diff

None. No job's permissions: block changes; top-level stays permissions: {} in both files.

New enforcement

scripts/check_privileged_ref_guard.py, wired into validate_all as privileged-ref-guard, discovers every workflow exposing checkout_ref, asserts the guard is the first step of every checking-out job, and then executes the extracted guard body against the full privileged/safe event matrix. A future reusable that adds checkout_ref without the guard fails the gate. Verified it is not vacuous: a stub guard that always exits 0 is reported as a failure.

Docs correction

.github/rulesets/branch-main.json declares "allowed_merge_methods": ["merge"], and the live settings agree — allow_squash_merge: false, allow_rebase_merge: false, allow_merge_commit: true, matching the estate-wide atomic-commit policy. README.md, SECURITY.md, AGENTS.md, .claude/CLAUDE.md, docs/08-governance-rulesets.md, and the nddev-change-flow / nddev-release-flow skills all said squash-only. Corrected in the authored sources and remirrored via sync_skills.py.

Checklist

  • Full-SHA pins unchanged
  • Least-privilege permissions (top-level {}, per-job minimal)
  • timeout-minutes present on changed jobs
  • persist-credentials: false unchanged
  • No ${{ }} inside run:
  • No paid actions added to private-free/cross-tier files
  • actionlint passes on both changed files (exit=0)
  • zizmor --persona regular --min-severity low — "No findings to report"
  • catalog/capabilities.yml risk notes + docs/02-private-free.md updated
  • CHANGELOG.md updated under [Unreleased]
  • Commits signed (-S) and signed off (-s), Conventional Commits
  • python3 scripts/validate_all.pyOK (all 19 checks)

Tier impact

  • Public (free OSS suite)
  • Private free tier
  • Private paid tier

Both workflows are unverified in catalog/runtime-coverage.yml, so no proven_digest re-proof is required.

…vents

private-static.yml and cross-platform-smoke.yml are the only reusables that
let the caller choose the checked-out commit. That is safe on pull_request
and unsafe on pull_request_target or workflow_run, where the job holds the
caller's write token and secrets. The rule lived only in a risk note and in
three dismissed code-scanning alerts, so nothing enforced it.

A reusable inherits the caller's github context, so github.event_name is the
calling run's event. Both workflows now open with a fail-closed guard that
refuses a non-empty checkout_ref on a privileged event, with no opt-out input
and env indirection instead of ${{ }} in run:.

check_privileged_ref_guard.py discovers every workflow exposing checkout_ref,
asserts the guard is the first step, and executes the extracted guard body
across the privileged/safe event matrix.

Signed-off-by: Danil Silantyev <danilsilantyevwork@gmail.com>
.github/rulesets/branch-main.json declares allowed_merge_methods: ["merge"]
and the live settings agree (allow_squash_merge and allow_rebase_merge are
both false), matching the estate-wide atomic-commit policy. README, SECURITY,
AGENTS, .claude/CLAUDE.md, docs/08-governance-rulesets.md and the
nddev-change-flow / nddev-release-flow skills all claimed squash-only, which
pointed every agent at a merge method the repository rejects.

Signed-off-by: Danil Silantyev <danilsilantyevwork@gmail.com>
@rldyourmnd
rldyourmnd merged commit 9f17b6f into main Aug 2, 2026
11 checks passed
@rldyourmnd
rldyourmnd deleted the fix/privileged-ref-guard branch August 2, 2026 08:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant