codeql-resolver/skills/codeql-permission-classification/SKILL.md
Permission requirements for GitHub Actions
npx skillsauth add jacobpevans/claude-code-plugins codeql-permission-classificationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Single source of truth for GitHub Actions permission requirements.
GitHub Actions provides these permission scopes:
contents # Read/write repository content (checkout, tags, releases)
pull-requests # Read/write PR comments, reviews, assignments
issues # Read/write issue comments, labels, projects
deployments # Read/write deployment status
packages # Read/write packages
actions # Read/write GitHub Actions (runners, artifacts, caches)
checks # Read/write check runs and annotations
statuses # Read/write commit statuses
security-events # Read/write code scanning and secret scanning results
| Action | Required Permissions | Use Case |
|--------|----------------------|----------|
| actions/checkout@v6 | contents: read | Clone repository |
| actions/upload-artifact@v6 | None (usually) | Store build artifacts |
| actions/download-artifact@v6 | None (usually) | Retrieve artifacts |
| actions/setup-node@v6 | None | Install Node.js |
| actions/github-script@v6 | Depends on script | Usually contents: read minimum |
| actions/create-release@v1 | contents: write | Create GitHub release |
| github/codeql-action/upload-sarif@v2 | security-events: write | Upload CodeQL results |
Q1: Does your job use actions/checkout?
contents: readQ2: Does your job modify repository (create PR, tag, release)?
contents: writeQ3: Does your job post comments to PR or issues?
pull-requests: write (PR) or issues: write (issues)Q4: Does your job modify deployments?
deployments: writeQ5: Does your job use github.rest API?
Q6: Does your job call a reusable workflow?
Q7: Is your job just aggregating results with no API access?
permissions: {} (empty - no token needed)test:
runs-on: ubuntu-latest
permissions:
contents: read # For checkout
steps:
- uses: actions/checkout@v6
- run: npm test
release:
runs-on: ubuntu-latest
permissions:
contents: write # For creating release
steps:
- uses: actions/checkout@v6
- uses: actions/create-release@v1
with:
tag_name: v1.0.0
comment:
runs-on: ubuntu-latest
permissions:
contents: read # For checkout
pull-requests: write # For creating comment
steps:
- uses: actions/checkout@v6
- run: npm run lint | tee lint-result.txt
- uses: actions/github-script@v6
with:
script: |
github.rest.pulls.createReview({
pull_number: context.issue.number,
body: 'Lint check passed'
})
If .github/workflows/_validate.yml contains:
jobs:
validate:
permissions:
contents: read
pull-requests: write # This job posts comments
steps: ...
Then the caller must declare:
validate:
permissions:
contents: read
pull-requests: write # Union of nested job permissions
uses: ./.github/workflows/_validate.yml
gate:
runs-on: ubuntu-latest
permissions: {} # Explicitly no permissions needed
steps:
- uses: re-actors/alls-green@release/v1
with:
allowed-skips: job1, job2
jobs: ${{ toJSON(needs) }}
Important: When a job calls a reusable workflow via uses:, the caller job must declare permissions for all jobs within the reusable workflow.
Why: The reusable workflow's nested jobs inherit the permissions from the caller's GITHUB_TOKEN, so the caller must declare everything the callee needs.
Example mismatch (WRONG):
# ci-gate.yml
validate:
# Missing permissions!
uses: ./.github/workflows/_validate.yml
# _validate.yml
jobs:
check:
permissions:
contents: read
pull-requests: write
steps: ...
Correct:
# ci-gate.yml
validate:
permissions:
contents: read
pull-requests: write # Declare what _validate.yml needs
uses: ./.github/workflows/_validate.yml
Without explicit permissions:
GITHUB_TOKEN: read-all (dangerous!)GITHUB_TOKEN: read-write (very dangerous!)With explicit permissions:
❌ Mistake 1: Forgetting PR/issues permissions
# WRONG - Creates comment but no permission
comment:
steps:
- uses: actions/github-script@v6
with:
script: github.rest.issues.createComment({...})
✅ Correct:
comment:
permissions:
issues: write # ← Add this
steps:
- uses: actions/github-script@v6
with:
script: github.rest.issues.createComment({...})
❌ Mistake 2: Not declaring union of reusable workflow permissions
# WRONG - Reusable workflow's jobs need more permissions
my-job:
permissions:
contents: read # But reusable also needs pull-requests: write
uses: ./.github/workflows/_validate.yml
✅ Correct:
my-job:
permissions:
contents: read
pull-requests: write # Include all permissions from reusable
uses: ./.github/workflows/_validate.yml
❌ Mistake 3: Excessive permissions
# WRONG - Too much permission
build:
permissions:
contents: write # Unnecessary - only reads files
steps:
- uses: actions/checkout@v6
- run: npm build
✅ Correct (least-privilege):
build:
permissions:
contents: read # Minimum needed for checkout
steps:
- uses: actions/checkout@v6
- run: npm build
Run CodeQL scan locally:
codeql database create my_db --language=actions --source-root=.
codeql database analyze my_db actions/security-and-quality.qls --format=sarif-latest --output=results.sarif
Or wait for GitHub to scan and check:
gh pr checks # See if CodeQL permission alerts appear
tools
Use when installing or choosing CLI tools in a Nix flake repo, editing flake.nix or home-manager config, or when tempted to pip/pipx/uv/brew/npm install anything. Tools come from the dev shell or nix shell — never ad-hoc package managers.
testing
Use when creating or editing GitHub Actions workflows that call reusable workflows (uses: OWNER/repo/.github/workflows/...) — org owner references must be the literal current org, and shared-CI homes are under dryvist.
development
Use when adding or editing .pre-commit-config.yaml, wiring pre-commit hooks into a repo, scaffolding a new repo's lint/hook setup, or deciding where a hook or shared lint config should live. Covers the canonical nix-devenv/dryvist-.github architecture, profiles, and consumer patterns.
testing
Check PR merge readiness, sync local repo, cleanup stale worktrees; optional cross-repo sweep and stale-branch prune modes