Skip to content
78 changes: 78 additions & 0 deletions .github/workflows/README.md
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 |
5 changes: 4 additions & 1 deletion .github/workflows/changelog.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ on:
pull_request:
types: [opened, synchronize, reopened, labeled, unlabeled]

permissions:
contents: read

jobs:
changelog:
name: Verify Changelog
Expand All @@ -13,7 +16,7 @@ jobs:
!contains(github.event.pull_request.title, '[skip changelog]') &&
!contains(github.event.pull_request.labels.*.name, 'skip-changelog')
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0

Expand Down
34 changes: 21 additions & 13 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ on:
env:
CARGO_TERM_COLOR: always

permissions:
contents: read

jobs:
format:
name: Format Check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
with:
components: rustfmt
- run: cargo fmt --all --check
Expand All @@ -25,22 +28,26 @@ jobs:
runs-on: ubuntu-latest
needs: format
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- run: cargo clippy --workspace -- -D warnings

machete:
name: Unused Dependencies
runs-on: ubuntu-latest
needs: format
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: taiki-e/install-action@v2
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: taiki-e/install-action@650c5ca14212efbbf3e580844b04bdccf68dac31 # v2
with:
tool: cargo-machete@0.9.1
- run: cargo machete
Expand All @@ -54,11 +61,12 @@ jobs:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
with:
key: ${{ matrix.os }}
- uses: taiki-e/install-action@nextest
save-if: ${{ github.ref == 'refs/heads/main' }}
- uses: taiki-e/install-action@cd05dcd6eb73067dda063b97a15b7060049dacd9 # nextest
- run: cargo nextest run --workspace
- run: cargo test --doc --workspace
6 changes: 4 additions & 2 deletions .github/workflows/claude-code-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ on:
# - "src/**/*.js"
# - "src/**/*.jsx"

permissions: {}

jobs:
claude-review:
# Restricted to repository owner only
Expand All @@ -24,13 +26,13 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 1

- name: Run Claude Code Review
id: claude-review
uses: anthropics/claude-code-action@v1
uses: anthropics/claude-code-action@6867bb3ab0b2c0a10629b6823e457347e74ad6d2 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
plugin_marketplaces: 'https://github.com/anthropics/claude-code.git'
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/claude.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ on:
pull_request_review:
types: [submitted]

permissions: {}

jobs:
claude:
# Restricted to avifenesh only
Expand All @@ -29,13 +31,13 @@ jobs:
actions: read # Required for Claude to read CI results on PRs
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 1

- name: Run Claude Code
id: claude
uses: anthropics/claude-code-action@v1
uses: anthropics/claude-code-action@6867bb3ab0b2c0a10629b6823e457347e74ad6d2 # v1
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}

Expand Down
15 changes: 8 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,20 @@ jobs:
artifact: agnix.exe

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Install musl tools
if: matrix.target == 'x86_64-unknown-linux-musl'
run: sudo apt-get update && sudo apt-get install -y musl-tools

- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
with:
targets: ${{ matrix.target }}

- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
with:
key: release-${{ matrix.target }}
save-if: ${{ startsWith(github.ref, 'refs/tags/') }}

Copilot AI Feb 4, 2026

Copy link

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.

Copilot uses AI. Check for mistakes.

- name: Build release binary
run: cargo build --release --target ${{ matrix.target }}
Expand All @@ -71,7 +72,7 @@ jobs:
(Get-FileHash agnix-${{ matrix.target }}.zip -Algorithm SHA256).Hash.ToLower() + " agnix-${{ matrix.target }}.zip" | Out-File -Encoding ASCII agnix-${{ matrix.target }}.zip.sha256

- name: Upload artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: agnix-${{ matrix.target }}
path: |
Expand All @@ -83,10 +84,10 @@ jobs:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Download all artifacts
uses: actions/download-artifact@v4
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
path: artifacts
merge-multiple: true
Expand All @@ -105,7 +106,7 @@ jobs:
fi

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2
with:
name: ${{ steps.notes.outputs.version }}
body_path: release_notes.md
Expand Down
14 changes: 8 additions & 6 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ jobs:
name: CodeQL Analysis
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@2588666de8825e1e9dc4e2329a4c985457d55b32 # v3
with:
languages: rust
queries: security-extended
Expand All @@ -30,17 +30,19 @@ jobs:
run: cargo build --release --workspace

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@2588666de8825e1e9dc4e2329a4c985457d55b32 # v3
with:
category: "/language:rust"

audit:
name: Security Audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: dtolnay/rust-toolchain@4be9e76fd7c4901c61fb841f559994984270fce7 # stable
- uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2
with:
save-if: ${{ github.ref == 'refs/heads/main' }}
- name: Install cargo-audit
run: cargo install cargo-audit
- name: Run security audit
Expand Down
22 changes: 15 additions & 7 deletions .github/workflows/test-action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ on:
- '.github/workflows/test-action.yml'
workflow_dispatch:

permissions:
contents: read

jobs:
test-action:
name: Test (${{ matrix.os }})
Expand All @@ -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' }}

Copilot AI Feb 4, 2026

Copy link

Choose a reason for hiding this comment

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

The cache save restriction in test-action.yml only allows caching on the main branch, but this workflow is configured to run on feature/* and fix/* branches (see lines 5-9). This means builds on feature/* and fix/* branches will never be able to save caches, potentially slowing down development on these branches.

Consider whether these branches should also be allowed to save caches, or if the trigger pattern should be restricted. The current configuration prevents cache poisoning but may impact developer experience on feature branches.

Suggested change
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/') }}

Copilot uses AI. Check for mistakes.

- name: Test action (default inputs)
id: test-default
Expand Down Expand Up @@ -167,15 +172,18 @@ jobs:
name: Test SARIF Upload
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.


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' }}

- name: Run agnix with SARIF
id: agnix
Expand All @@ -187,7 +195,7 @@ jobs:

- name: Upload SARIF to GitHub
if: steps.agnix.outputs.sarif-file != ''
uses: github/codeql-action/upload-sarif@v3
uses: github/codeql-action/upload-sarif@2588666de8825e1e9dc4e2329a4c985457d55b32 # v3
with:
sarif_file: ${{ steps.agnix.outputs.sarif-file }}
continue-on-error: true
Loading
Loading