Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
da3b17d
Update harden-github-actions skill from co-development flywheel
flavorjones Mar 19, 2026
c2be5ed
Add suppression checklists and hard-stop clauses to harden-github-act…
flavorjones Mar 19, 2026
739d154
Extract guidance for zizmor findings into rule-specific files
flavorjones Mar 19, 2026
6fe6117
Extract rule guidance into separate reference files for progressive d…
flavorjones Mar 19, 2026
cf34832
Improve cache-poisoning rule: context-specific suppression comments
flavorjones Mar 19, 2026
d16fa07
Add guidance to remove existing actionlint job when adding lint-actions
flavorjones Mar 19, 2026
f77229f
Add pedantic zizmor pass to catch single-job workflow permissions
flavorjones Mar 19, 2026
91c6092
Add unpinned-images rule with digest lookup instructions
flavorjones Mar 19, 2026
8dbc4eb
Simplify unpinned-images rule: suppress by default
flavorjones Mar 19, 2026
8923ad6
Run zizmor with GITHUB_TOKEN to enable online audits locally
flavorjones Mar 19, 2026
f874bac
Add actionlint step to workflow order
flavorjones Mar 20, 2026
2eb39a2
Update bot-conditions rule: use dual actor + user.login check
flavorjones Mar 20, 2026
ffe7e22
Add step to enforce job-level permissions on all workflows
flavorjones Mar 20, 2026
82ea6f0
Add local workflow linting guidance for bin/setup and bin/ci
flavorjones Mar 20, 2026
da43535
Incorporate learnings from Jeremy's reviews
flavorjones Mar 20, 2026
a0cbd3d
Remove incorrect scorecard permissions: read-all exception
flavorjones Mar 20, 2026
ef2e125
Add shellcheck to bin/setup requirements — actionlint needs it for sc…
flavorjones Mar 20, 2026
98b3c93
Add lexxy PR as example for bin/setup and config/ci.rb local linting
flavorjones Mar 20, 2026
57c51ad
Use version tags in CI job template, run pinact after to pin SHAs
flavorjones Mar 20, 2026
e4093a3
Fix zizmor-action version tag to v0
flavorjones Mar 20, 2026
18a4c02
Use exact version tags for actionlint and zizmor-action — no major ve…
flavorjones Mar 20, 2026
8272d1f
Skip local linting section if no bin/ci exists — bin/setup alone is n…
flavorjones Mar 20, 2026
3863a34
Add bin/setup-without-bin/ci to common mistakes
flavorjones Mar 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
267 changes: 267 additions & 0 deletions skills/harden-github-actions/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
---
name: zizmor-resolution
description: >
Use when resolving zizmor warnings in GitHub Actions workflows, hardening CI
pipelines, or pinning actions to SHA hashes. Covers artipacked, template-injection,
excessive-permissions, secrets-outside-env, dependabot-execution, and when to
suppress vs fix.

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

This skill frontmatter is missing a triggers: list. Other skills in this repo consistently include triggers: (see e.g. skills/install-md/SKILL.md), and the skill-crafting guide documents it as part of the required frontmatter. Add appropriate trigger phrases so the skill can be activated reliably.

Suggested change
suppress vs fix.
suppress vs fix.
triggers:
- zizmor
- zizmor warnings
- harden github actions
- github actions security
- github actions workflow hardening
- pin github actions to sha
- pin actions to sha

Copilot uses AI. Check for mistakes.
---

# Resolving Zizmor Warnings in GitHub Actions
Comment on lines +1 to +10

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

The frontmatter name doesn't match the skill directory/PR intent (skills/harden-github-actions/ vs name: zizmor-resolution). This will make the skill hard to discover/invoke consistently; rename the skill name (and ideally the H1) to harden-github-actions to match the folder and README entry format used by other skills.

Copilot uses AI. Check for mistakes.

## 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.

Comment on lines +10 to +20

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

Per AGENTS.md's contributing checklist, adding a new skill also requires adding it to the README skills table. This PR introduces skills/harden-github-actions/ but README.md doesn't list it yet; please add an entry so it's discoverable.

Copilot uses AI. Check for mistakes.
## 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:

```bash
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. **Pin actions** with `pinact run`
2. **Address zizmor warnings** by severity (high → medium → low → informational).
3. **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.
4. **Run actionlint** and fix any findings. Commit.
5. **Add zizmor CI job** using the standard template
6. **Add local workflow linting** to `bin/setup` and `bin/ci` (see below). Skip if these
scripts don't exist in the project.
7. **Configure dependabot** to batch github-actions updates weekly

## 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.

```bash
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.

| Rule | File | Action |
|------|------|--------|
| `artipacked` | `references/rule-artipacked.md` | Fix (add `persist-credentials: false`); suppress only if job does `git push` |
| `template-injection` | `references/rule-template-injection.md` | Always fix (move expressions to `env:` vars) |
| `excessive-permissions` | `references/rule-excessive-permissions.md` | Always fix (set `permissions: {}` at workflow level, scope per job) |
| `dangerous-triggers` | `references/rule-dangerous-triggers.md` | Fix or suppress with 5-point checklist |
| `secrets-outside-env` | `references/rule-secrets-outside-env.md` | Fix (add `environment:`) or suppress with 3-point checklist |
| `bot-conditions` | `references/rule-bot-conditions.md` | Always fix (dual check: `actor` + `user.login`); revert auto-fix |
| `superfluous-actions` | `references/rule-superfluous-actions.md` | Always suppress (never replace with inline code) |
| `cache-poisoning` | `references/rule-cache-poisoning.md` | Suppress (default); revert auto-fixes; only escalate if custom cache keys |
| `unpinned-images` | `references/rule-unpinned-images.md` | Suppress (default); digest pinning is nontrivial |
| `dependabot-execution` | `references/rule-dependabot-execution.md` | Fix or suppress with 3-point checklist |
| `dependabot-cooldown` | `references/rule-dependabot-cooldown.md` | Always fix (add `cooldown: default-days: 10` to all ecosystems) |

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

Cooldown guidance is inconsistent: the rule table says to add cooldown: default-days: 10 for dependabot-cooldown, but later examples (and references/rule-dependabot-cooldown.md) use default-days: 7. Align these values so readers don't apply conflicting configurations.

Suggested change
| `dependabot-cooldown` | `references/rule-dependabot-cooldown.md` | Always fix (add `cooldown: default-days: 10` to all ecosystems) |
| `dependabot-cooldown` | `references/rule-dependabot-cooldown.md` | Always fix (add `cooldown: default-days: 7` to all ecosystems) |

Copilot uses AI. Check for mistakes.

Comment on lines +93 to +106

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

The decision-guide Markdown table starts each row with ||, which renders as an extra empty column in GitHub Markdown. Change the table rows to start with a single | so the table displays correctly.

Copilot uses AI. Check for mistakes.
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`),
placed near existing lint jobs (e.g., rubocop, eslint) since it's a linting concern:

```yaml
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
```
Comment on lines +128 to +145

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

The standard CI job template doesn't declare job-level permissions:. Earlier the skill instructs setting workflow-level permissions: {}; if users follow that, this job will fail (at minimum actions/checkout typically needs contents: read). Add an explicit minimal permissions: block to the lint-actions job in the template.

Copilot uses AI. Check for mistakes.

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:

```bash
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.

```bash
# 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

- **bin/setup + config/ci.rb**: [lexxy#882](https://github.com/basecamp/lexxy/pull/882)
- **Makefile**: [basecamp-sdk@aa1f2d50](https://github.com/basecamp/basecamp-sdk/commit/aa1f2d50)

## Dependabot Configuration

### GitHub Actions entry

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

```yaml
- 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:

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

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

If an ecosystem entry is missing the cooldown block, add it.

## Common Mistakes

| Mistake | Correction |
|---------|------------|
| Guessing what permissions an action needs | **Read 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 reason | Always explain WHY the fix can't be applied |
| Suppressing `template-injection` | This should always be fixed, never suppressed |
| Adding `persist-credentials: false` to a workflow that does `git push` | Suppress `artipacked` with a comment instead |
| Fixing permissions by removing the block entirely | Move to job-level, don't remove — implicit permissions may be too broad |
| Using `--fix` instead of `--fix=all` | Safe mode silently holds back fixes; use `--fix=all` and review the diff |
| Committing without verifying clean zizmor output | Always re-run `zizmor --min-severity=<level> .` before committing |
| Analyzing all findings up front before starting work | Follow the workflow order step by step — pin, then fix by severity, then CI job, then dependabot |
| Adding the zizmor CI job at the end of the workflow file | Place it near existing lint jobs — it's a linting concern, not a test |
| Replacing an action with inline code for `superfluous-actions` | Always suppress — actions are more maintainable and receive upstream fixes |
| Not specifying permissions on reusable workflow caller jobs | Caller jobs must declare permissions; reusable workflows inherit from the caller |
| Adding tools to bin/setup when there's no bin/ci | Only add local linting if a local CI script exists to run the tools |
Comment on lines +250 to +266

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

The "Common Mistakes" Markdown table also starts rows with ||, which will render an unintended blank first column. Use a single leading | for standard Markdown table formatting.

Copilot uses AI. Check for mistakes.
| Running commands in the main repo instead of the worktree | Verify `pwd` and `git branch` before starting |
71 changes: 71 additions & 0 deletions skills/harden-github-actions/references/permission-mappings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# GitHub Actions Permission Mappings

GITHUB_TOKEN permission requirements for common GitHub Actions. Used by the
`harden-github-actions` skill when resolving `excessive-permissions` findings.

**If an action is not in this table, you MUST fetch its README on GitHub and read the
documented permission requirements before proceeding. Then add it to this table.**

## Actions

| Action | Permissions | Notes |
|--------|------------|-------|
| `actions/ai-inference` | `models: read` | + `contents: read` recommended; PAT required for MCP server feature |
| `actions/attest-build-provenance` | `id-token: write`, `attestations: write`, `contents: read` | + `packages: write` when `push-to-registry: true` for container images |
| `actions/cache` | none | Uses Actions cache service via implicit runner credentials, not GITHUB_TOKEN |
| `actions/checkout` | `contents: read` | |
Comment on lines +11 to +16

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

This Markdown table uses || at the start of each row/header, which creates an extra empty first column in GitHub Markdown rendering. Switch to a single leading | per row for proper table formatting.

Copilot uses AI. Check for mistakes.
| `actions/configure-pages` | none | Reads Pages config only; `enablement: true` needs a PAT with `pages:write` |
| `actions/create-github-app-token` | none | Authenticates via GitHub App credentials, not GITHUB_TOKEN; *generates* a token with its own permissions |
| `actions/create-release` | `contents: write` | Archived/unmaintained; consider `softprops/action-gh-release` instead |
| `actions/dependency-review-action` | `contents: read` | + `pull-requests: write` if `comment-summary-in-pr` is `always` or `on-failure` |
| `actions/deploy-pages` | `pages: write`, `id-token: write` | |
| `actions/download-artifact` | none | `actions: read` only when using `github-token` input to download from other repos/workflow runs |
| `actions/github-script` | depends on script | No fixed permissions; required permissions depend on which GitHub API calls the script makes. Key distinction: PR comments (`gh pr comment`, `updateIssueComment` on PR objects) need `pull-requests: write`; applying labels via the Issues API needs `issues: write`. Read the script to determine. |
| `actions/labeler` | `contents: read`, `pull-requests: write` | + `issues: write` only if the action needs to create labels that don't already exist |
| `actions/setup-go` | `contents: read` | |
| `actions/setup-java` | `contents: read` | |
| `actions/setup-node` | `contents: read` | |
| `actions/setup-ruby` | `contents: read` | |
| `actions/stale` | `issues: write`, `pull-requests: write` | + `contents: write` when `delete-branch: true` |
| `actions/upload-artifact` | none | Uses Actions artifact storage via implicit runner credentials |
| `actions/upload-pages-artifact` | none | Only creates a tar archive |
| `actions/upload-release-asset` | `contents: write` | Archived/unmaintained; consider `softprops/action-gh-release` instead |
| `anchore/sbom-action` | `contents: write` | + `actions: read` when attaching release assets (implicit for public repos) |
| `aquasecurity/trivy-action` | `contents: read` | + `security-events: write` when uploading SARIF; + `contents: write` when submitting SBOMs to Dependency Graph |
| `basecamp/sdk` (sub-actions) | varies | Contains 5 composite actions under `actions/`. Only `release-orchestrate` needs a token: `actions: read`, `contents: write`. The others (`conformance-run`, `rubric-check`, `service-drift`, `smithy-verify`) need none. |
| `cachix/install-nix-action` | none | Uses `github.token` only to avoid API rate limits when downloading Nix |
| `dependabot/fetch-metadata` | `pull-requests: read` | + `pull-requests: write` for auto-approving; + `contents: write` for auto-merging; PAT required for `alert-lookup` or `compat-lookup` |
| `dev-build-deploy/commit-me` | `pull-requests: read` | + `pull-requests: write` when `update-labels: true`; + `contents: write` for automatic rebase-merge detection |
| `devcontainers/ci` | none | When pushing images to ghcr.io, the workflow must log in separately (e.g., `docker/login-action`), which requires `packages: write` |
| `docker/build-push-action` | `contents: read` | Registry auth (e.g., `packages: write` for GHCR) is handled by `docker/login-action`, not this action |
| `docker/login-action` | none | The action itself needs no permissions; pass `secrets.GITHUB_TOKEN` as the `password` input. The *token* needs `packages: write` for GHCR push, `packages: read` for pull-only. |
| `docker/metadata-action` | `contents: read` | Reads repo context (tags, branches, commits) only |
| `docker/setup-buildx-action` | none | Only configures the Docker Buildx builder environment |
| `docker/setup-qemu-action` | none | Installs QEMU static binaries for multi-platform builds |
| `dorny/paths-filter` | `pull-requests: read` | Only on `pull_request` events; on `push` events uses git commands directly and needs no permissions |
| `elastic/docs` | not a standard action | Elastic's internal documentation build tooling. Contains a composite action at `.github/actions/docs-preview` needing `issues: write` for PR comments, but not intended for external use. Check the repo source if encountered. |
| `github/codeql-action` (/init, /analyze, /upload-sarif) | `security-events: write`, `contents: read` | `security-events: write` for all advanced setup workflows; `contents: read` additionally required for private repos |
| `golangci/golangci-lint-action` | `contents: read` | + `pull-requests: read` if using `only-new-issues`; + `checks: write` for inline PR annotations |
| `google-github-actions/release-please-action` | `contents: write`, `pull-requests: write` | May also need "Allow GitHub Actions to create and approve pull requests" enabled in repo settings |
| `goreleaser/goreleaser-action` | `contents: write` | Token passed via `env: GITHUB_TOKEN`; PAT with `repo` scope needed for cross-repo operations (e.g., Homebrew taps) |
| `gradle/actions` (sub-actions) | varies | `setup-gradle`: none for basic use; `contents: write` for dependency graph submission; `pull-requests: write` for PR comment summaries. `dependency-submission`: `contents: write`. `wrapper-validation`: none. |
| `ko-build/setup-ko` | none | + `packages: write` when pushing container images to ghcr.io; no permissions needed if only building |
| `necko-actions/setup-smithy` | none | Installs Smithy CLI and adds to PATH |
| `ossf/scorecard-action` | `security-events: write`, `id-token: write`, `contents: read` | For public repos with `publish_results: true`; private repos also need `issues: read`, `pull-requests: read`, `checks: read` |
| `reviewdog/action-rubocop` | `contents: read`, `pull-requests: write` | For `github-pr-review` and `github-pr-check` reporters; `checks: write` may also be needed for `github-check` reporter |
| `rhysd/actionlint` | none | Not a standard action (no `action.yml`); used by downloading the binary via shell script or Docker. Does not use GITHUB_TOKEN. |

Copilot AI Mar 20, 2026

Copy link

Choose a reason for hiding this comment

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

The rhysd/actionlint row states it's "Not a standard action (no action.yml)" and is used by downloading a binary, but the skill's CI template uses it via uses: rhysd/actionlint@.... This is internally inconsistent and may confuse permission research; clarify whether it's a GitHub Action and keep the note focused on its permission requirements (e.g., that it doesn't use GITHUB_TOKEN).

Suggested change
| `rhysd/actionlint` | none | Not a standard action (no `action.yml`); used by downloading the binary via shell script or Docker. Does not use GITHUB_TOKEN. |
| `rhysd/actionlint` | none | Lints workflow files only; does not call GitHub APIs or use `GITHUB_TOKEN`. |

Copilot uses AI. Check for mistakes.
| `securego/gosec` | none | Runs gosec in Docker; no token input. If uploading SARIF via `github/codeql-action/upload-sarif`, that action needs `security-events: write`. |
| `sigstore/cosign-installer` | none | Only installs cosign. Subsequent cosign *commands* need `id-token: write` for OIDC keyless signing. |
| `softprops/action-gh-release` | `contents: write` | + `discussions: write` if using `discussion_category_name` |
| `rubygems/configure-rubygems-credentials` | `id-token: write` | + `contents: write` only if using `bundle exec rake release` for git push |
| `zizmorcore/zizmor-action` | none | With `advanced-security: false` (recommended in this skill). With `advanced-security: true`: `security-events: write`; private repos also need `contents: read`, `actions: read`. |
| `zzak/action-discord` | `contents: read` | Reads commit metadata for Discord webhook notifications |

## Common patterns (not specific actions)

| Pattern | Permissions | Notes |
|---------|------------|-------|
| `npm publish --provenance` | `id-token: write` | OIDC for npm provenance attestation |
| Push to GHCR | `packages: write` | |
| Sigstore/cosign signing | `id-token: write` | |
| Attestation actions | `attestations: write` | |
Loading
Loading