StackGuard docs Back to site Playground
GitHub
Getting started

Installation

StackGuard is a single static binary. Install it with a package manager, download a release, or build it with go install.

Package managers

# macOS and Linux (Homebrew)
brew install nexutils/tap/stackguard

# Windows (Scoop)
scoop install stackguard

Go install

With a Go 1.24+ toolchain, install straight from source:

$ go install github.com/nexutils/StackGuard/cmd/stackguard@latest

Prebuilt binaries

Download the archive for your platform from the releases page. Builds exist for Linux, macOS and Windows on both amd64 and arm64.

VERSION=1.0.0
curl -sSLO https://github.com/nexutils/StackGuard/releases/download/v${VERSION}/stackguard_${VERSION}_linux_amd64.tar.gz
tar -xzf stackguard_${VERSION}_linux_amd64.tar.gz
sudo mv stackguard /usr/local/bin/

Verify the download. Every release includes checksums.txt. Run sha256sum -c checksums.txt --ignore-missing before installing.

Check the install

$ stackguard version

Quick start

Point StackGuard at a directory. It discovers every file it understands, scores the result, and tells you what to fix.

Scan a directory

$ stackguard scan .

You can also pass individual files and folders:

$ stackguard scan docker-compose.yml k8s/ terraform/

Read the output

Each finding carries a severity, the rule ID, the exact location, and a message. The run ends with a production-readiness score.

$ stackguard scan .

ERROR    TF_SEC_001   prod/s3.tf:12    bucket "assets" grants a public ACL (public-read)
WARNING  K8S_OPS_001  k8s/api.yaml:23  Deployment "api" has no resource requests or limits

Score 87/100   1 error, 1 warning

Just the score

$ stackguard score .
Score 87/100
  1 error, 1 warning, 0 info

Next

When the output looks right, wire it into CI so a regression fails the build. See CI integration.

Supported targets

A target is a technology StackGuard knows how to parse. One scan can cover your whole repository across all of them.

The nine targets

TargetDetects
composeDocker Compose services, networks and volumes
dockerfileDockerfile instructions and build stages
kubernetesKubernetes manifests (the pod spec wherever it lives)
helmHelm charts and rendered templates
terraformTerraform resources, data sources and modules
githubGitHub Actions workflows and steps
jenkinsJenkins pipeline files
ansibleAnsible plays and tasks
nginxnginx server and location configuration

How detection works

Many targets share the .yaml extension, so the registry asks parsers in priority order: path-based first (GitHub workflows, Helm charts), then name-based, then content sniffing. Kubernetes looks for apiVersion and kind; Ansible looks for plays. Only the first 4 KiB of a file is read for detection.

Scanning a subset

Restrict a run to specific targets with --target, or set targets in the config file.

$ stackguard scan . --target kubernetes,terraform

Scanning & output

The scan command prints to the console by default. Switch formats for pipelines, dashboards or code scanning.

Output formats

FormatUse it for
consoleReading results in a terminal (the default)
jsonMachine processing, custom tooling, metrics
sarifGitHub code scanning and other SARIF consumers
htmlA shareable, self-contained report page
$ stackguard scan . --format sarif --output stackguard.sarif

Scan profiles

A profile sets how strict a scan is by filtering which rules run. Choose one with --profile (the default is standard).

ProfileRuns
quickErrors only, and only security and reliability rules
standardWarnings and above, all categories (the default)
strictEverything from info up, all categories, and off-by-default rules too
$ stackguard scan . --profile strict

Console and file options

  • --output writes the report to a file instead of stdout.
  • --quiet prints only findings and the score.
  • --show-passed lists every passed rule instead of a summary.
  • --omit-skipped drops skipped rules from json and sarif output (stats are unchanged).

Failing the scan

--fail-on sets the lowest severity that makes the scan exit with code 1. The default is error; use none to never fail on severity.

$ stackguard scan . --fail-on warning

See Exit codes & scoring for the full behavior.

CI integration

StackGuard is a single binary with meaningful exit codes, so it drops into any pipeline. Nothing to host and no service to run.

GitHub Actions

Scan, gate the build, and upload SARIF so findings appear in the pull request.

# .github/workflows/stackguard.yml
- name: StackGuard
  run: >
    stackguard scan .
    --fail-on error --min-score 80
    --format sarif --output stackguard.sarif

- uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: stackguard.sarif

Choosing a gate

  • Severity gate: --fail-on error fails only on errors. Raise it to warning as the codebase improves.
  • Score gate: --min-score 80 fails when the production-readiness score drops below the threshold.
  • Use either or both. The first one to trip fails the build.

Other CI systems work the same way. Run the binary, check the exit code. SARIF and JSON output make findings portable to any dashboard.

Configuration

StackGuard reads .stackguard.yaml from the current directory upward, stopping at the repository root. Flags and environment variables override it.

The config file

# Rule IDs that never run.
disabled_rules:
  - COMP_SEC_003

# Off-by-default rule IDs to turn on.
enabled_rules:
  - COMP_PROD_005

# Replace the severity of specific rules.
severity_overrides:
  K8S_REL_003: WARNING

# Glob patterns to skip, relative to each scanned directory.
exclude:
  - "**/testdata/**"
  - "*.generated.yaml"

# Only scan these targets.
targets: [compose, kubernetes, terraform]

# Extra directories of rule files, relative to this file.
rules_dirs:
  - ./policies

# Scan profile: quick, standard or strict.
profile: standard

# Severity that fails the scan (or none), and the score gate.
fail_on: ERROR
min_score: 80
format: console

Unknown keys are an error, so a typo like sevrity: fails loudly instead of being silently ignored. min_score must be between 0 and 100, and fail_on also accepts none or never to disable the severity gate.

Precedence

From lowest to highest: built-in defaults, the config file, environment variables, then command-line flags. Environment variables use the STACKGUARD_ prefix, for example STACKGUARD_MIN_SCORE, STACKGUARD_PROFILE or STACKGUARD_FAIL_ON. --disable and --exclude are added to the configured lists rather than replacing them.

Ignoring files

A .stackguardignore file holds one glob per line. These directories are always skipped: .git, .hg, .svn, node_modules, vendor, .terraform, .venv, venv, __pycache__, .idea and .stackguard. Files larger than 5 MiB are skipped too.

Writing custom rules

A rule is a YAML file. It names the resources it applies to and describes the bad condition as a query. No Go code is needed.

Anatomy of a rule

id: ACME_SEC_001              # TARGET_CATEGORY_NNN
title: Service runs as root
severity: ERROR                # ERROR | WARNING | INFO
category: security            # security | reliability | operational | maintainability | performance
target: compose
message: "Service '{{ .Resource.Name }}' runs as root"
remediation: |
  Set a non-root `user:` in the service definition.
query:
  not:
    path: user
    exists: true

Paths

Paths are evaluated against a resource's properties.

SyntaxMeaning
a.b.cnested keys
ports.0.targetlist index
ports.*.targetevery element of a list or map
services.*.**every scalar below the node
depends_on.{dep}capture a map key into a variable

Operators and combinators

Leaf conditions use operators such as equals, exists, contains, in, matches (RE2), and numeric comparisons like gte. Combine them with all, any and not, or use where to require several conditions on the same element.

Validate your rules

$ stackguard rules --validate --rules-dir ./policies
$ stackguard explain ACME_SEC_001 --rules-dir ./policies

Suppressing findings

When a finding is reviewed and accepted, silence it in place with a comment instead of disabling the rule everywhere.

Inline suppressions

A comment containing stackguard:ignore suppresses findings on its own line and the line directly below it.

# stackguard:ignore COMP_SEC_001,COMP_SEC_003 -- reviewed in SEC-142
privileged: true

With no rule IDs listed, it suppresses every rule on that line. The number of suppressed findings is reported in stats.suppressed in the JSON output.

Disabling a rule entirely

To turn a rule off across the whole repository, add its ID to disabled_rules in the config file or pass --disable. See Configuration.

CLI reference

Everything runs through stackguard and five sub-commands.

scan

Scan files or directories for issues. Usage: stackguard scan [path...]

FlagDescription
-p, --profilescan profile: quick, standard or strict (default standard)
-f, --formatconsole, json, sarif or html (default console)
--fail-onseverity that fails the scan: error, warning, info or none
--min-scoreexit 1 if the score falls below this value
-o, --outputwrite the report to a file
--targetonly scan these targets
--disable / --enableturn rules off, or switch on off-by-default rules
--excludeglob patterns to skip
--rules-dirextra directory of rule files (repeatable)
-j, --jobsparallel parsers (default: number of CPUs)
-q, --quietprint only findings and the score
--show-passedconsole: list every passed rule instead of a summary
--omit-skippedjson/sarif: drop rules with status skipped

score

Print the production-readiness score only. Usage: stackguard score [path...]. Flags: -f, --format (console or json), --min-score, -p, --profile, plus the shared filters --target, --exclude, --rules-dir and -j, --jobs.

rules

List the available rules, or validate a rule directory. Usage: stackguard rules. Flags: --target, --category, --severity, -f, --format (table or json), --rules-dir and --validate.

explain

Show the details, reason and remediation for one rule. Usage: stackguard explain RULE_ID. Flags: -f, --format (text or json) and --rules-dir. It also shows which file the rule loaded from.

version

Print the StackGuard version, commit, build date and platform. Usage: stackguard version. Flag: -f, --format (text or json).

Global flags

Available on every command: --config, --log-level, --log-format, -v, --verbose and --no-color. The NO_COLOR environment variable is also honored.

Rules

Rules ship embedded in the binary and merge with any local layers you add. Each one has a stable ID and a category.

Rule IDs

IDs follow TARGET_CATEGORY_NNN, for example TF_SEC_001 or K8S_OPS_001. The ID is stable, so you can reference it in suppressions and config.

Categories

CategoryCovers
securityExposure, privilege and access problems
reliabilityAvailability, data safety and resilience
operationalLimits, health checks and day-two operations
maintainabilityReproducibility and drift, such as unpinned images
performanceResource use and efficiency

Layered loading

Rules load in layers and later layers win on a shared ID: built-in rules, then $STACKGUARD_HOME/rules, then .stackguard/rules, then rules_dirs from config, then --rules-dir flags.

Off by default

Some rules assume an organisation-specific convention and ship off by default. Turn them on with enabled_rules or --enable. Run stackguard rules to see which are marked.

Exit codes & scoring

The score turns a list of findings into one number, and the exit code turns that into a pass or fail for CI.

The score

Every scan starts at 100 and loses points per finding, down to a floor of 0.

SeverityDeduction
ERROR-10 points
WARNING-3 points
INFO-1 point

So one error, two warnings and one info gives a score of 83.

Gating on the score

--min-score, or min_score in config, fails the run when the score is below the threshold. A value of 0 disables the check.

Exit codes

CodeMeaning
0Success. No finding reached the fail-on severity and the score passed.
1A finding reached the fail-on severity, or the score fell below min-score.
2Usage error (bad flags, arguments, configuration or rules).
3Internal error (an unexpected I/O or runtime failure).