Skip to content

ci: pin actions to SHAs, add Dependabot, and gate on a gitleaks scan - #8

Merged
curtyo18 merged 3 commits into
masterfrom
ci/pin-actions-and-secret-scan
Jul 20, 2026
Merged

ci: pin actions to SHAs, add Dependabot, and gate on a gitleaks scan#8
curtyo18 merged 3 commits into
masterfrom
ci/pin-actions-and-secret-scan

Conversation

@curtyo18

Copy link
Copy Markdown
Owner

Closes #6. Closes #7.

Both follow-ups from #3 in one branch — they're the same concern (what enters the repo and what the runner executes) and neither is big enough to justify its own review.

#6 — SHA pins + Dependabot

action SHA tag
actions/checkout 11d5960a326750d5838078e36cf38b85af677262 v4.4.0
actions/setup-node 49933ea5288caeca8642d1e84afbd3f7d6820020 v4.4.0

Verified both ways: v4.4.0 and the floating v4 currently resolve to exactly these commits, so the pins are a behavioural no-op against what was already running — this changes the trust model without changing the build. (The versions suggested in #6, v4.2.2 / v4.1.0, were stale; pinning those would have been a silent downgrade.)

.github/dependabot.yml adds the github-actions ecosystem, grouped, weekly. Pinning without this is arguably worse than tracking a tag — a SHA doesn't rot loudly, it just quietly ages.

npm deliberately excluded. All three projects carry esbuild, which produces the committed *.user.js that #3's drift check requires to be byte-identical. An esbuild bump changes those bytes, so every such Dependabot PR would arrive red and need a manual rebuild commit. That's a review routine worth adopting on purpose, not switching on as a side effect.

#7 — secret scan

gitleaks 8.30.1 as a checksum-verified release binary rather than a marketplace action: one less third-party action in the supply chain and nothing extra to keep pinned. Runs as a secret-scan job in ci.yml (same triggers/permissions, so a separate workflow would duplicate the concurrency block for nothing).

.gitleaks.toml extends the upstream default ruleset and adds one repo-specific rule for the Auto Login share string. This is the part a generic scanner cannot catch: per docs/adr/0003, a share string is AL1:/AL1U: + base64url of every stored credential — encoding, not encryption — so it reads as ordinary text. The rule was written against the real encoder in auto-login/src/share.ts, not a guess at the format.

Scans full history, not a base..head range (a deviation from #7's suggestion). History is clean, so it doesn't arrive red; computing a diff range is the part that breaks quietly, since a mis-computed base scans nothing and still exits 0. At ~1s for this repo, whole-history buys immunity to that for free.

Verification

The last PR shipped a check that silently could not fail, so each claim here was tested rather than assumed:

  • History clean — 64 commits, default ruleset plus the custom rule, zero findings. Nothing rewritten, nothing suppressed; no allowlist was needed.
  • The rule actually fires — planted a genuine 214-char share string built with the repo's own encoding (AL1U: + base64url of a real credential object). Scan exits 1. Removed afterwards.
  • No false positive on the source constantsSHARE_PREFIX = 'AL1:' in share.ts and in the built bundle do not trip it; the length floor handles that, confirmed by the clean scan.
  • Checksum verified independently — downloaded the pinned release artifact and confirmed sha256 matches what's in the workflow.
  • SHAs verified against the live tag refs.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WSt6ZYkUkUyDc28cHgwhDj

curtyo18 and others added 3 commits July 20, 2026 23:00
`actions/checkout@v4` and `actions/setup-node@v4` tracked mutable tags. A tag
can be repointed by whoever controls the upstream repo, so a compromised action
would have executed in our runner. Both are now full commit SHAs with the
version in a trailing comment.

Both `v4` floating tags currently resolve to v4.4.0, so these pins are a no-op
against what was already running — this changes the trust model, not the build:

  actions/checkout   11d5960a326750d5838078e36cf38b85af677262  v4.4.0
  actions/setup-node 49933ea5288caeca8642d1e84afbd3f7d6820020  v4.4.0

Dependabot lands in the same commit because it is what makes pinning an
improvement rather than a regression. An unpinned tag at least drifts forward;
a pin with nothing bumping it just ages silently until you are a year behind
and no longer noticing. Grouped so the actions arrive as one PR.

Not added here: the npm ecosystem for the three project directories. They carry
devDependencies only, but esbuild is among them and it produces the committed
*.user.js that CI requires to be byte-identical to source — so every esbuild
bump PR would arrive red on the drift check and need a manual rebuild commit to
go green. That is a review routine worth adopting deliberately rather than
switching on as a side effect of this issue.

Refs #6

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WSt6ZYkUkUyDc28cHgwhDj
This repo is public and nothing checked content before now — the pre-commit
hook only bumps @Version in staged *.user.js.

Auto Login sharpens the generic risk. Per docs/adr/0003 credentials are stored
and shared in recoverable plaintext, and an export is `AL1:` +
base64url(deflate-raw(JSON)) — an encoding, not encryption. Pasted into a
fixture, README, or issue body that is a full credential dump that reads as
ordinary text, and no generic rule fires on it. Hence the custom rule, written
against the actual encoder in auto-login/src/share.ts rather than a guess: it
covers the `AL1U:` uncompressed fallback too, and its length floor keeps it off
the `SHARE_PREFIX` constants in source and in the built bundle.

Verified to fire rather than merely to pass — a planted share string of each
form, a non-example AKIA key, and a GitHub PAT all produce findings and a
non-zero exit, in both working-tree and git-history mode. The current tree and
all 62 commits of history are clean, so no allowlist was needed and the check
does not arrive red.

Scans full history rather than a base..head range. History being clean makes
that affordable (~1s) and it removes the failure mode that matters most here:
a mis-computed range scans nothing and still exits 0, which is a check that
cannot fail. In practice it only ever trips on what a diff adds.

gitleaks is installed as a version-pinned, SHA256-verified release binary
rather than a marketplace action — one less third-party action in the supply
chain and nothing extra to keep pinned.

Refs #7

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WSt6ZYkUkUyDc28cHgwhDj
Review findings on the gitleaks job.

The `\b` anchor in the share-string rule was a false negative, not a
safety margin: `AL1U?:` is already distinctive, and requiring a word
boundary let `share_AL1:<body>` through untouched. Verified before and
after — the prefixed form now fires, the bare form still does.

The full-history comment claimed the job "only ever fails on what a diff
adds", which is wrong in the one case that matters. A hit anywhere in
history fails every branch, and deleting the secret later does not clear
it because the blob stays reachable. That behaviour is deliberate and
matches the repo's git-workflow standard, but nobody should meet it for
the first time on a red merge queue, so the comment now states it and
points at --baseline-path / .gitleaksignore as the deferral hatch.

Also: `detect` is deprecated in 8.30.1 in favour of `git`, so use the
supported form now rather than discovering it at v9; --retry-all-errors
so curl retries connection failures, not just HTTP statuses; and the
commit count says 64, which is what the scan actually reports.

Documented rather than fixed: the *stored* form of the same credentials.
Review proposed a rule keyed on "password"/"username"/"updatedAt", but
that is not the shape — per src/types.ts a credential is `value` on a
`fill` step, with no key naming it and nothing distinguishing a filled
password from a filled username. A rule keyed on the real shape
(pagePattern + value) hits the fixtures in auto-login/test/ — runner.test.ts
alone has `value: 'hunter2'` — so it would need an allowlist over that
directory, which would blind the scan to a real share string committed
into a fixture. That is one of the leak paths issue #7 named, so the gap
is now written down in .gitleaks.toml instead of papered over.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WSt6ZYkUkUyDc28cHgwhDj
@curtyo18
curtyo18 merged commit 900dbfe into master Jul 20, 2026
4 checks passed
@curtyo18
curtyo18 deleted the ci/pin-actions-and-secret-scan branch July 20, 2026 22:10
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.

CI: secret / proprietary-reference scan CI: pin GitHub Actions to commit SHAs + add Dependabot

1 participant