Skip to content

sisaku-security/sisakulint

Repository files navigation

sisakulint logo

sisakulint

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.

Go Reference Go Report Card License Latest Release GitHub stars BlackHat Arsenal 2025


Why sisakulint

  • 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 on rewrites the YAML in place; -fix dry-run previews 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).

How it compares

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.


Table of contents


Quick start

# macOS
brew tap sisaku-security/homebrew-sisakulint
brew install sisakulint

# Run in any repo with a .github/workflows/ directory
sisakulint

Other 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 rule

Exit codes: 0 = clean, 1 = findings, 2 = bad CLI args, 3 = fatal error.


Installation

Homebrew (macOS / Linuxbrew)

brew tap sisaku-security/homebrew-sisakulint
brew install sisakulint

go install

go install github.com/sisaku-security/sisakulint/cmd/sisakulint@latest

Requires Go 1.25 or newer.

Pre-built binary (Linux / Windows)

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/

Build from source

git clone https://github.com/sisaku-security/sisakulint.git
cd sisakulint
go build ./cmd/sisakulint

As a GitHub Action

The 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 Scanning

Useful 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=nofilter

What it detects

OWASP Top 10 CI/CD Security Risks coverage

OWASP Risk Description sisakulint Rules
CICD-SEC-01 Insufficient Flow Control Mechanisms improper-access-control, bot-conditions, unsound-contains, ai-action-unrestricted-trigger
CICD-SEC-02 Inadequate Identity and Access Management permissions
CICD-SEC-03 Dependency Chain Abuse known-vulnerable-actions, archived-uses, impostor-commit, ref-confusion, reusable-workflow-taint
CICD-SEC-04 Poisoned Pipeline Execution (PPE) dangerous-triggers-*, code-injection-*, envvar-injection-*, envpath-injection-*, output-clobbering-*, argument-injection-*, untrusted-checkout-*, request-forgery-*, ai-action-prompt-injection
CICD-SEC-05 Insufficient PBAC self-hosted-runners, ai-action-excessive-tools, ai-action-unsafe-sandbox, ai-action-execution-order
CICD-SEC-06 Insufficient Credential Hygiene credentials, artipacked, secrets-in-artifacts, secret-exfiltration, secret-exposure, unmasked-secret-exposure, secrets-inherit, secret-in-log
CICD-SEC-07 Insecure System Configuration timeout-minutes, deprecated-commands, cache-bloat
CICD-SEC-08 Ungoverned Usage of 3rd Party Services action-list, commit-sha, unpinned-images, dependabot-github-actions
CICD-SEC-09 Improper Artifact Integrity Validation artifact-poisoning-*, cache-poisoning-*
CICD-SEC-10 Insufficient Logging and Visibility obfuscation

Full documentation: https://sisaku-security.github.io/lint/


Rule reference

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

Example: detecting real vulnerabilities

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 build

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


Auto-fix

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 files

Auto-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@v3

After:

- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
- uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v3
untrusted-checkout β€” add explicit ref in privileged contexts

Before:

on:
  pull_request_target:
    types: [opened, synchronize]
jobs:
  build:
    steps:
      - uses: actions/checkout@v4

After:

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-sha calls 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.

SARIF + reviewdog integration

sisakulint -format "{{sarif .}}"                  # stdout
sisakulint -format "{{sarif .}}" > results.sarif  # to file
sisakulint -format "{{sarif .}}" | reviewdog -f=sarif -reporter=github-pr-review
reviewdog showing sisakulint findings inline on a GitHub PR

sisakulint findings rendered inline on a GitHub PR via reviewdog

A complete GitHub Actions recipe is in Installation β†’ As a GitHub Action.


Configuration

Generate a starter config:

sisakulint -init   # writes .github/action.yaml

The 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 decisions

JSON schema for editor autocompletion

Add 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-sha rule 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 export secrets.GITHUB_TOKEN to run: steps automatically)
    • GH_TOKEN (set by gh auth login)
    • -github-token "$(gh auth token)" (CLI flag, highest priority)

    A fine-grained PAT with public_repo (or just Metadata: Read-only on 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, and deprecated-commands require manual fixes as they depend on your specific use case

  • Auto-fix capabilities: Currently, timeout-minutes, commit-sha, credentials, untrusted-checkout, and artifact-poisoning rules support auto-fix. More rules will support auto-fix in future releases

Architecture

sisakulint architecture diagram
.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.go implementing the visitor pattern
  • Auto-fixer β€” pkg/core/autofixer.go

BlackHat Arsenal 2025

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.


Contributing

Bug reports, rule proposals, and PRs are very welcome.

If sisakulint helps you keep a workflow safe, please ⭐️ the repo β€” it makes it much easier for other security teams to find.


License

Apache License 2.0 Β© sisaku-security contributors.

Citation

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}
}

About

CI-Friendly static linter with autofix, SAST, semantic analysis for GitHub Actions

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages