I turned OpenSSF Scorecard on expecting a pat on the head. go-tool-base is a security-minded project, I’m careful, surely the robot would agree. The robot did not agree. It handed back a report card with a fair bit of red ink, and the most pointed finding on it wasn’t about my code at all. It was about me.
A linter for the things you don’t call code
Scorecard is an automated set of checks that grades a repository’s supply-chain hygiene: are your CI dependencies pinned, are your workflow tokens least-privilege, is your branch protected, do commits get reviewed. It’s a linter, but pointed at the part of the project you don’t usually think of as code, the build and release machinery and the practices around them. And like any good linter, its value is mostly in catching the things you’d swear you’d already got right.
Three of its findings were worth the price of admission on their own.
Pin the actions you don’t control
The first was about how go-tool-base’s GitHub Actions referenced other actions. Like nearly everyone, I’d written uses: actions/checkout@v6. Scorecard doesn’t like that, and it’s right not to.
@v6 is a tag, and a tag is mutable. Whoever controls that action can move v6 to point at different code tomorrow, and your CI will pick it up silently on the next run. For an action that runs in a job holding your repository token, that’s a supply-chain hole the width of a barn door: compromise the tag, compromise every pipeline that trusts it. The fix is to pin to an immutable commit SHA, with the human-readable version left as a comment, which is exactly what I changed:
- - uses: actions/checkout@v6
- - uses: actions/setup-go@v6
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
+ - uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6
Now the action is frozen at bytes I reviewed. Dependabot still bumps the SHA when a real new version lands, so I get updates as reviewable pull requests rather than as silent tag movements. The pin doesn’t stop me updating. It stops me updating without noticing.
Give the workflow token the least it can do
The second finding was about permissions. My workflow declared its token permissions at the top, once, for the whole file:
permissions:
contents: read
security-events: write
id-token: write
That reads as careful, and it’s still too broad, because top-level permissions apply to every job in the workflow. A job that only needs to read the repo is now also holding id-token: write and security-events: write, for no reason other than that some other job in the same file needed them. Scorecard rejects exactly this, and the fix is to default the whole workflow to read-only and grant write narrowly, in the job that actually needs it:
permissions: read-all
Write permissions moved down into the single job that uses them. It’s the same least-privilege instinct that runs through everything else in these projects, just applied to a CI token instead of an IAM role: a credential should be able to do the one thing it’s for, and nothing else, no matter how convenient the broad grant looked.
The finding that was about me
The third one stung, because there was no YAML to fix. Scorecard’s Code-Review check scores how consistently changes are reviewed before they land, and mine scored badly for the most embarrassing possible reason: I’d set up branch protection on main, and then, being the solo maintainer in a hurry, I’d been merrily bypassing it to push straight to main whenever it suited me.
So I had a rule, written down and enforced by the platform, that I was personally and routinely ignoring. Scorecard noticed, totted up the unreviewed commits, and graded me on it. There’s something properly humbling about a robot reading your git history and pointing out that the person breaking your security policy most often is you. The fix wasn’t code. It was going through a pull request like everyone else, even when “everyone else” is just me on a different day.
The bottom line
OpenSSF Scorecard is a linter for your supply chain, and like any linter it’s most useful when it tells you something you were sure you’d already handled. It dinged go-tool-base for referencing actions by mutable tag instead of pinned SHA, for granting workflow-token write permissions at the top level where every job inherited them, and for a Code-Review score I’d earned fair and square by bypassing my own branch protection.
The first two were quick, satisfying changes with a clear security story. The third was the one that stuck, because the tool I’d added to grade the project ended up grading the maintainer, and was entirely right to. Turn it on. Brace yourself a little.
