Skip to content

Latest commit

 

History

History
683 lines (535 loc) · 34.2 KB

File metadata and controls

683 lines (535 loc) · 34.2 KB
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.


Remote pull request scans

Scan the exact PR head without cloning it yourself:

sisakulint -remote sisaku-security/sisakulint-agent -pr 18

# Service/CI callers should pin the webhook revision to prevent stale jobs from
# scanning a newer head. Repeat -remote-target to limit report/fix scope while
# every workflow in the snapshot remains available for cross-file analysis.
sisakulint \
  -remote sisaku-security/sisakulint-agent \
  -pr 18 \
  -expected-head-sha 0123456789abcdef0123456789abcdef01234567 \
  -remote-target .github/workflows/deploy.yml \
  -format "{{sarif .}}"

Pull request URLs are recognized as well (-remote https://github.com/owner/repo/pull/18). The command downloads an archive at the resolved immutable head SHA, safely materializes the complete repository, analyzes all workflows with local project context, and reports only workflows changed in the PR. Private repositories can authenticate with -github-token, SISAKULINT_GITHUB_TOKEN, GITHUB_TOKEN, GH_TOKEN, an existing gh auth session, or Git credentials. Remote scanning currently supports GitHub.com only; GitHub Enterprise Server and custom GitHub API base URLs are not supported.

-fix dry-run can preview remote fixes. Applying -fix on requires a new, explicit -remote-checkout-dir; sisakulint never writes directly to GitHub. PR-mode fixes are limited to workflows changed in the PR and, when provided, the -remote-target selection. Fixers which mutate repository files such as .github/dependabot.yaml remain enabled for local scans but are disabled for this workflow-only response contract.


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 parserpkg/ast, pkg/core/parse_*.go
  • Expression parserpkg/expressions (full GitHub Actions ${{ }} grammar)
  • Shell parser & taintpkg/shell (scope-aware bash semantics, function-arg propagation)
  • Rule enginepkg/core/*rule.go implementing the visitor pattern
  • Auto-fixerpkg/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}
}