A fast, security-first static analyzer with heuristic auto-fix for GitHub Actions workflows.
Find injection, credential leakage, supply-chain, and pipeline-poisoning bugs in .github/workflows/ β and let -fix repair most of them for you.
- Security-first by design. Full coverage of the OWASP Top 10 CI/CD Security Risks β code/env/path/output injection, untrusted checkouts, artifact & cache poisoning, ref confusion, impostor commits, and more.
- Semantic, not regex. A real AST + expression parser + shell taint analyzer (
mvdan.cc/sh) β not string matching. Cross-step and cross-file taint propagation through$GITHUB_ENV, reusable workflow boundaries, and shell function arguments. - Auto-fix that ships PRs. 27+ rules carry an auto-fixer.
sisakulint -fix onrewrites the YAML in place;-fix dry-runpreviews the diff first. - Built for CI. SARIF output drops straight into reviewdog for inline PR review comments.
- AI-agent aware. Detects prompt injection, dangerous tool exposure, unsafe sandbox flags, and execution-order issues in claude-code-action and similar AI agent integrations (the Clinejection attack class).
| sisakulint | actionlint | zizmor | StepSecurity | CodeQL | |
|---|---|---|---|---|---|
| Workflow syntax / shell linting | β | β | partial | β | β |
| OWASP CI/CD Top-10 coverage | full | β | partial | runtime only | partial |
| Cross-file taint for reusable workflows | β | β | β | β | β |
Cross-step / cross-job taint via $GITHUB_ENV |
β | β | β | runtime | β |
| Shell-aware taint (incl. function args) | β | β | β | β | partial |
| AI agent action rules (Clinejection) | β | β | β | β | β |
| Auto-fix | 27+ rules | β | β | N/A | |
| SARIF + reviewdog | β | β | β | β | β |
Static-analysis tools sit upstream of runtime tools β sisakulint catches bugs at PR review time, before any workflow runs.
- Quick start
- Installation
- What it detects
- Rule reference
- Example: detecting real vulnerabilities
- Auto-fix
- SARIF + reviewdog integration
- Configuration
- Architecture
- BlackHat Arsenal 2025
- Contributing
- License
- Citation
# macOS
brew tap sisaku-security/homebrew-sisakulint
brew install sisakulint
# Run in any repo with a .github/workflows/ directory
sisakulintOther ways to run:
sisakulint .github/workflows/release.yml # one file
sisakulint -fix dry-run # preview auto-fixes
sisakulint -fix on # apply auto-fixes
sisakulint -format "{{sarif .}}" # SARIF for CI
sisakulint -enable-rule missing-timeout-minutes # opt-in ruleExit codes: 0 = clean, 1 = findings, 2 = bad CLI args, 3 = fatal error.
brew tap sisaku-security/homebrew-sisakulint
brew install sisakulintgo install github.com/sisaku-security/sisakulint/cmd/sisakulint@latestRequires Go 1.25 or newer.
Download from the releases page and place it on $PATH:
# Linux example
curl -sSL -o sisakulint https://github.com/sisaku-security/sisakulint/releases/latest/download/sisakulint-linux-amd64
chmod +x sisakulint
sudo mv sisakulint /usr/local/bin/git clone https://github.com/sisaku-security/sisakulint.git
cd sisakulint
go build ./cmd/sisakulintThe easiest way to wire sisakulint into CI is the official sisaku-security/sisakulint-action. It installs the binary, runs the scan, renders findings as inline PR annotations, and (optionally) uploads SARIF to GitHub Code Scanning.
name: sisakulint
on:
pull_request:
paths: [".github/workflows/**"]
push:
branches: [main]
paths: [".github/workflows/**"]
permissions:
contents: read
pull-requests: write # inline PR annotations
security-events: write # only if upload-sarif: true
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
persist-credentials: false
- uses: sisaku-security/sisakulint-action@596af4ab15e8c5b232c74aa97525a0302e7b7af4 # v1.0.0
with:
fail-on: high # none | low | medium | high | critical
upload-sarif: true # send SARIF to GitHub Code ScanningUseful inputs: version, working-directory, args, config-file, autofix (off / on / dry-run), fail-on, upload-sarif, sarif-file. See the action's README for the full list.
Alternative: pipe SARIF into reviewdog
If you'd rather use reviewdog for inline PR review comments instead of GitHub annotations:
- uses: reviewdog/action-setup@v1
- name: sisakulint + reviewdog
env:
REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
curl -sSL -o sisakulint \
https://github.com/sisaku-security/sisakulint/releases/latest/download/sisakulint-linux-amd64
chmod +x sisakulint
./sisakulint -format "{{sarif .}}" \
| reviewdog -f=sarif -reporter=github-pr-review -filter-mode=nofilterFull documentation: https://sisaku-security.github.io/lint/
50+ rules across syntax, configuration, credentials, injection, checkout, supply chain, poisoning, access control, and AI-agent action security.
Click to expand the full rule list
| Category | Rule | Severity | Description | Fix | Docs |
|---|---|---|---|---|---|
| Syntax | id | Low | ID collision detection for jobs/env vars | docs | |
| env-var | Low | Environment variable name validation | docs | ||
| permissions | High | Permission scopes and values validation | Yes | docs | |
| workflow-call | Medium | Reusable workflow call validation | docs | ||
| job-needs | Low | Job dependency validation | docs | ||
| expression | Medium | Expression syntax validation | docs | ||
| cond | Medium | Conditional expression validation | Yes | docs | |
| deprecated-commands | High | Deprecated workflow commands detection | docs | ||
| Config | timeout-minutes | Low | Ensures timeout-minutes is set (opt-in) | Yes | docs |
| cache-bloat | Low | Cache bloat with restore/save pair | Yes | docs | |
| Credentials | credentials | High | Hardcoded credentials detection | Yes | docs |
| secret-exposure | High | Excessive secrets exposure detection | Yes | docs | |
| unmasked-secret-exposure | High | Unmasked derived secrets detection | Yes | docs | |
| artipacked | Critical | Credential leakage via persisted checkout | Yes | docs | |
| secrets-in-artifacts | High | Sensitive data in artifact uploads | Yes | docs | |
| secrets-inherit | High | Excessive secrets inheritance | Yes | docs | |
| secret-exfiltration | Critical | Secret exfiltration via network commands | docs | ||
| secret-in-log | Critical | Secret values printed to build logs (taint-tracked) | Yes | docs | |
| Injection | code-injection-critical | Critical | Untrusted input in privileged triggers | Yes | docs |
| code-injection-medium | Medium | Untrusted input in normal triggers | Yes | docs | |
| envvar-injection-critical | Critical | Untrusted input to $GITHUB_ENV (privileged) | Yes | docs | |
| envvar-injection-medium | Medium | Untrusted input to $GITHUB_ENV (normal) | Yes | docs | |
| envpath-injection-critical | Critical | Untrusted input to $GITHUB_PATH (privileged) | Yes | docs | |
| envpath-injection-medium | Medium | Untrusted input to $GITHUB_PATH (normal) | Yes | docs | |
| output-clobbering-critical | Critical | Untrusted input to $GITHUB_OUTPUT (privileged) | Yes | docs | |
| output-clobbering-medium | Medium | Untrusted input to $GITHUB_OUTPUT (normal) | Yes | docs | |
| argument-injection-critical | Critical | Command-line argument injection (privileged) | Yes | docs | |
| argument-injection-medium | Medium | Command-line argument injection (normal) | Yes | docs | |
| Checkout | untrusted-checkout | Critical | Untrusted PR code in privileged contexts | Yes | docs |
| untrusted-checkout-toctou-critical | Critical | TOCTOU with labeled events | Yes | docs | |
| untrusted-checkout-toctou-high | High | TOCTOU with deployment environment | Yes | docs | |
| Supply Chain | commit-sha | High | Action version pinning validation | Yes | docs |
| action-list | Low | Organization allowlist/blocklist enforcement | docs | ||
| impostor-commit | Critical | Fork network impostor commit detection | Yes | docs | |
| ref-confusion | High | Branch/tag name collision detection | Yes | docs | |
| known-vulnerable-actions | Varies | Known CVE detection via GitHub Advisories | Yes | docs | |
| archived-uses | Medium | Archived action/workflow detection | docs | ||
| unpinned-images | Medium | Container image digest pinning | docs | ||
| dependabot-github-actions | Medium | Missing github-actions ecosystem in dependabot.yaml | Yes | docs | |
| reusable-workflow-taint | Critical | Untrusted inputs in reusable workflow calls | Yes | docs | |
| Poisoning | artifact-poisoning-critical | Critical | Artifact poisoning and path traversal | Yes | docs |
| artifact-poisoning-medium | Medium | Third-party artifact download in untrusted triggers | Yes | docs | |
| cache-poisoning | High | Unsafe cache patterns with untrusted inputs | Yes | docs | |
| cache-poisoning-poisonable-step | High | Untrusted code execution after unsafe checkout | Yes | docs | |
| Access Control | improper-access-control | High | Label-based approval and synchronize events | Yes | docs |
| bot-conditions | High | Spoofable bot detection conditions | Yes | docs | |
| unsound-contains | Medium | Bypassable contains() in conditions | Yes | docs | |
| dangerous-triggers-critical | Critical | Privileged triggers without mitigations | Yes | docs | |
| dangerous-triggers-medium | Medium | Privileged triggers with partial mitigations | Yes | docs | |
| Other | obfuscation | High | Obfuscated workflow pattern detection | Yes | docs |
| self-hosted-runners | High | Self-hosted runner security risks | docs | ||
| request-forgery-critical | Critical | SSRF vulnerabilities (privileged) | Yes | docs | |
| request-forgery-medium | Medium | SSRF vulnerabilities (normal) | Yes | docs | |
| AI Actions | ai-action-unrestricted-trigger | High | AI agent actions with allowed_non_write_users: "*" |
docs | |
| ai-action-excessive-tools | High | Dangerous tools (Bash/Write/Edit) under untrusted triggers | docs | ||
| ai-action-prompt-injection | High | Untrusted input interpolated into AI agent prompts | docs | ||
| ai-action-unsafe-sandbox | High | Unsafe sandbox / safety-strategy settings | docs | ||
| ai-action-execution-order | Medium | AI agent action not the last step in a job | docs |
Given a workflow with several common security mistakes:
name: PR Comment Handler
on:
pull_request_target:
types: [opened, synchronize]
issue_comment:
types: [created]
jobs:
process-pr:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Echo PR title
run: |
echo "Processing PR: ${{ github.event.pull_request.title }}"
- name: Run build
run: npm install && npm run buildsisakulint -enable-rule missing-timeout-minutes flags every line that matters, with a code snippet and a link to the rule docs:
.github/workflows/demo.yaml:1:1: workflow does not have explicit 'permissions' block.
Follow the principle of least privilege.
See https://sisaku-security.github.io/lint/docs/rules/permissions/ [permissions]
1 π| name: PR Comment Handler
.github/workflows/demo.yaml:4:3: dangerous trigger (critical): pull_request_target +
issue_comment without any security mitigations.
See https://sisaku-security.github.io/lint/docs/rules/dangeroustriggersrulecritical/ [dangerous-triggers-critical]
4 π| pull_request_target:
.github/workflows/demo.yaml:13:9: the action ref should be a full length commit SHA. [commit-sha]
13 π| - uses: actions/checkout@v4
.github/workflows/demo.yaml:15:16: untrusted PR code checked out in pull_request_target context.
See https://sisaku-security.github.io/lint/docs/rules/untrustedcheckout/ [untrusted-checkout]
15 π| ref: ${{ github.event.pull_request.head.sha }}
.github/workflows/demo.yaml:19:35: code injection (critical): github.event.pull_request.title
is interpolated into an inline script.
See https://sisaku-security.github.io/lint/docs/rules/codeinjectioncritical/ [code-injection-critical]
19 π| echo "Processing PR: ${{ github.event.pull_request.title }}"
[sisaku:π€] Detected 7 errors in 1 file checked
| Finding | OWASP | Severity | Auto-fix |
|---|---|---|---|
| Missing permissions block | CICD-SEC-02 | High | Yes |
| Dangerous privileged triggers | CICD-SEC-01 | Critical | Yes |
| Action not pinned to SHA | CICD-SEC-08 | High | Yes |
| Credential exposure (persist-credentials) | CICD-SEC-06 | Critical | Yes |
| Untrusted checkout | CICD-SEC-04 | Critical | Yes |
| Code injection | CICD-SEC-04 | Critical | Yes |
| Cache poisoning | CICD-SEC-09 | High | Yes |
Run sisakulint -fix on and most of these are repaired in place.
27+ rules ship with an auto-fixer. Two modes:
sisakulint -fix dry-run # show diff, don't write
sisakulint -fix on # apply changes to YAML filesAuto-fix-capable rules currently include: timeout-minutes, commit-sha, credentials, code-injection-*, envvar-injection-*, envpath-injection-*, output-clobbering-*, argument-injection-*, request-forgery-*, untrusted-checkout, untrusted-checkout-toctou-*, artifact-poisoning-*, cache-poisoning, cache-poisoning-poisonable-step, cache-bloat, artipacked, secrets-in-artifacts, secrets-inherit, secret-in-log, secret-exposure, unmasked-secret-exposure, improper-access-control, bot-conditions, unsound-contains, obfuscation, ref-confusion, impostor-commit, known-vulnerable-actions, dangerous-triggers-*, cond, permissions, dependabot-github-actions, reusable-workflow-taint (cross-file ChainFixer lifts callee ${{ inputs.X }} into a step-level env:).
A few representative fixes:
Code injection β move untrusted input into env:
Before:
- run: echo "Processing PR: ${{ github.event.pull_request.title }}"After:
- env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: echo "Processing PR: $PR_TITLE"commit-sha β pin action tags to full SHA, preserve original tag as comment
Before:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3After:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v3untrusted-checkout β add explicit ref in privileged contexts
Before:
on:
pull_request_target:
types: [opened, synchronize]
jobs:
build:
steps:
- uses: actions/checkout@v4After:
on:
pull_request_target:
types: [opened, synchronize]
jobs:
build:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.base.ref }}Notes
- Always review the diff (or use
-fix dry-run) before committing.commit-shacalls the GitHub API to resolve tags; unauthenticated requests are limited to 60/hr.- A handful of rules (e.g.
expression,secret-exfiltration,self-hosted-runners,action-list, AI-action rules) are warning-only because the right fix depends on the user's policy.
sisakulint -format "{{sarif .}}" # stdout
sisakulint -format "{{sarif .}}" > results.sarif # to file
sisakulint -format "{{sarif .}}" | reviewdog -f=sarif -reporter=github-pr-reviewA complete GitHub Actions recipe is in Installation β As a GitHub Action.
Generate a starter config:
sisakulint -init # writes .github/action.yamlThe config file lets you set per-repo allowlists, block lists, and rule overrides. Useful flags:
sisakulint -ignore "permissions" -ignore "SC2086" # mute specific rules / patterns
sisakulint -enable-rule missing-timeout-minutes # enable an opt-in rule
sisakulint -boilerplate # print a hardened workflow skeleton
sisakulint -debug # dump AST traversal + rule decisionsAdd to your VS Code settings.json:
"yaml.schemas": {
"https://github.com/sisaku-security/homebrew-sisakulint/raw/main/settings.json": "/.github/workflows/*.{yml,yaml}"
}-
Always review changes: Even though autofix is automated, always review the changes made to your workflow files before committing them
-
Commit SHA fixes require internet: The
commit-sharule needs to fetch commit information from GitHub, so it requires an active internet connection -
Rate limiting: The commit SHA autofix makes GitHub API calls, which are subject to rate limiting. Unauthenticated requests are capped at 60 requests per hour. Set a token to lift the limit to 5,000 req/h:
SISAKULINT_GITHUB_TOKEN(preferred β scoped to this tool)GITHUB_TOKEN(read from the process environment; inside a GitHub Actions step you must map it explicitly, e.g.env: GITHUB_TOKEN: ${{ github.token }}β the runner does not exportsecrets.GITHUB_TOKENtorun:steps automatically)GH_TOKEN(set bygh auth login)-github-token "$(gh auth token)"(CLI flag, highest priority)
A fine-grained PAT with
public_repo(or justMetadata: Read-onlyon the targets you pin) is sufficient. When sisakulint detects no token at startup, it now prints a warning. If the rate limit is exhausted mid-run, sisakulint aborts with a non-zero exit and skips writing partial output for the affected file rather than leaving a mix of pinned and unpinned actions on disk. -
Backup your files: Consider committing your changes or backing up your workflow files before running autofix
-
Not all rules support autofix: Some rules like
expression,permissions,issue-injection,cache-poisoning, anddeprecated-commandsrequire manual fixes as they depend on your specific use case -
Auto-fix capabilities: Currently,
timeout-minutes,commit-sha,credentials,untrusted-checkout, andartifact-poisoningrules support auto-fix. More rules will support auto-fix in future releases
.github/workflows/*.yml
β
βΌ
[ AST parser ] βββΊ [ Expression parser (${{ }}) ]
β β
βΌ βΌ
[ Shell parser (mvdan.cc/sh) ] [ Taint propagator ]
β β
βββββββββββββΊ [ Rule engine ] ββββββ 50+ rules
β
βββββββββββββββ΄ββββββββββββββ
βΌ βΌ
[ Error formatter ] [ Auto-fixer ]
β β
βΌ βΌ
SARIF / pretty text in-place YAML rewrite
- AST parser β
pkg/ast,pkg/core/parse_*.go - Expression parser β
pkg/expressions(full GitHub Actions${{ }}grammar) - Shell parser & taint β
pkg/shell(scope-aware bash semantics, function-arg propagation) - Rule engine β
pkg/core/*rule.goimplementing the visitor pattern - Auto-fixer β
pkg/core/autofixer.go
sisakulint was showcased at BlackHat Asia 2025 Arsenal. The talk covers the SAST design, the semantic-analysis approach to GitHub Actions security, the auto-fix pipeline, and real-world OWASP CI/CD Top-10 case studies. Originally built as a SecHack365 2023 project under NICT.
Bug reports, rule proposals, and PRs are very welcome.
- File issues at https://github.com/sisaku-security/sisakulint/issues
- Adding a new rule: see
docs/RULES_GUIDE.mdand the example workflows underscript/actions/ - Run the test suite:
go test ./... - Run against the bundled examples:
sisakulint script/actions/
If sisakulint helps you keep a workflow safe, please βοΈ the repo β it makes it much easier for other security teams to find.
Apache License 2.0 Β© sisaku-security contributors.
If you reference sisakulint in academic or industry work:
@software{sisakulint,
title = {sisakulint: CI-friendly static linter with SAST semantic analysis for GitHub Actions},
author = {sisaku-security contributors},
year = {2025},
url = {https://github.com/sisaku-security/sisakulint}
}
