fix(security): OSSF Scorecard Tier-1 hardening (token perms + pin deps)#82
Merged
Conversation
12 stories (3 precondition, 8 acceptance, 1 deferred) covering Token-Permissions + Pinned-Deps lift, Docker digest reproducibility, Scorecard non-regression of currently-10 checks, and Python-removal verification. Council-approved 2026-05-13 (3 reviewers, APPROVED_AFTER_REVISION). Refs #81 Co-authored-by: Claude <noreply@anthropic.com>
Top-level write permissions hit OSSF Scorecard Token-Permissions check (currently 0/10). Move security-events:write and actions:read to the analyze job — the only consumer in this workflow. Top level keeps contents:read only. CodeQL analyze action is on the recognized- action list, so job-level security-events:write is not penalized. Refs #81 Co-authored-by: Claude <noreply@anthropic.com>
Top-level packages:write and id-token:write hit OSSF Scorecard Token-Permissions. The publish job is the only consumer of both. cosign-installer is on the recognized-actions list, so job-level id-token:write keeps keyless signing working without penalty. Comments preserved — they explain why each permission exists. Refs #81 Co-authored-by: Claude <noreply@anthropic.com>
golang:1.24-alpine and alpine:3.21 were unpinned tag references. Tags are mutable — a supply-chain compromise of the upstream publisher would silently land in our build. Pin both by sha256 digest. Dependabot's docker ecosystem (.github/dependabot.yml) rotates digests weekly with PR review. Tag names preserved on the preceding comment line (Dockerfile FROM syntax rejects trailing inline comments after AS stage). Refs #81 Co-authored-by: Claude <noreply@anthropic.com>
go install ...@latest is unpinned by Scorecard's definition — every CI run resolves dynamically. Pin to the dereferenced commit SHA of the latest release tag (v1.1.4 / d1f380186385b4f64e00313f31743df8e4b89a77). Tag-derived SHA is the only safe form: gh api releases/latest .target_commitish can return branch HEAD, not the tag commit. Manual quarterly bump cadence — owner reviews when next release is worth picking up. Existing gosec + CodeQL gates provide secondary CVE coverage. Refs #81 Co-authored-by: Claude <noreply@anthropic.com>
Project rule (.claude/rules/golang.md): all crypto is Go stdlib.
Two integration scripts were using inline python3 with the
cryptography package for challenge-response Ed25519 keygen+sign:
tests/sec-l2b/integration.sh (3 blocks) and scripts/smoke/core-
contract.sh (2 blocks). The latter is what the smoke-l25 CI gate
actually runs. Plan (.plans/2026-05-13-ossf-scorecard-tier1-plan.md)
covered only the first script — scope expanded after audit found
core-contract.sh still required python3+cryptography even after
integration.sh was converted.
Replace with tests/sec-l2b/edsign (~50 lines, crypto/ed25519
stdlib only). Both bash scripts build the helper once per run and
use it for keygen+sign; HTTP POST stays in curl, JSON body
construction in jq -nc. S6 oversized-body generator uses
head -c 1048576 /dev/zero | tr '\\0' 'x' instead of python.
Delete pip install step from ci.yml smoke-l25 job; add setup-go
since the job now runs `go build` for the helper. Update
core-contract.sh dep-check to require go instead of python3.
Repo is now Python-free across .github/, tests/sec-l2b/, and
scripts/smoke/. OSSF Scorecard Pinned-Dependencies finding for
the pip line disappears entirely (rather than being hash-pinned).
Acceptance verified locally against live broker:
- scripts/smoke/core-contract.sh: 10/10 PASS
- tests/sec-l2b/integration.sh: 9 PASS / 1 SKIP (HSTS-TLS) / 0 FAIL
HTTP wire behavior unchanged: same Ed25519 keypair generation,
same hex-decoded nonce signing, same JSON body shape, same POST
to /v1/register, same HTTP error handling via curl -w %{http_code}.
Refs #81
Co-authored-by: Claude <noreply@anthropic.com>
Closes #81 Co-authored-by: Claude <noreply@anthropic.com>
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
Go 1.24 gofmt enforces blank line + tab indent for indented code blocks in doc comments. Format check failed on lines 7 and 11 of edsign/main.go. Refs #81 Co-authored-by: Claude <noreply@anthropic.com>
This was referenced May 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Lifts OSSF Scorecard from 6.2 → ~7.3 by fixing Token-Permissions (0 → 10) and Pinned-Dependencies (8 → 10). Repo also becomes Python-free as a side benefit.
Closes #81. Plan:
~/proj/devflow/agentwrit/.plans/2026-05-13-ossf-scorecard-tier1-plan.md. Spec:~/proj/devflow/agentwrit/.plans/specs/2026-05-13-ossf-scorecard-tier1-spec.md. Stories:tests/ossf-scorecard-tier1/user-stories.md.Commits
test(ossf-scorecard-tier1): register acceptance stories— 12 stories (3 PRE, 8 ACC, 1 DEF), council-approvedfix(ci): move codeql security-events permission to job levelfix(ci): move release workflow write permissions to job levelfix(docker): pin base images by SHA digestfix(ci): pin govulncheck install by commit SHAtest(crypto): replace python ed25519 helper with go, remove pip from CI— single coupled commit (helper + 2 scripts + ci.yml)docs(changelog): record OSSF Scorecard Tier-1 hardening— closes OSSF Scorecard Tier-1 hardening: token-permissions + pinned-dependencies #81Plan deviations (documented in commit messages)
FROM image@sha256:... AS stage # tag. Dockerfile parser rejects trailing comments afterAS stage("FROM requires either one or three arguments"). Moved tag-name comments to preceding line. Verifieddocker build .succeeds.tests/sec-l2b/integration.shlines 85-129. Audit found 2 more python blocks inintegration.sh(S2 keygen + S6 1MB body) AND 2 python blocks inscripts/smoke/core-contract.sh(the scriptsmoke-l25actually runs — plan misidentified). Deleting pip install without also convertingcore-contract.shwould break smoke-l25. Owner approved scope expansion to drop Python from the repo entirely.Acceptance — verified locally against live broker before push
scripts/smoke/core-contract.sh(thesmoke-l25CI gate): 10/10 PASS — health, admin auth, launch token, challenge, register (EdDSA JWT), validate, revoke, revoked-reject, OOS-403.tests/sec-l2b/integration.sh: 9 PASS / 1 SKIP (HSTS-TLS) / 0 FAIL — S1-S6 + R1-R4.Test plan
analyze (go)+CodeQLsucceed (verifies job-levelsecurity-events: writeworks)docker-buildsucceeds (verifies SHA-pinned base images build in CI)smoke-l25succeeds (verifies Go ed25519 helper end-to-end in Docker)govulnchecksucceeds (verifies pinned SHA install resolves)grep -rE "python3|pip install|cryptography" .github/ tests/sec-l2b/ scripts/smoke/returns empty post-mergescorecard.ymlviaworkflow_dispatch; verify Token-Permissions = 10, Pinned-Deps = 10, overall ≥ 7.0v*tag push