Skip to content
Security · Workflow

GitHub Actions hardening for agent workflows

Rules and a zizmor skill for agents editing GitHub Actions: SHA-pinned actions, permissions: {}, no template injection, no pull_request_target, read-only agent jobs.

Verified install Not agent-tested v1.0.0 MIT updated github-actionsgithub
Equips
  • 1 1 Rules
  • 1 1 Skills
  • – 0 MCP

Agents now write and edit CI workflows, and increasingly run inside them on untrusted issue and PR text. The common mistakes are the ones attackers use: actions pinned to a movable tag, an issue title expanded straight into a shell script, pull_request_target checking out fork code, a default token with write access to everything. This loadout gives your agent the hardening rules from GitHub's secure-use guide and Security Lab, a pattern for agent workflows that separates thinking (read-only) from acting (narrow writes), and a skill that runs zizmor offline and fixes each audit. The verifier runs zizmor 1.31.1 on the example workflow (clean) and on an unsafe agent-triage workflow.

What's in it

  • Actions pinned to full commit SHAs, kept current by Dependabot
  • permissions: {} at the top; per-job grants; persist-credentials: false
  • Untrusted event text through env, never inline in run
  • Agent jobs read-only; a separate narrow job applies their output
  • zizmor --offline as the check, no token needed

You'll need

  • Tools: uv, gh
Level 1 · automated

Verification

Verified install

Checked at commit 021470d · 9 passed, 0 failed, 1 not applicable

CheckResultWhat we found
Listinglisting.yaml matches the schema and declares every file in the folder. Pass
Schema valid; 5 files declared, none missing or extra
  • tested.status: untested (agent tests are separate from Level 1)
Files parseJSON parses; SKILL.md and .mdc front matter is valid YAML with the fields each agent needs; code fences are closed. Pass All 5 files parse; front matter valid
AdaptersCLAUDE.md imports @AGENTS.md, GEMINI.md imports @./AGENTS.md, and both match the generator. Pass
CLAUDE.md and GEMINI.md import AGENTS.md and are up to date
  • CLAUDE.md: matches the generator
  • GEMINI.md: matches the generator
Install pathsEvery file lands where each agent documents it, with no collisions, and the laid-out project is re-discovered correctly. Pass
Correct paths for all 6 agents; layouts re-discovered
  • Claude Code: 4 files, 1 skill in .claude/skills/
  • Codex CLI: 3 files, 1 skill in .agents/skills/
  • Cursor: 3 files, 1 skill in .agents/skills/
  • Gemini CLI: 4 files, 1 skill in .agents/skills/
  • Antigravity CLI: 3 files, 1 skill in .agents/skills/
  • GitHub Copilot: 3 files, 1 skill in .agents/skills/
MCP configmcp.json is structurally valid, converts to each agent's format, declares no literal secrets, and referenced packages exist on npm/PyPI. Not applicable No MCP servers in this loadout
Safety scanThe intake scanner finds no credentials, risky shell, hidden Unicode or prompt-injection wording, and the listing's network flag is honest. Pass
Clean: no credentials, risky shell or hidden text
  • Scanner intake-precheck/1: verdict clean; network yes (listing says yes)
ReferencesLinks and file paths between the loadout's files resolve, including for each agent's installed layout. Pass 2 references between files resolve for every agent
Size limitsFile count and sizes are within the submission limits; SKILL.md bodies stay under 500 lines. Pass
5 files, 5.8 KB total; largest AGENTS.md 2.3 KB
  • Limits: 30 files, 32 KB per file, 128 KB total, 500 lines per skill or rule
LicenceA recognised SPDX licence, with the licence text available. Pass MIT, by Agent Loadout; text in the repository LICENSE
Starter buildA minimal fixture project with the loadout applied installs and builds or type-checks, and the loadout's own code examples compile. Pass
zizmor 1.31.1 (offline) passes the harden-workflow skill's example workflow with no findings and reports template injection, pull_request_target, unpinned actions, persisted credentials and default permissions in an agent-triage workflow
  • Applied 7 loadout files at every agent's paths
  • ✓ place the example workflow: bash -c mkdir -p good/.github/workflows && cp .agents/skills/harden-workflow/references/issue-triage.yml good/.github/workflows/ && echo placed
  • ✓ hardened workflow: no findings: uvx zizmor@1.31.1 --offline good
  • ✓ every uses: is pinned to a full SHA with a version comment: bash -c grep -E '^\s*- uses:' good/.github/workflows/issue-triage.yml | grep -vE '@[0-9a-f]{40} # v[0-9]' && exit 1; echo pinned
  • ✓ unsafe agent-triage workflow is caught: bash -c uvx zizmor@1.31.1 --offline bad; echo "exit=$?"
  • Covers: SHA-pinned actions with version comments
  • Covers: permissions: {} at the top with per-job grants; persist-credentials: false
  • Covers: Untrusted issue text passed through env, never expanded inside run
  • Covers: pull_request_target and unpinned agent actions reported

What this means: a script checked the files themselves: they parse, they install where each agent looks, any MCP config is sound, the safety scan is clean, references between files resolve, and, where a free and repeatable fixture exists, a small starter project builds with the loadout applied. No AI agent was run. Per-agent results ("tested with agent") are separate and stay untested until an agent actually runs.

Install

Choose your agent. Run the commands from your project root, or download a zip already laid out in the right folders.

Claude Code reads CLAUDE.md (imports AGENTS.md with @AGENTS.md) and skills from .claude/skills/.

Download zip
curl -fsSL --create-dirs -o AGENTS.md https://loadout.jnw-it.com/raw/github-actions-hardening/1.0.0/AGENTS.md
curl -fsSL --create-dirs -o CLAUDE.md https://loadout.jnw-it.com/raw/github-actions-hardening/1.0.0/CLAUDE.md
curl -fsSL --create-dirs -o .claude/skills/harden-workflow/SKILL.md https://loadout.jnw-it.com/raw/github-actions-hardening/1.0.0/skills/harden-workflow/SKILL.md
curl -fsSL --create-dirs -o .claude/skills/harden-workflow/references/issue-triage.yml https://loadout.jnw-it.com/raw/github-actions-hardening/1.0.0/skills/harden-workflow/references/issue-triage.yml
FileGoes to
AGENTS.mdAGENTS.md
CLAUDE.mdCLAUDE.md
skills/harden-workflow/SKILL.md.claude/skills/harden-workflow/SKILL.md
skills/harden-workflow/references/issue-triage.yml.claude/skills/harden-workflow/references/issue-triage.yml

Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want.

Files

Rules AGENTS.md Rules for writing and changing GitHub Actions workflows, including ones that run AI agents. 33 lines
Download Raw
# AGENTS.md: GitHub Actions hardening

Rules for writing or changing anything under `.github/workflows/`, including workflows that run AI agents on issues
and pull requests. A workflow is code that runs with the repository's tokens and secrets; review it like code.

## Every workflow
- Pin every third-party action to a full commit SHA with the version in a comment:
  `uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1`. Tags and branches can be moved.
  Keep pins current with Dependabot (`package-ecosystem: github-actions`), not by switching back to tags.
- `permissions: {}` at the top, then grant each job only what it needs (`contents: read`, `issues: write`, ...).
- `actions/checkout` with `persist-credentials: false` unless a later step must push.
- Never write `${{ github.event.* }}`, `${{ github.head_ref }}` or other attacker-controllable values inside `run:`.
  Pass them through `env:` and quote the variable: `TITLE: ${{ github.event.issue.title }}` then `"$TITLE"`.
- Secrets only in the steps that need them, through `env:` or `with:`. Never echo them or write them to files that
  are uploaded as artifacts.
- Pin runner images (`ubuntu-24.04`, not `ubuntu-latest`) when reproducibility matters.

## Triggers
- Avoid `pull_request_target` and `workflow_run`. If one is unavoidable, never check out or run the pull request's
  code in that job, and never give it secrets.
- Code from forks runs under `pull_request`, with a read-only token and no secrets. Keep it that way.

## Workflows that run AI agents
- Issue and PR text is untrusted input to the agent. Run the agent job with a read-only token and no write secrets.
- Split "think" from "act": the agent job writes its proposal to an artifact or output; a separate job with narrow
  write permission applies it after checks (labels from a fixed list, comments only, no pushes to default branches).
- Pin agent actions and CLIs to exact versions, and cap spend and turns where the tool supports it.

## Before finishing
- Run the `harden-workflow` skill: `uvx zizmor@1.31.1 --offline .github/workflows` must report no findings, or each
  remaining finding is justified in a comment and the PR description.
- Do not push workflow changes to the default branch or re-run workflows with secrets unless the user asks.
Adapter CLAUDE.md Imports AGENTS.md for Claude Code. 10 lines
Download Raw
# CLAUDE.md

@AGENTS.md

## Claude Code notes
- Skills from this loadout live in `.claude/skills/`: `harden-workflow`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From the Agent Loadout "GitHub Actions hardening for agent workflows" v1.0.0 — https://loadout.jnw-it.com/loadouts/github-actions-hardening/ -->
Adapter GEMINI.md Imports AGENTS.md for Gemini CLI. 10 lines
Download Raw
# GEMINI.md

@./AGENTS.md

## Gemini CLI notes
- Skills from this loadout live in `.agents/skills/`: `harden-workflow`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From the Agent Loadout "GitHub Actions hardening for agent workflows" v1.0.0 — https://loadout.jnw-it.com/loadouts/github-actions-hardening/ -->
Skill skills/harden-workflow/SKILL.md Audit with zizmor and fix each finding. 26 lines
Download Raw
---
name: harden-workflow
description: Audit and fix GitHub Actions workflows with zizmor before they are committed. Pins actions to commit SHAs, narrows permissions, removes template injection and unsafe triggers. Use for any change under .github/workflows.
---

# Harden a workflow

Example of the target shape: `references/issue-triage.yml`.

1. Audit offline (no token needed): `uvx zizmor@1.31.1 --offline .github/workflows`. Exit code 0 means no findings.
   With a `GH_TOKEN`, drop `--offline` to enable the online audits (known-vulnerable actions, impostor commits).
2. Fix by audit name:
   - `unpinned-uses`: resolve the SHA for the tag you want with
     `gh api repos/OWNER/REPO/git/ref/tags/TAG --jq .object.sha`. If `.object.type` is `tag` (annotated),
     follow it once more with `gh api repos/OWNER/REPO/git/tags/SHA --jq .object.sha`. Write
     `uses: OWNER/REPO@SHA # TAG`.
   - `template-injection`: move the expression into `env:` and reference `"$VAR"` in the script.
   - `excessive-permissions`: add `permissions: {}` at the top and per-job grants.
   - `artipacked`: set `persist-credentials: false` on checkout.
   - `dangerous-triggers`: switch `pull_request_target` to `pull_request`, or split privileged work into a job that
     never touches PR code.
3. Re-run zizmor until clean. If you must keep a finding, add an inline `# zizmor: ignore[audit-name]` comment with
   the reason, and mention it to the user.
4. Check the workflow still parses: `gh workflow view <file>` on a pushed branch, or `actionlint` if installed.
5. Summarise the changes for the user. Do not push to the default branch yourself.
Skill file skills/harden-workflow/references/issue-triage.yml Hardened example workflow. 38 lines
Download Raw
# Labels new issues from their title. Shows the hardened shape: SHA pins, no default permissions,
# per-job grants, no persisted credentials, untrusted text passed through env.
name: issue-triage
on:
  issues:
    types: [opened]

permissions: {}

jobs:
  triage:
    runs-on: ubuntu-24.04
    timeout-minutes: 5
    permissions:
      contents: read
      issues: write
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false
      - name: Pick a label from a fixed list
        id: pick
        env:
          TITLE: ${{ github.event.issue.title }}
        run: |
          label=question
          case "${TITLE,,}" in
            *bug*|*crash*|*error*) label=bug ;;
            *feature*|*request*) label=enhancement ;;
          esac
          echo "label=$label" >> "$GITHUB_OUTPUT"
      - name: Apply the label
        env:
          GH_TOKEN: ${{ github.token }}
          ISSUE: ${{ github.event.issue.number }}
          LABEL: ${{ steps.pick.outputs.label }}
        run: gh issue edit "$ISSUE" --repo "$GITHUB_REPOSITORY" --add-label "$LABEL"

All files: source zip · Licence MIT · By Agent Loadout · First listed

Credits

Public work that inspired this loadout. The wording here is our own; follow the links for the originals.

Installing and testing

How do I install GitHub Actions hardening for agent workflows in Claude Code?

From your project root, put AGENTS.md, CLAUDE.md, skills/harden-workflow/SKILL.md at .claude/skills/harden-workflow/SKILL.md, skills/harden-workflow/references/issue-triage.yml at .claude/skills/harden-workflow/references/issue-triage.yml. The "Claude Code" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/github-actions-hardening/claude-code.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install GitHub Actions hardening for agent workflows in Codex CLI?

From your project root, put AGENTS.md, skills/harden-workflow/SKILL.md at .agents/skills/harden-workflow/SKILL.md, skills/harden-workflow/references/issue-triage.yml at .agents/skills/harden-workflow/references/issue-triage.yml. The "Codex CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/github-actions-hardening/codex.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install GitHub Actions hardening for agent workflows in Cursor?

From your project root, put AGENTS.md, skills/harden-workflow/SKILL.md at .agents/skills/harden-workflow/SKILL.md, skills/harden-workflow/references/issue-triage.yml at .agents/skills/harden-workflow/references/issue-triage.yml. The "Cursor" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/github-actions-hardening/cursor.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install GitHub Actions hardening for agent workflows in Gemini CLI?

From your project root, put AGENTS.md, GEMINI.md, skills/harden-workflow/SKILL.md at .agents/skills/harden-workflow/SKILL.md, skills/harden-workflow/references/issue-triage.yml at .agents/skills/harden-workflow/references/issue-triage.yml. The "Gemini CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/github-actions-hardening/gemini-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install GitHub Actions hardening for agent workflows in Antigravity CLI?

From your project root, put AGENTS.md, skills/harden-workflow/SKILL.md at .agents/skills/harden-workflow/SKILL.md, skills/harden-workflow/references/issue-triage.yml at .agents/skills/harden-workflow/references/issue-triage.yml. The "Antigravity CLI" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/github-actions-hardening/antigravity-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install GitHub Actions hardening for agent workflows in GitHub Copilot?

From your project root, put AGENTS.md, skills/harden-workflow/SKILL.md at .agents/skills/harden-workflow/SKILL.md, skills/harden-workflow/references/issue-triage.yml at .agents/skills/harden-workflow/references/issue-triage.yml. The "GitHub Copilot" tab on this page has the curl commands, or download https://loadout.jnw-it.com/download/github-actions-hardening/copilot.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

Has GitHub Actions hardening for agent workflows been tested?

It passed the Level 1 automated install checks on 2026-10-17: the files parse, install to each agent's documented paths, and pass the safety scan. No AI agent has run it yet, so no agent pass or fail is claimed.

More for Claude Code, Codex, Cursor, Gemini CLI, Antigravity CLI and GitHub Copilot.