Git, CI/CD & GitOpsXLIX · Infrastructure CIInfrastructure CI
Security scanning — tfsec, checkov, trivy, kics, snyk
What you'll learn
- Explain why security scanning is a dedicated pipeline stage rather than a lint rule
- Run tfsec, checkov, and trivy against a Terraform configuration and read their output
- Identify the categories of misconfiguration each scanner targets
- Recognise the limits of static security analysis and what runtime signals it cannot see
Prerequisites
Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x
The security stage is the third gate in the pipeline. It exists as a separate stage — rather than as a category of lint rule — because the rule set is large, the rule authors are security specialists rather than language-tool authors, and the consequences of a finding are categorically different from a lint warning. A missing resource tag is a cost- allocation problem; a public S3 bucket is a data-exposure problem. The two findings call for different urgency, different reviewers, and different escalation paths. Conflating them in the lint stage means both get the wrong response.
What security scanning catches
A security scanner reads the configuration, the rendered plan, or the container image and applies a rule set that encodes known-bad patterns. The rule set is maintained by security specialists and updated frequently as new vulnerabilities are disclosed. The categories of finding are:
- Encryption at rest. Storage resources without
encryption configuration:
aws_s3_bucket,aws_ebs_volume,azurerm_storage_account,kubernetes.io/pvc. - Encryption in transit. Resources that allow plaintext
protocols: HTTP load balancers, plaintext Redis, S3
buckets without
enforce_ssl. - Public exposure. Resources whose access policy permits
anonymous or wildcard access:
aws_s3_bucket_public_access_blockabsent, security groups with0.0.0.0/0ingress, Kubernetes services of typeLoadBalancerexposing sensitive ports. - Least privilege. IAM policies and Kubernetes RBAC
bindings with
*actions,*resources, or wildcard principals. - Logging and audit. Resources that should produce audit trails but do not have them configured: CloudTrail, VPC flow logs, Kubernetes audit policy.
tfsec .
checkov -d .
trivy fs .
These three commands cover the bulk of an infrastructure
repository. tfsec reads Terraform; checkov reads
Terraform, CloudFormation, Kubernetes manifests, Dockerfile,
and ARM templates; trivy fs . reads files for both
infrastructure misconfigurations and known-vulnerable
dependencies.
Tooling landscape
The security-stage toolset is fragmented because no single tool covers every language and every rule category with high signal-to-noise. The teams in this course typically combine two or three scanners and accept the overlap:
- tfsec — Terraform-only, focused on AWS rules, fast, low false-positive rate, open-source. The default scanner for Terraform-only repositories.
- checkov — Multi-language (Terraform, K8s, Dockerfile, ARM), larger rule set, more false positives, open-source with a paid cloud tier. The default scanner for multi-language repositories.
- trivy — Filesystem and image scanner. Reads Terraform, Kubernetes, Dockerfile, and lockfiles. Also scans container images and filesystems for known-vulnerable dependencies. The default scanner for image and dependency security.
- kics — Checkmarx’s scanner, multi-language, broader rule set than checkov in some categories, narrower in others. Often paired with checkov in larger teams.
- snyk — Commercial scanner with a free tier, strong on dependencies and container images, weaker on Terraform than tfsec or checkov in most rule categories.
The choice between these tools is rarely about finding coverage — every tool has rules the others lack — and more often about integration, licensing, and the rule categories the team cares about most.
Reading scanner output
Each tool produces output in its own format, but the canonical output is a list of findings, each with:
- A rule identifier. For tfsec, this is
AWS001or similar; for checkov,CKV_AWS_18; for trivy, a CVE orAVD-AWS-0001. - A severity. Critical, high, medium, low, or sometimes informational.
- A file path and line number.
- A description and remediation guidance.
The rule identifier is the hook for the rest of the
pipeline. A finding with ID CKV_AWS_18 is suppressible
with a # checkov:skip=CKV_AWS_18:reason comment, with
an entry in .checkov.yaml, or by setting the rule’s
severity below the pipeline’s failure threshold. The
suppression mechanism must be intentional: every
suppressed finding should have a recorded rationale and
a review date.
flowchart LR
A[Configuration] --> B[Scanner]
B --> C{Findings}
C -->|none| D[Pass]
C -->|suppressed| E[Pass with rationale]
C -->|failing| F[Fail build]
F --> G[Engineer reviews]
G --> H{Fix or suppress}
H -->|fix| A
H -->|suppress| I[Record rationale]
What security scanning cannot catch
The security stage is not a substitute for runtime signals. It cannot catch:
- Active intrusion. A scanner reading configuration cannot tell you whether someone has already exfiltrated the bucket contents. Runtime detection (CloudTrail anomaly detection, GuardDuty, Falco) handles this.
- Vulnerabilities that depend on runtime state. A
scanner cannot tell you whether a Kubernetes pod is
running as root unless the manifest explicitly says so.
A scanner that reads the manifest may see
runAsUser: 0and flag it; a scanner that reads the image’sUSERdirective may miss it; runtime observability confirms what is actually running. - Vulnerabilities that depend on data flow. A scanner cannot trace whether an S3 bucket receives sensitive data and whether that bucket is publicly readable. Data classification and data flow analysis are different disciplines.
Production discipline
Three rules govern the security stage:
- Findings are pipeline failures by default. A
scanner with
--severity HIGHor above failing the build is the baseline. Lowering severity to allow findings through is a deliberate decision recorded in the pipeline configuration. - Suppressions are documented. Every
# checkov:skipcomment, every.tfsec.yamlexclusion, every.trivyignoreline has a recorded rationale. The rationale is reviewed on a schedule — quarterly is common — and stale suppressions are removed. - Scanner versions are pinned. A scanner that silently updates its rule set is a scanner that can produce different results on the same source. Pin the version in the CI image.
Cross-course references
- Terraform for Production Sysadmins — Part XXV (QualityGates) covers tfsec and checkov in depth.
- Kubernetes for Production Sysadmins — Part XXXIII (Admission) covers OPA Gatekeeper and Kyverno, which enforce some of the same rules at admission time as scanners enforce at CI time.
- Containers for Production Sysadmins — Parts XII-XV cover image scanning with trivy and the runtime signals the security stage cannot replace.
Quiz
Knowledge check · 4 questions
Q1. A pipeline runs `tfsec` and `checkov -d .` against the same Terraform configuration. Both flag the same missing-encryption finding. Why run both?
Q2. A tfsec finding that an S3 bucket has no `public_access_block` means the bucket is publicly readable.
Q3. Give the canonical command for running tfsec against a Terraform configuration and the canonical command for running checkov against the same configuration.
Q4. Diagnose a security pipeline where a high-severity checkov finding is suppressed across thirty files by a blanket .checkov.yaml exclusion and identify the production-discipline failure.
A team added `checkov:skip-dir: terraform/legacy/` six months ago to unblock a migration. Six months later, every change in `terraform/legacy/` is exempted from security scanning. The team has lost visibility into the security posture of an entire module.
Passing score: 75%. Answers are checked in this browser.