Skip to content

ci: GitHub Actions workflow security cleanup#678

Open
emptyhammond wants to merge 4 commits into
mainfrom
worktree-fixup-workflow
Open

ci: GitHub Actions workflow security cleanup#678
emptyhammond wants to merge 4 commits into
mainfrom
worktree-fixup-workflow

Conversation

@emptyhammond
Copy link
Copy Markdown

@emptyhammond emptyhammond commented May 26, 2026

Routine hygiene pass over the GitHub Actions workflows in this repo, addressing findings from a workflow security audit. Changes are split into four commits, one per finding type:

  • Disable credential persistence on actions/checkout steps so the default GITHUB_TOKEN is not left in the local git config after checkout. None of these workflows push back to the repo
    using that token.
  • Scope each job's permissions explicitly: top-level permissions: {}, with each job granted only the GITHUB_TOKEN scopes it actually needs (contents: read for the checkout-based jobs,
    id-token: write preserved on the PyPI / TestPyPI trusted-publishing jobs).
  • Disable caching on the release workflow (setup-uv enable-cache: false, drop the actions/cache step for .venv) so the tag-driven release build resolves dependencies from scratch and
    can't read a cache populated by an earlier untrusted run.
  • Pin all third-party actions to commit SHAs (with the tag preserved as a trailing comment) so an upstream tag move can't silently change what runs in CI. The internal ably/features
    reusable workflow reference is intentionally left on @main.

No behavioural changes intended — the workflows run the same checks against the same inputs.

Summary by CodeRabbit

  • Chores
    • Updated GitHub Actions across CI/CD workflows to reference pinned commit SHAs instead of floating version tags
    • Added explicit workflow and job-level permission declarations to establish restrictive access scopes
    • Disabled artifact caching in the release workflow

Review Change Stack

Set persist-credentials: false on every actions/checkout invocation so
the default GITHUB_TOKEN is not left in the local git config after
checkout. None of these workflows push back to the repo using that
token, so the credential is unused after checkout completes.
Set a top-level permissions: {} on each workflow and grant each job the
narrowest GITHUB_TOKEN scopes it actually needs (contents: read for
checkout-based jobs, id-token: write preserved for the PyPI trusted
publishing jobs). Previously the workflows ran with the repository's
default token permissions.
The release workflow runs on tag push and produces the artifacts that
get uploaded to PyPI, so any cache it reads is also a way for an
earlier untrusted run to influence what gets shipped. Switch setup-uv
to enable-cache: false and drop the actions/cache step for .venv so
the release build resolves dependencies from scratch each time.
Replace tag references (@v4, @v5, @release/v1, ...) with the
corresponding commit SHA, keeping the tag in a trailing comment so
the human-readable version is still visible. This protects CI from
an upstream tag being moved to point at different code than what we
last reviewed.

The ably/features reusable workflow reference is left on @main on
purpose, since that's an internal Ably workflow.
@emptyhammond emptyhammond requested a review from ttypic May 26, 2026 12:12
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

Walkthrough

Four GitHub Actions workflows are hardened with explicit least-privilege permissions and pinned action SHAs. Each workflow now declares permissions: {} at the top level and grants jobs only contents: read access. All GitHub Actions are switched from floating version tags to specific commit SHA references for supply-chain security.

Changes

Workflow Security Hardening

Layer / File(s) Summary
check.yml hardening
.github/workflows/check.yml
Adds empty top-level permissions: {} and restricts the check job to contents: read. Pins actions/checkout, actions/setup-python, astral-sh/setup-uv, and actions/cache to commit SHAs.
features.yml hardening
.github/workflows/features.yml
Adds empty top-level permissions: {} and restricts the build job to contents: read.
lint.yml hardening
.github/workflows/lint.yml
Adds empty top-level permissions: {} and restricts the lint job to contents: read. Pins actions/checkout, actions/setup-python, astral-sh/setup-uv, and actions/cache to commit SHAs.
release.yml hardening
.github/workflows/release.yml
Adds empty top-level permissions: {} and restricts jobs to contents: read. Pins all action references (checkout, setup-python, setup-uv, artifact upload/download, PyPI publish) to commit SHAs. Disables UV cache via enable-cache: false.

🎯 2 (Simple) | ⏱️ ~12 minutes

🐰 With permissions tightened and commits pinned,
GitHub Actions now run with safeguards built in,
Supply-chain secure, the workflows refined,
Each SHA confirmed, no floating tags left behind!
🔐✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'ci: GitHub Actions workflow security cleanup' accurately summarizes the main change—a security-focused update to GitHub Actions workflows with restricted permissions, pinned action versions, and disabled credential persistence.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-fixup-workflow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
.github/workflows/features.yml (1)

13-18: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix reusable-workflow permissions mismatch in .github/workflows/features.yml (lines 13-18)

The reusable workflow ably/features/.github/workflows/sdk-features.yml@main declares jobs.build.permissions as deployments: write and id-token: write, but this caller limits the job permissions for the reusable workflow call to permissions: contents: read; the caller’s permissions cap what the reusable workflow can use, so this can break the workflow (e.g., AWS OIDC requires id-token). Update the caller permissions to include the required scopes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/features.yml around lines 13 - 18, The caller limits
reusable-workflow scopes to only contents: read which prevents the called
workflow (uses: ably/features/.github/workflows/sdk-features.yml@main) from
obtaining the deploy and OIDC tokens it requests; update the caller permissions
block (the permissions: entry around the reusable workflow call) to include
deployments: write and id-token: write (alongside contents: read) so the
reusable workflow’s jobs.build permissions are not capped.
.github/workflows/release.yml (1)

19-41: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix incorrect version-tag label for pinned PyPI publish action in release workflow

In .github/workflows/release.yml, the pin pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b is labeled # release/v1, but there is no release/v1 tag in the upstream repo; that SHA points to the v1.14.0 tag instead (appears in both the publish-to-pypi and publish-to-testpypi steps). Other pinned action SHAs in this workflow match their labeled tags (actions/checkout v4, actions/setup-python v5, astral-sh/setup-uv v7, actions/upload-artifact/actions/download-artifact v4).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release.yml around lines 19 - 41, The workflow uses a
pinned SHA for pypa/gh-action-pypi-publish but the trailing comment label is
wrong; update the comment label for the pinned SHA
cef221092ed1bacb1cc03d23a2d87d1d172e277b (used in the publish-to-pypi and
publish-to-testpypi steps) to the correct tag (v1.14.0) so the comment matches
the actual upstream tag for that SHA; locate the two steps referencing
pypa/gh-action-pypi-publish in release.yml (publish-to-pypi and
publish-to-testpypi) and replace the incorrect `# release/v1` label with `#
v1.14.0`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In @.github/workflows/features.yml:
- Around line 13-18: The caller limits reusable-workflow scopes to only
contents: read which prevents the called workflow (uses:
ably/features/.github/workflows/sdk-features.yml@main) from obtaining the deploy
and OIDC tokens it requests; update the caller permissions block (the
permissions: entry around the reusable workflow call) to include deployments:
write and id-token: write (alongside contents: read) so the reusable workflow’s
jobs.build permissions are not capped.

In @.github/workflows/release.yml:
- Around line 19-41: The workflow uses a pinned SHA for
pypa/gh-action-pypi-publish but the trailing comment label is wrong; update the
comment label for the pinned SHA cef221092ed1bacb1cc03d23a2d87d1d172e277b (used
in the publish-to-pypi and publish-to-testpypi steps) to the correct tag
(v1.14.0) so the comment matches the actual upstream tag for that SHA; locate
the two steps referencing pypa/gh-action-pypi-publish in release.yml
(publish-to-pypi and publish-to-testpypi) and replace the incorrect `#
release/v1` label with `# v1.14.0`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8581515b-c34e-4f0b-bf0d-392c2975adbc

📥 Commits

Reviewing files that changed from the base of the PR and between 00b2ee6 and b2063da.

📒 Files selected for processing (4)
  • .github/workflows/check.yml
  • .github/workflows/features.yml
  • .github/workflows/lint.yml
  • .github/workflows/release.yml

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant