Context
Poutine runs today only via pr-validation.yml, which triggers on pull_request and workflow_dispatch — never on push to main. It therefore only ever analyzes PR merge refs, and its findings live and die with the PR.
Consequence: we have no standing Poutine coverage of main. Anything already merged has never been scanned. At time of writing, main has 3 open code scanning alerts, all from Scorecard, and zero from Poutine — not because main is clean, but because Poutine has never looked at it.
This was discovered while triaging code scanning alert #348 (default_permissions_on_risky_events on .github/workflows/pr-review.lock.yml), which was dismissed as a false positive: that file declares workflow-level permissions: {} (zero scopes) inherited by every job, but Poutine's utils.empty() helper cannot distinguish an empty permissions map from an absent one.
Scope
- Run a full Poutine scan against
main (poutine analyze_local . or a one-off workflow_dispatch run) and capture the complete finding set.
- Triage each finding: genuine issue, false positive, or accepted risk.
- Address genuine findings in a follow-up PR.
- Record false positives as scoped
skip entries in .poutine.yml with written justification, or dismiss with a documented reason.
- Decide whether Poutine should gain standing coverage of
main — e.g. a scheduled run in weekly-security-maintenance.yml — so findings on merged code surface without waiting for an unrelated PR to touch them.
Notes
- Poutine is advisory by design here:
poutine-soft-fail: true is set in pr-validation.yml, so it cannot block a merge. The homegrown template-injection gate remains the only hard gate. See docs/security/dangerous-workflow-detection.md.
- The action is SHA-pinned (
boostsecurityio/poutine-action@e240ebd3eff8b2db5a8e5f6b28f58739d7db2247, v1.1.4), so the rule set only changes when that pin is bumped.
skip entries in .poutine.yml match on fields the rule actually emits. default_permissions_on_risky_events emits only path and event_triggers — not job — so suppression for that rule cannot be scoped narrower than an entire file. Prefer fixing or dismissing over broad skips.
Related follow-up
scripts/security/Test-WorkflowPermissions.ps1 currently only greps for a top-level ^permissions: block, so it scores 100% while individual jobs carry no declaration. Teaching it to walk jobs — and to distinguish workflow-level {} (provably zero) from an absent block (inherits repo/org defaults) — would enforce the job-level convention in .github/instructions/workflows.instructions.md as a hard gate. Worth pairing with this work or splitting into its own issue.
Context
Poutine runs today only via
pr-validation.yml, which triggers onpull_requestandworkflow_dispatch— never onpushtomain. It therefore only ever analyzes PR merge refs, and its findings live and die with the PR.Consequence: we have no standing Poutine coverage of
main. Anything already merged has never been scanned. At time of writing,mainhas 3 open code scanning alerts, all from Scorecard, and zero from Poutine — not becausemainis clean, but because Poutine has never looked at it.This was discovered while triaging code scanning alert #348 (
default_permissions_on_risky_eventson.github/workflows/pr-review.lock.yml), which was dismissed as a false positive: that file declares workflow-levelpermissions: {}(zero scopes) inherited by every job, but Poutine'sutils.empty()helper cannot distinguish an empty permissions map from an absent one.Scope
main(poutine analyze_local .or a one-offworkflow_dispatchrun) and capture the complete finding set.skipentries in.poutine.ymlwith written justification, or dismiss with a documented reason.main— e.g. a scheduled run inweekly-security-maintenance.yml— so findings on merged code surface without waiting for an unrelated PR to touch them.Notes
poutine-soft-fail: trueis set inpr-validation.yml, so it cannot block a merge. The homegrown template-injection gate remains the only hard gate. See docs/security/dangerous-workflow-detection.md.boostsecurityio/poutine-action@e240ebd3eff8b2db5a8e5f6b28f58739d7db2247, v1.1.4), so the rule set only changes when that pin is bumped.skipentries in.poutine.ymlmatch on fields the rule actually emits.default_permissions_on_risky_eventsemits onlypathandevent_triggers— notjob— so suppression for that rule cannot be scoped narrower than an entire file. Prefer fixing or dismissing over broad skips.Related follow-up
scripts/security/Test-WorkflowPermissions.ps1currently only greps for a top-level^permissions:block, so it scores 100% while individual jobs carry no declaration. Teaching it to walk jobs — and to distinguish workflow-level{}(provably zero) from an absent block (inherits repo/org defaults) — would enforce the job-level convention in.github/instructions/workflows.instructions.mdas a hard gate. Worth pairing with this work or splitting into its own issue.