/iac-scan
Scan changed infrastructure/deploy config for security misconfigurations and
exposed secrets. Static and read-only — analyzes the files directly.
Auto-run safety envelope: the auto-run path uses only local static linters
that need no network and no auth — hadolint, tfsec, checkov (offline/local
policy only), kube-score, actionlint, zizmor. That's why it's safe to
auto-run, unlike /pentest (an authenticated, networked scanner that probes live
targets). Anything that reaches the network — e.g. trivy pulling vulnerability
DBs, or checkov fetching remote policies — is opt-in / recommend-only, never
part of the auto-run. If only networked tools are available, run the signature
checks instead and recommend the networked scan separately.
Hard rules
- Evidence-grounded: file:line, the offending directive, the rule/CWE, and the
concrete risk ("container runs as root", "0.0.0.0/0 on port 22", "secret baked
into image layer").
- Severity by blast radius: internet-exposed, privilege-escalating, or
secret-leaking misconfigs are CRITICAL/HIGH; style/pinning nits are LOW.
- Smallest correct fix, shown as a diff; do not apply it.
- Scope to the changed infra files. Don't re-scan the whole tree.
- Prefer real scanners when present. Local static linters (
hadolint,
tfsec, kube-score, actionlint, zizmor, offline checkov) are part of
the auto-run path; networked scanners (trivy DB pulls, remote-policy checkov)
are recommend-only. Fall back to the signature checks below when none are
present — and always say which mode ran (which tools, or signature mode).
Trigger (what counts as infra/deploy config)
- Containers:
Dockerfile, *.dockerfile, docker-compose*.y*ml, .dockerignore
- Terraform:
*.tf, *.tfvars
- Kubernetes / Helm:
*.y*ml with kind: under k8s/, manifests/, charts/,
helm/, or deploy/
- CI/CD:
.github/workflows/**, .gitlab-ci.yml, Jenkinsfile, *.circleci/*
- Web/proxy/cloud:
nginx.conf, Caddyfile, *.tf cloud resources, serverless.yml
If none of these changed, say so and stop.
What to check
Docker — non-root USER; pinned base image (no :latest); no secrets in
ENV/ARG/layers; no ADD http://…; minimal surface; HEALTHCHECK; no
unnecessary EXPOSE; --no-install-recommends/cleaned caches. (hadolint)
docker-compose — no privileged: true; no network_mode: host; never bind
/var/run/docker.sock; secrets via secrets: not environment:; pinned image tags.
Terraform — no security-group/firewall 0.0.0.0/0 on sensitive ports
(22/3389/DB); no public buckets/blobs; no IAM Action: "*" / Resource: "*";
encryption at rest + in transit enabled; no hardcoded secrets/keys; remote state
with locking; logging/versioning on. (tfsec / checkov)
Kubernetes / Helm — runAsNonRoot, drop privileged/allowPrivilegeEscalation;
no hostPath/hostNetwork/hostPID; resource limits/requests set;
readOnlyRootFilesystem; no default ServiceAccount with broad RBAC / cluster-admin;
pinned image digests; NetworkPolicy present; no secrets in plain env. (kube-score / kubesec)
CI/CD (GitHub Actions et al.) — no pull_request_target running untrusted PR
code; least-privilege top-level permissions:; third-party actions pinned to a
full commit SHA (not a moving tag); no script injection via unsanitized
${{ github.event.* }} in run:; secrets never echoed; no persist-credentials
left on when not needed. (actionlint + zizmor)
Web/proxy — TLS enforced; security headers (HSTS, CSP, X-Content-Type-Options);
no directory listing; no server tokens leaking versions.
Steps
- Scope — resolve changed infra files (or
$ARGUMENTS). None → stop.
- Tool pass — for each file type, if the matching scanner is installed, run it
scoped to the changed files and capture findings verbatim. Note which tools ran.
- Signature pass — for file types with no installed scanner, Grep/Read for the
patterns above. Mark these Observed (clear match) vs Suspected.
- Secrets sweep — across all changed infra files, flag high-entropy strings,
AKIA…, private keys, tokens, password=/secret= literals. (Complements
/defense's app-code secret scan.)
- Report — group by severity; smallest fix per finding as a diff.
Report format
# IaC scan — <scope> @ <date>
Scanners run: hadolint ✓ / tfsec ✗ (signature mode) / actionlint ✓ …
## CRITICAL / HIGH
- <file:line> — <directive> — <rule/CWE> — <risk> — <fix>
## MEDIUM / LOW
- …
## Secrets
- <file:line> — <kind> — rotate + move to a secret manager
Anti-patterns (do not do)
- Claiming a misconfig without quoting the directive and naming the risk.
- Scanning the whole repo instead of the changed infra files.
- Applying fixes or rewriting manifests — this scans and drafts; the user applies.
- Treating a pinning/style nit as CRITICAL, or burying an open-to-the-world port.
Related commands
/defense — app-code OWASP/secrets sweep; /iac-scan is its infra counterpart.
/pentest — deeper external scanner; recommend after /iac-scan finds HIGH/CRITICAL
or when a live environment needs probing.
/cso — threat-model the deployment topology at design time (/write-plan).
1---2name: iac-scan3description: Infrastructure-as-Code security scan for changed deploy config — Dockerfiles, docker-compose, Terraform, Kubernetes/Helm, and CI/CD workflows. Flags misconfigurations and exposed secrets (root containers, open ingress, wildcard IAM, privileged pods, untrusted CI triggers). Static diff-scoped analysis (safe to auto-run); uses tfsec/checkov/hadolint/actionlint when present. Use when asked to "scan infra", "IaC scan", "check the Dockerfile/terraform/k8s", "CI security", or after changing deploy/infra config.4---56# /iac-scan78Scan changed infrastructure/deploy config for security misconfigurations and9exposed secrets. **Static and read-only** — analyzes the files directly.1011**Auto-run safety envelope:** the auto-run path uses only **local static linters**12that need no network and no auth — `hadolint`, `tfsec`, `checkov` (offline/local13policy only), `kube-score`, `actionlint`, `zizmor`. That's why it's safe to14auto-run, unlike `/pentest` (an authenticated, networked scanner that probes live15targets). Anything that reaches the network — e.g. `trivy` pulling vulnerability16DBs, or `checkov` fetching remote policies — is **opt-in / recommend-only**, never17part of the auto-run. If only networked tools are available, run the signature18checks instead and recommend the networked scan separately.1920## Hard rules2122- **Evidence-grounded:** file:line, the offending directive, the rule/CWE, and the23 concrete risk ("container runs as root", "0.0.0.0/0 on port 22", "secret baked24 into image layer").25- **Severity by blast radius:** internet-exposed, privilege-escalating, or26 secret-leaking misconfigs are CRITICAL/HIGH; style/pinning nits are LOW.27- **Smallest correct fix**, shown as a diff; do not apply it.28- **Scope to the changed infra files.** Don't re-scan the whole tree.29- **Prefer real scanners when present.** Local static linters (`hadolint`,30 `tfsec`, `kube-score`, `actionlint`, `zizmor`, offline `checkov`) are part of31 the auto-run path; networked scanners (`trivy` DB pulls, remote-policy `checkov`)32 are recommend-only. Fall back to the signature checks below when none are33 present — and always say which mode ran (which tools, or signature mode).3435## Trigger (what counts as infra/deploy config)3637- Containers: `Dockerfile`, `*.dockerfile`, `docker-compose*.y*ml`, `.dockerignore`38- Terraform: `*.tf`, `*.tfvars`39- Kubernetes / Helm: `*.y*ml` with `kind:` under `k8s/`, `manifests/`, `charts/`,40 `helm/`, or `deploy/`41- CI/CD: `.github/workflows/**`, `.gitlab-ci.yml`, `Jenkinsfile`, `*.circleci/*`42- Web/proxy/cloud: `nginx.conf`, `Caddyfile`, `*.tf` cloud resources, `serverless.yml`4344If none of these changed, say so and stop.4546## What to check4748**Docker** — non-root `USER`; pinned base image (no `:latest`); no secrets in49`ENV`/`ARG`/layers; no `ADD http://…`; minimal surface; `HEALTHCHECK`; no50unnecessary `EXPOSE`; `--no-install-recommends`/cleaned caches. *(hadolint)*5152**docker-compose** — no `privileged: true`; no `network_mode: host`; never bind53`/var/run/docker.sock`; secrets via `secrets:` not `environment:`; pinned image tags.5455**Terraform** — no security-group/firewall `0.0.0.0/0` on sensitive ports56(22/3389/DB); no public buckets/blobs; no IAM `Action: "*"` / `Resource: "*"`;57encryption at rest + in transit enabled; no hardcoded secrets/keys; remote state58with locking; logging/versioning on. *(tfsec / checkov)*5960**Kubernetes / Helm** — `runAsNonRoot`, drop `privileged`/`allowPrivilegeEscalation`;61no `hostPath`/`hostNetwork`/`hostPID`; resource `limits`/`requests` set;62`readOnlyRootFilesystem`; no `default` ServiceAccount with broad RBAC / `cluster-admin`;63pinned image digests; NetworkPolicy present; no secrets in plain env. *(kube-score / kubesec)*6465**CI/CD (GitHub Actions et al.)** — no `pull_request_target` running untrusted PR66code; least-privilege top-level `permissions:`; third-party actions pinned to a67**full commit SHA** (not a moving tag); no script injection via unsanitized68`${{ github.event.* }}` in `run:`; secrets never `echo`ed; no `persist-credentials`69left on when not needed. *(actionlint + zizmor)*7071**Web/proxy** — TLS enforced; security headers (HSTS, CSP, X-Content-Type-Options);72no directory listing; no server tokens leaking versions.7374## Steps75761. **Scope** — resolve changed infra files (or `$ARGUMENTS`). None → stop.772. **Tool pass** — for each file type, if the matching scanner is installed, run it78 scoped to the changed files and capture findings verbatim. Note which tools ran.793. **Signature pass** — for file types with no installed scanner, Grep/Read for the80 patterns above. Mark these **Observed** (clear match) vs **Suspected**.814. **Secrets sweep** — across all changed infra files, flag high-entropy strings,82 `AKIA…`, private keys, tokens, `password=`/`secret=` literals. (Complements83 `/defense`'s app-code secret scan.)845. **Report** — group by severity; smallest fix per finding as a diff.8586## Report format8788```markdown89# IaC scan — <scope> @ <date>9091Scanners run: hadolint ✓ / tfsec ✗ (signature mode) / actionlint ✓ …9293## CRITICAL / HIGH94- <file:line> — <directive> — <rule/CWE> — <risk> — <fix>9596## MEDIUM / LOW97- …9899## Secrets100- <file:line> — <kind> — rotate + move to a secret manager101```102103## Anti-patterns (do not do)104105- Claiming a misconfig without quoting the directive and naming the risk.106- Scanning the whole repo instead of the changed infra files.107- Applying fixes or rewriting manifests — this scans and drafts; the user applies.108- Treating a pinning/style nit as CRITICAL, or burying an open-to-the-world port.109110## Related commands111112- `/defense` — app-code OWASP/secrets sweep; `/iac-scan` is its infra counterpart.113- `/pentest` — deeper external scanner; recommend after `/iac-scan` finds HIGH/CRITICAL114 or when a live environment needs probing.115- `/cso` — threat-model the deployment topology at design time (`/write-plan`).