ADR 023: OpenSSF Scorecard (Repo Security Posture)

This legacy URL now resolves to the canonical ADR.

Context

The security stack now covers source code (CodeQL, ADR 032), dependencies and IaC (Trivy, ADR 047), and workflow content (zizmor, ADR 049). Each watches files in the repository. None watches the repository's own configuration: branch protection, the default GITHUB_TOKEN permission, whether a security policy exists, whether releases are signed.

That configuration is exactly what decays silently. zizmor already flags an unpinned action or an over-broad permissions: block within a changed workflow file, on the PR that introduces it. But a branch-protection rule relaxed during an incident, or the default token permission widened in repo settings, lives in GitHub settings, not in any file a scanner reads. Every existing tool assumes that repo-level ground rather than checking it.

This repo is also public and doubles as a portfolio, so its posture is part of what it demonstrates. Much of the hardening Scorecard measures, SHA-pinned actions, scoped permissions, active dependency-update tooling, is already done deliberately (ADRs 013, 018, 024, 032, 047, 049), with nothing yet measuring or displaying it.

Decision

Adopt OpenSSF Scorecard via its official GitHub Action:

  • A scheduled weekly workflow (Tuesdays, Trivy owns Mondays), also triggered on pushes to main and branch-protection changes, uploading SARIF to the GitHub Security tab like Trivy and zizmor. Findings live where every other scanner's findings live, no new console.
  • publish_results: true, so the score is verifiable at scorecard.dev and the README carries a live badge alongside the SonarQube ones.
  • Actions SHA-pinned like everything else (ADR 049); Renovate keeps the digests fresh.

Scorecard is a meta-check: it is deliberately not blocking on PRs. Its findings record existing posture weaknesses and regressions for weekly triage. Treat them like Trivy's scheduled scans in the Security tab. Workflow permissions live in version-controlled YAML and should be fixed promptly through PRs. Branch protection is a repository setting and needs separate remediation.

Why It Adds Something New

Every existing scanner answers "is this artifact safe?"; Scorecard answers "is this repository configured so that unsafe artifacts get caught?" It audits the layer the other tools depend on. The overlaps are intentional agreement rather than redundancy: its pinned-dependencies check confirms what zizmor enforces per-PR, its SAST/dependency-update checks confirm CodeQL and Renovate are actually wired in and running. Where zizmor audits workflow content, Scorecard audits repo settings. Adjacent layers, no substitute relationship in either direction.

Alternatives Considered

ToolRoleProsConsVerdict
OpenSSF Scorecard (accepted)Posture meta-checkIndustry-standard rubric (OpenSSF/Linux Foundation); SARIF + badge; zero config to maintain.Some checks are heuristic; a few (fuzzing, best-practices badge) aren't worth pursuing for this stack and stay red by choice.Accepted.
GitHub security overviewNative settings pageZero setup.A dashboard to remember to read, not a check with history, alerts, or a public score.Rejected: watch-it-or-lose-it.
allstar (OpenSSF)Posture enforcementCan auto-enforce policies.An installed GitHub App with write access. Heavier trust footprint than a read-only weekly scan warrants here.Rejected: Less Is More.
Manual periodic reviewn/aNo tooling.Exactly the silent-decay failure mode this closes.Rejected.

Where It Sits On The Adoption Curve

Early-to-late majority. Scorecard has run at scale since 2020, scores a million-plus repos, and its rubric is referenced by supply-chain frameworks (SLSA, S2C2F); the scoring heuristics still evolve between releases. Mature enough to trust, standard enough that the badge means something to a reader. It comfortably sits inside the Goldilocks zone.

Relation To Building Philosophy

  • Less Is More. One workflow file, one badge; findings in the same Security tab as Trivy and zizmor. The rejected alternative (allstar) would have added an app with write access.
  • Short Feedback Loops. A posture regression surfaces within a week, with the specific check and remediation named, instead of at the next manual audit (i.e. never).
  • Build Flywheels. The hardening work of ADRs 013–049 becomes a measured, maintained score rather than a one-time effort; each future repo inherits the rubric without setup.
  • Respect Goodhart's Law. Treat the score as a diagnostic. Checks we deliberately skip, e.g. fuzzing, whose payoff doesn't map onto a memory-safe TS/Python site, stay red rather than being gamed green, and the number is published so the incentive is honesty.

Gaps & Risks

  • Heuristic checks. Scorecard infers some practices (e.g. code review) from public signals and can under- or over-credit them; scores move between Scorecard releases. Accepted: the value is the trend and the named regressions, not the absolute number.
  • Weekly cadence. A posture regression can live for up to a week before the scheduled run flags it; the main-push and branch-protection triggers narrow the common cases.
  • Public score. A visibly imperfect score is by design. See Goodhart above.

Consequences

Positive

  • The repo-settings layer every other scanner assumes is now itself monitored, with regressions surfacing in the Security tab.
  • Existing hardening becomes externally verifiable (badge + scorecard.dev).
  • Free, no new platform, no config to maintain.

Negative

  • One more scheduled workflow and Security-tab category to triage.
  • A few rubric checks (e.g. fuzzing, best-practices badge) aren't worth pursuing for this stack and stay red by choice, capping the headline score below 10.