Skip to content

fix(security): OSSF Scorecard Tier-1 hardening (token perms + pin deps)#82

Merged
devonartis merged 8 commits into
developfrom
fix/ossf-scorecard-tier1
May 14, 2026
Merged

fix(security): OSSF Scorecard Tier-1 hardening (token perms + pin deps)#82
devonartis merged 8 commits into
developfrom
fix/ossf-scorecard-tier1

Conversation

@devonartis
Copy link
Copy Markdown
Owner

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

  1. test(ossf-scorecard-tier1): register acceptance stories — 12 stories (3 PRE, 8 ACC, 1 DEF), council-approved
  2. fix(ci): move codeql security-events permission to job level
  3. fix(ci): move release workflow write permissions to job level
  4. fix(docker): pin base images by SHA digest
  5. fix(ci): pin govulncheck install by commit SHA
  6. test(crypto): replace python ed25519 helper with go, remove pip from CI — single coupled commit (helper + 2 scripts + ci.yml)
  7. docs(changelog): record OSSF Scorecard Tier-1 hardeningcloses OSSF Scorecard Tier-1 hardening: token-permissions + pinned-dependencies #81

Plan deviations (documented in commit messages)

  • Dockerfile inline-comment syntax (T3): Plan suggested FROM image@sha256:... AS stage # tag. Dockerfile parser rejects trailing comments after AS stage ("FROM requires either one or three arguments"). Moved tag-name comments to preceding line. Verified docker build . succeeds.
  • T5 scope expansion: Plan covered only tests/sec-l2b/integration.sh lines 85-129. Audit found 2 more python blocks in integration.sh (S2 keygen + S6 1MB body) AND 2 python blocks in scripts/smoke/core-contract.sh (the script smoke-l25 actually runs — plan misidentified). Deleting pip install without also converting core-contract.sh would 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 (the smoke-l25 CI 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

  • All 20 CI gates green
  • analyze (go) + CodeQL succeed (verifies job-level security-events: write works)
  • docker-build succeeds (verifies SHA-pinned base images build in CI)
  • smoke-l25 succeeds (verifies Go ed25519 helper end-to-end in Docker)
  • govulncheck succeeds (verifies pinned SHA install resolves)
  • grep -rE "python3|pip install|cryptography" .github/ tests/sec-l2b/ scripts/smoke/ returns empty post-merge
  • After merge to develop + promote to main: trigger scorecard.yml via workflow_dispatch; verify Token-Permissions = 10, Pinned-Deps = 10, overall ≥ 7.0
  • cosign keyless signing verified on next v* tag push

devonartis and others added 7 commits May 13, 2026 19:53
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>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 14, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

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>
@devonartis devonartis merged commit 3ecf3fd into develop May 14, 2026
20 checks passed
@devonartis devonartis deleted the fix/ossf-scorecard-tier1 branch May 14, 2026 00:14
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.

1 participant