Skip to content

fix(security): pin actions to SHAs, scope tokens, broaden credential redaction - #609

Merged
imran-siddique merged 1 commit into
mainfrom
sec/supply-chain-hardening
Sep 5, 2026
Merged

fix(security): pin actions to SHAs, scope tokens, broaden credential redaction#609
imran-siddique merged 1 commit into
mainfrom
sec/supply-chain-hardening

Conversation

@imran-siddique

Copy link
Copy Markdown
Member

Part of a proactive security sweep across agentrust-io. cmcp had the largest share of the real findings.

Action pinning (24 refs)

Every third-party action ran from a mutable tag. The ones that matter are on the release path and hold credentials:

  • docker/login-action@v4 (registry credentials)
  • sigstore/cosign-installer@v3 (signing)
  • actions/attest-build-provenance@v4 (id-token: write)
  • docker/build-push-action@v7

A tag is repointable by whoever controls the action repo, so signing and publishing trusted a moving target. All now pin a commit SHA with the version kept in a trailing comment.

Token scope

ci.yml, docker.yml, release-drafter.yml and sbom.yml had no top-level permissions: block, so any job without its own block inherited the repository default. Added contents: read as the floor. docker.yml and sbom.yml already declared job-level packages: write / id-token: write and keep them, so no job loses a capability it was using.

Shell injection

Four sites interpolated github.event.release.tag_name or github.ref_name directly into run: blocks. ${{ }} expands before bash parses the line, so those names were shell input rather than arguments. All now go through env:.

Defence in depth rather than a live hole, since creating a tag or release already needs write access.

Credential redaction (code-scanning #136)

_redact_auth_headers redacted only Authorization. The same call is configured with OPAQUE_API_KEY, so a deployment carrying it in x-api-key, x-auth-token or a cookie would have written it to the debug log in clear. Redaction is now deny-by-default over a name match, with a parametrized test across the spellings that leaked and a companion test proving non-credential headers stay readable, since blinding that log would cost the failure diagnostics it exists for.

TLS floor (code-scanning #2)

The pinning fixture built its server with PROTOCOL_TLS_SERVER and no floor, leaving TLSv1 and TLSv1.1 reachable in the very test that asserts the gateway transport rules. Sets minimum_version = TLSv1_2.

Verification

tests/unit/test_tls_pinning.py 12 passed, test_opaque_fail_closed_594.py 29 passed. Full suite 1604 passed with 4 failures that reproduce on a clean checkout of main: the local venv has agent-manifest 0.11.1 against a >=0.11.2 pin. Unrelated to this change, and CI installs the pin.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XbDBXDWWvMFa7c2jGgyq9t

…redaction

Supply chain. Every third-party action ran from a mutable tag, including
the ones that hold credentials: docker/login-action, cosign-installer,
attest-build-provenance and build-push-action. A tag can be repointed by
whoever controls the action repo, so the release path trusted a moving
target. All 24 refs now pin a commit SHA with the version in a comment.

Token scope. ci.yml, docker.yml, release-drafter.yml and sbom.yml had no
top-level permissions block, so any job without its own block inherited
the repository default. The three that do declare job-level permissions
keep them; the top-level contents: read is the floor, not a change to
what those jobs can do.

Shell injection. release.yml and release-drafter.yml substituted
github.event.release.tag_name and github.ref_name straight into run:
blocks. ${{ }} expands before bash parses the line, so those names were
shell input. All four sites now pass through env.

HW-008 redaction was an Authorization special case, but the same request
is configured with OPAQUE_API_KEY, and a deployment carrying it under
x-api-key or a cookie would have logged it in clear on the debug path.
Redaction is now deny-by-default over a name match, with a parametrized
test over the spellings that leaked.

The TLS pinning fixture built its server with PROTOCOL_TLS_SERVER and no
floor, leaving TLSv1 and TLSv1.1 reachable in a test that exists to prove
the gateway's transport rules. Sets minimum_version to TLSv1.2.

Closes code-scanning alerts #136 and #2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XbDBXDWWvMFa7c2jGgyq9t
@imran-siddique
imran-siddique requested review from a team as code owners September 5, 2026 21:01
@imran-siddique
imran-siddique merged commit cdaf97c into main Sep 5, 2026
15 checks passed
@imran-siddique
imran-siddique deleted the sec/supply-chain-hardening branch September 5, 2026 21:11
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