Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
basecamp avatar

Harden Github Actions

  • 8 installs
  • 76 repo stars
  • Updated July 31, 2026
  • basecamp/house-skills

harden-github-actions is a Claude Code skill that resolves zizmor security warnings and hardens GitHub Actions CI workflows.

About

harden-github-actions is a Claude Code skill for hardening GitHub Actions workflows against zizmor's security warnings. A developer uses it when a CI pipeline has zizmor findings such as template-injection, excessive-permissions, or artipacked. It provides a workflow order, auto-fix review steps, action SHA pinning with pinact, and a per-rule decision guide for when to fix versus suppress.

  • Resolves zizmor security warnings in GitHub Actions workflows
  • Pins actions to SHA hashes with pinact and scopes permissions per job
  • Per-rule decision guide covering when to fix vs suppress

Harden Github Actions by the numbers

  • 8 all-time installs (skills.sh)
  • Ranked #1,684 of 2,203 Security skills by installs in the Skillselion catalog
  • Data as of Aug 5, 2026 (Skillselion catalog sync)
At a glance

harden-github-actions capabilities & compatibility

Capabilities
ci security audit · action pinning · workflow hardening
Works with
github
Use cases
security audit · ci cd · devops
From the docs

What harden-github-actions says it does

zizmor identifies security vulnerabilities in GitHub Actions workflows. This skill documents the decision guidelines for resolving each warning type: when to fix, how to fix, and when to suppress with
SKILL.md
Run `pinact run --min-age 10` from the repository root. This pins all actions in `.github/workflows/` to SHA hashes
SKILL.md
npx skills add https://github.com/basecamp/house-skills --skill harden-github-actions

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs8
repo stars76
Last updatedJuly 31, 2026
Repositorybasecamp/house-skills

What it does

Harden GitHub Actions CI workflows by resolving zizmor warnings and pinning actions to SHA hashes.

Who is it for?

Fixing zizmor findings and hardening GitHub Actions workflows.

When should I use this skill?

You are resolving zizmor warnings, hardening CI, or pinning actions to SHA hashes.

What you get

A hardened CI pipeline with a clean zizmor check and correctly justified suppressions.

  • hardened GitHub Actions workflows
  • SHA-pinned actions
  • justified suppression comments

By the numbers

  • 7-step workflow order
  • decision guide covers 11 zizmor rules

Files

SKILL.mdMarkdownGitHub ↗

Resolving Zizmor Warnings in GitHub Actions

Overview

zizmor identifies security vulnerabilities in GitHub Actions workflows. This skill documents the decision guidelines for resolving each warning type: when to fix, how to fix, and when to suppress with an inline comment explaining why.

Core principle: Fix the vulnerability whenever possible. Suppress only when the fix would break required functionality, and always include a reason in the suppression comment.

Prerequisites

This work should be done on a branch in a git worktree. Before starting any work, verify you are in the worktree directory and on the correct branch:

pwd          # should be the worktree path
git branch   # should show the feature branch, not main

Workflow Order

Always work in this order. Each step is a separate commit.

1. Add zizmor CI job using the standard template 2. Configure dependabot to batch github-actions updates weekly 3. Add local workflow linting to bin/setup and bin/ci (see below). Skip if these scripts don't exist in the project. 4. Pin actions with pinact run 5. Address zizmor warnings by severity (high → medium → low → informational). 6. Ensure all permissions are job-level. Check every workflow file for top-level permissions: blocks. Replace with permissions: {} and add per-job permissions. This goes beyond what zizmor flags — zizmor misses single-job workflows. Commit. 7. Run actionlint and fix any findings. Commit.

Running pinact

Run pinact run --min-age 10 from the repository root. This pins all actions in .github/workflows/ to SHA hashes, skipping any versions published less than 10 days ago.

Running zizmor

Always run zizmor with a GitHub token so that online audits (like ref-version-mismatch and impostor-commit) can resolve SHAs against the GitHub API. Without a token, these audits are silently skipped and findings will only surface in CI.

GITHUB_TOKEN=$(gh auth token) zizmor .

Filter severity by passing the flag --min-severity=<level> where level can be high, medium, or low. Informational warnings may be emitted by omitting this flag entirely.

Auto-fix workflow

For each severity level (high, then medium, then low, then informational):

1. Run zizmor --fix=all --min-severity=<level> . to auto-correct fixable findings (--fix alone uses safe mode which silently holds back some fixes; use --fix=all and rely on diff review as the safety net) 2. STOP and review the diff. Check each auto-fix against the Decision Guide below.

  • cache-poisoning fixes will disable caching — almost always revert these and suppress instead
  • artipacked fixes add persist-credentials: false — revert if the workflow needs git push
  • superfluous-actions fixes replace actions with inline code — always revert these and suppress instead
  • bot-conditions auto-fix replaces github.actor with user.login — revert and apply the dual check instead (see rule file)
  • template-injection fixes are generally correct

3. Revert any incorrect fixes 4. For reverted fixes, apply the correct resolution manually (e.g., suppress with a reason) 5. Manually fix anything --fix didn't handle. For `excessive-permissions`: you MUST research each action's permissions. Do not guess. See the permission research process below. 6. Run zizmor --min-severity=<level> . to verify a clean check at this severity level 7. Commit

After completing all default severity levels, run a pedantic pass:

1. Run zizmor --persona=pedantic --min-severity=high . 2. Address findings the same way as above — the most common pedantic finding is excessive-permissions on single-job workflows where zizmor's default persona doesn't flag it. Apply the same fix: permissions: {} at workflow level, scoped per job. 3. Run zizmor --persona=pedantic --min-severity=high . to verify clean 4. Commit

Decision Guide by Rule

When you encounter a zizmor finding, read the corresponding rule file in references/ for full decision guidance, suppression checklists, and examples. Only read the rules you need.

RuleFileAction
artipackedreferences/rule-artipacked.mdFix (add persist-credentials: false); suppress only if job does git push
template-injectionreferences/rule-template-injection.mdAlways fix (move expressions to env: vars)
excessive-permissionsreferences/rule-excessive-permissions.mdAlways fix (set permissions: {} at workflow level, scope per job)
dangerous-triggersreferences/rule-dangerous-triggers.mdFix or suppress with 5-point checklist
secrets-outside-envreferences/rule-secrets-outside-env.mdFix (add environment:) or suppress with 3-point checklist
bot-conditionsreferences/rule-bot-conditions.mdAlways fix (dual check: actor + user.login); revert auto-fix
superfluous-actionsreferences/rule-superfluous-actions.mdAlways suppress (never replace with inline code)
cache-poisoningreferences/rule-cache-poisoning.mdSuppress (default); revert auto-fixes; only escalate if custom cache keys
unpinned-imagesreferences/rule-unpinned-images.mdSuppress (default); digest pinning is nontrivial
dependabot-executionreferences/rule-dependabot-execution.mdFix or suppress with 3-point checklist
dependabot-cooldownreferences/rule-dependabot-cooldown.mdAlways fix (add cooldown: default-days: 10 to all ecosystems)

Permission mappings for excessive-permissions are in references/permission-mappings.md.

For findings not covered in this skill, consult https://docs.zizmor.sh/audits/ for detailed explanations and resolution guidance.

Suppression Format

Always use inline comments with the rule name and a reason:

# zizmor: ignore[rule-name] -- reason why suppression is necessary

The -- separator before the reason is a convention for readability. Never suppress without a reason. If you can't articulate why the fix would break something, apply the fix instead.

Standard Zizmor CI Job

Add this job to the repository's main CI workflow file (often ci.yml or ci-checks.yml).

Placement matters. Before inserting, find the existing lint job (rubocop, eslint, golangci-lint, etc.) in the workflow and place lint-actions immediately after it. If there is no lint job, place it immediately before the first test job. Never append it to the end of the file — it is a linting concern, not a test or deployment step:

lint-actions:
  name: GitHub Actions audit
  runs-on: ubuntu-latest

  steps:
    - uses: actions/checkout@v6
      with:
        persist-credentials: false

    - name: Run actionlint
      uses: rhysd/actionlint@v1.7.11

    - name: Run zizmor
      uses: zizmorcore/zizmor-action@v0.5.2
      with:
        advanced-security: false

Use version tags, not SHA hashes — run pinact run --min-age 10 immediately after adding this job to pin them. This ensures the SHAs match what pinact produces for the rest of the workflow.

Before adding this job, check if the workflow already has a standalone `actionlint` job. If it does, remove it — lint-actions replaces it. Do not create duplicate actionlint runs.

Local Workflow Linting

If the project has a bin/ci script (or equivalent like config/ci.rb), add workflow linting so developers catch issues locally before pushing. If bin/setup also exists, add tool installation there too. Skip this section entirely if there is no local CI script.

bin/setup — tool installation

Check if actionlint, shellcheck, and zizmor are already installed. If not, install them using the platform's package manager. Read the existing bin/setup script to understand its conventions before adding to it.

shellcheck is required — actionlint uses it to lint shell scripts in run: blocks. Without shellcheck, actionlint silently skips script checks and local results won't match CI.

Install all three tools using the same pattern:

for tool in actionlint shellcheck zizmor; do
  if ! command -v "$tool" &> /dev/null; then
    if command -v brew &> /dev/null; then
      brew install "$tool"
    elif command -v pacman &> /dev/null; then
      sudo pacman -S --noconfirm "$tool"
    else
      echo "Error: install $tool manually" >&2
      exit 1
    fi
  fi
done

Adapt this to match the script's existing style (e.g., if it uses functions, conditionals, or a different error pattern, follow that convention).

bin/ci — running the linters

Add actionlint and zizmor as separate steps. Read the existing bin/ci script to understand its conventions before adding to it.

# Lint GitHub Actions workflows
actionlint
zizmor .

Each tool should be a separate command so failures are clearly attributable. Place these near other linting steps if the script has them.

Examples

Dependabot Configuration

GitHub Actions entry

Ensure .github/dependabot.yml includes a github-actions entry with batching. The schedule must be weekly — not daily.

- package-ecosystem: github-actions
  directory: "/"
  groups:
    github-actions:
      patterns:
        - "*"
  schedule:
    interval: weekly
  cooldown:
    default-days: 7

The groups block batches all action updates into a single PR instead of one PR per action.

Cooldown on all ecosystems

Add cooldown to every ecosystem entry in dependabot.yml. Use semver-granular cooldowns for real package ecosystems so low-risk patches flow faster while major bumps get more soak time:

# For package ecosystems (bundler, npm, gomod, gradle, pip, etc.)
cooldown:
  semver-major-days: 7
  semver-minor-days: 3
  semver-patch-days: 2
  default-days: 7

# For github-actions (semver-granular keys are NOT supported)
cooldown:
  default-days: 7

If an ecosystem entry is missing the cooldown block, add it. If an existing cooldown block has different values, override them with the values above — these are the standard.

Common Mistakes

MistakeCorrection
Guessing what permissions an action needsRead the action's README. If it's not in the permission mappings table, research it before proceeding.
Accepting cache-poisoning auto-fixes without review--fix=all disables caching; almost always revert and suppress instead
Suppressing without a reasonAlways explain WHY the fix can't be applied
Suppressing template-injectionThis should always be fixed, never suppressed
Adding persist-credentials: false to a workflow that does git pushSuppress artipacked with a comment instead
Fixing permissions by removing the block entirelyMove to job-level, don't remove — implicit permissions may be too broad
Using --fix instead of --fix=allSafe mode silently holds back fixes; use --fix=all and review the diff
Committing without verifying clean zizmor outputAlways re-run zizmor --min-severity=<level> . before committing
Analyzing all findings up front before starting workFollow the workflow order step by step — CI job, dependabot, local linting, pin, then fix by severity
Adding the zizmor CI job at the end of the workflow filePlace it near existing lint jobs — it's a linting concern, not a test
Replacing an action with inline code for superfluous-actionsAlways suppress — actions are more maintainable and receive upstream fixes
Not specifying permissions on reusable workflow caller jobsCaller jobs must declare permissions; reusable workflows inherit from the caller
Adding tools to bin/setup when there's no bin/ciOnly add local linting if a local CI script exists to run the tools
Running commands in the main repo instead of the worktreeVerify pwd and git branch before starting

Common PR Feedback (Incorrect or Misleading)

Automated reviewers (Copilot, cubic, etc.) frequently flag these. They are wrong or misleading — dismiss them.

FeedbackWhy it's wrong
ruby/setup-ruby with bundler-cache: true needs actions: writeNo. Bundler cache works with contents: read. The cache API uses the implicit GITHUB_TOKEN. Do not add actions: write.
persist-credentials: false will break git fetch / git worktreeOnly true for private repos. All our target repos are public — unauthenticated HTTPS fetch works fine.
cooldown is not a valid Dependabot configuration keyIt is valid. GitHub added cooldown to Dependabot v2 config in late 2025. Copilot's training data predates this feature.
Checkout version inconsistency (v3 in existing jobs vs v6 in lint-actions)The skill pins existing versions as-is; upgrading is dependabot's job after merge. The lint-actions job template uses v6 independently.

Related skills

Securityappsecaudit

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.