-
-
Notifications
You must be signed in to change notification settings - Fork 31
ci: harden GitHub Actions with permissions, SHA pins, and cache restrictions #154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
23e437e
438b7c6
406a4ea
7c3ead7
71d9739
488c8c1
7e25a7d
951a70b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| # GitHub Actions Workflows | ||
|
|
||
| This directory contains CI/CD workflows for the agnix project. | ||
|
|
||
| ## Security Hardening | ||
|
|
||
| All workflows follow security best practices: | ||
|
|
||
| ### 1. Explicit Permissions | ||
|
|
||
| Every workflow declares minimum required permissions at the workflow level. | ||
| Jobs that need additional permissions declare them at the job level. | ||
|
|
||
| - `permissions: {}` - No permissions (used when jobs specify their own) | ||
| - `permissions: contents: read` - Read-only access to repository contents | ||
|
|
||
| ### 2. SHA-Pinned Actions | ||
|
|
||
| All third-party actions are pinned to specific commit SHAs to prevent | ||
| supply chain attacks. The SHA pins are documented with version comments | ||
| for maintainability. | ||
|
|
||
| ### 3. Cache Save Restrictions | ||
|
|
||
| Rust caches (`Swatinem/rust-cache`) are configured with `save-if` conditions | ||
| to only save caches on protected branches (main) or tag pushes. This prevents | ||
| cache poisoning from pull requests. | ||
|
|
||
| ## SHA Pin Reference | ||
|
|
||
| When updating actions, use these SHA commits (last verified: 2025-02): | ||
|
|
||
| ```yaml | ||
| # GitHub Official Actions | ||
| actions/checkout@v4: 34e114876b0b11c390a56381ad16ebd13914f8d5 | ||
| actions/upload-artifact@v4: ea165f8d65b6e75b540449e92b4886f43607fa02 | ||
| actions/download-artifact@v4: d3f86a106a0bac45b974a628896c90dbdf5c8093 | ||
|
|
||
| # Rust Tooling | ||
| dtolnay/rust-toolchain@stable: 4be9e76fd7c4901c61fb841f559994984270fce7 | ||
| Swatinem/rust-cache@v2: 779680da715d629ac1d338a641029a2f4372abb5 | ||
| taiki-e/install-action@v2: 650c5ca14212efbbf3e580844b04bdccf68dac31 | ||
| taiki-e/install-action@nextest: cd05dcd6eb73067dda063b97a15b7060049dacd9 | ||
|
|
||
| # Security | ||
| github/codeql-action@v3: 2588666de8825e1e9dc4e2329a4c985457d55b32 | ||
|
|
||
| # Release | ||
| softprops/action-gh-release@v2: a06a81a03ee405af7f2048a818ed3f03bbf83c7b | ||
|
|
||
| # Claude Code | ||
| anthropics/claude-code-action@v1: 6867bb3ab0b2c0a10629b6823e457347e74ad6d2 | ||
| ``` | ||
|
|
||
| ## Updating Action Versions | ||
|
|
||
| When a new version of an action is released: | ||
|
|
||
| 1. Check the release notes for security implications | ||
| 2. Get the full SHA of the release tag: | ||
| ```bash | ||
| git ls-remote --tags https://github.com/owner/repo refs/tags/vX.Y.Z | ||
| ``` | ||
| 3. Update all occurrences in workflow files | ||
| 4. Update this README with the new SHA | ||
| 5. Test the workflows on a feature branch before merging | ||
|
|
||
| ## Workflow Overview | ||
|
|
||
| | Workflow | Trigger | Purpose | | ||
| |----------|---------|---------| | ||
| | ci.yml | push/PR to main | Lint, test, build | | ||
| | release.yml | tag push (v*) | Build and publish releases | | ||
| | security.yml | push/PR/schedule | CodeQL analysis and security audit | | ||
| | test-action.yml | push/PR (action paths) | Test the GitHub Action | | ||
| | changelog.yml | PR | Verify CHANGELOG.md is updated | | ||
| | claude.yml | issue/PR comments | Claude Code assistant | | ||
| | claude-code-review.yml | PR | Automated code review | |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -14,6 +14,9 @@ on: | |||||
| - '.github/workflows/test-action.yml' | ||||||
| workflow_dispatch: | ||||||
|
|
||||||
| permissions: | ||||||
| contents: read | ||||||
|
|
||||||
| jobs: | ||||||
| test-action: | ||||||
| name: Test (${{ matrix.os }}) | ||||||
|
|
@@ -24,12 +27,14 @@ jobs: | |||||
| os: [ubuntu-latest, macos-latest, windows-latest] | ||||||
|
|
||||||
| steps: | ||||||
| - uses: actions/checkout@v4 | ||||||
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | ||||||
|
|
||||||
| - name: Install Rust | ||||||
| uses: dtolnay/rust-toolchain@stable | ||||||
| uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable | ||||||
|
|
||||||
| - uses: Swatinem/rust-cache@v2 | ||||||
| - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2 | ||||||
| with: | ||||||
| save-if: ${{ github.ref == 'refs/heads/main' }} | ||||||
|
||||||
| save-if: ${{ github.ref == 'refs/heads/main' }} | |
| save-if: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/feature/') || startsWith(github.ref, 'refs/heads/fix/') }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Include contents read when overriding job permissions
The workflow now sets permissions: contents: read at the top level, but this job overrides it with a job-level permissions block that only includes security-events: write. Job-level permissions replace workflow defaults, so this job no longer has contents: read, which actions/checkout relies on. That means the Test SARIF Upload job will fail at checkout when the hardened permissions change takes effect. Add contents: read to this job’s permissions to keep checkout working while still limiting access.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The release.yml workflow uses a different cache save condition than described in the PR description. The description states "Restricted cache saves to main branch only" but release.yml correctly uses
startsWith(github.ref, 'refs/tags/')instead since it runs on tag pushes, not on main branch pushes. While the implementation is correct, the PR description could be more precise about this distinction.