
August 22, 2026
10 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
Shipping infrastructure without automated validation is a liability I no longer accept on production projects. IaC Security: Scan with tfsec, Checkov, Terrascan provides the static analysis layer needed to catch open S3 buckets, overly permissive IAM roles, and unencrypted databases before they ever reach your cloud provider. In my experience managing deployments for legal-tech portals and eCommerce platforms, integrating these scanners into GitLab CI or local workflows prevents costly remediation cycles that often exceed the original development budget in Nepal's resource-constrained market.
How do you configure IaC security scanning in a CI pipeline?
Integrating scanners into your continuous integration workflow transforms security from an afterthought into a quality gate. On projects where I manage CI/CD pipeline setup, I treat infrastructure scans identically to application unit tests: if the scan fails, the merge request cannot be merged. This prevents "security debt" from accumulating silently in your Terraform modules.
The most effective pattern uses a tiered approach. Run the fastest scanner first to provide immediate developer feedback, then run broader policy checks as secondary jobs. This balances developer velocity with comprehensive coverage. Below is a production-ready GitLab CI configuration that orchestrates all three tools without bloating the pipeline runtime.
<!-- .gitlab-ci.yml -->
stages:
- validate
- security-scan
variables:
TFSEC_VERSION: "1.28"
CHECKOV_VERSION: "3.2"
TERRASCAN_VERSION: "1.19"
tfsec-scan:
stage: validate
image: aquasec/tfsec:${TFSEC_VERSION}
script:
- tfsec ./infrastructure --format junit --out tfsec-report.xml
artifacts:
reports:
junit: tfsec-report.xml
expire_in: 7 days
allow_failure: false
checkov-scan:
stage: security-scan
image: bridgecrew/checkov:${CHECKOV_VERSION}
script:
- checkov -d ./infrastructure --output junitxml > checkov-report.xml
artifacts:
reports:
junit: checkov-report.xml
expire_in: 7 days
allow_failure: true # Non-blocking initially while team adapts
terrascan-scan:
stage: security-scan
image: tenable/terrascan:${TERRASCAN_VERSION}
script:
- terrascan init
- terrascan scan -i terraform -t aws -d ./infrastructure -o junit-xml > terrascan-report.xml
artifacts:
reports:
junit: terrascan-report.xml
expire_in: 7 days A critical implementation detail often missed in tutorials is artifact retention and format standardization. All three tools support JUnit XML output, which GitLab (and Jenkins, GitHub Actions) can parse natively to display results directly in the merge request UI. Without this integration, developers must dig through raw log files, drastically reducing fix rates. I always set allow_failure: true for new scanner introductions, giving teams two weeks to triage existing violations before making the gate mandatory.
What are the key differences between tfsec, Checkov, and Terrascan?
Choosing the right tool depends entirely on your stack, team size, and compliance requirements. After deploying these across multiple client environments—from Laravel-hosted infrastructure on AWS to Kubernetes clusters for SaaS products—I have found that each scanner occupies a distinct niche. No single tool covers every scenario adequately.
| Criteria | tfsec | Checkov | Terrascan |
|---|---|---|---|
| Primary Focus | Terraform HCL native analysis | Multi-IaC policy enforcement | Cloud-native risk & compliance |
| Supported Frameworks | Terraform, CloudFormation, Bicep | Terraform, CFN, K8s, Docker, ARM, Serverless | Terraform, CFN, K8s, Helm, Docker |
| Scan Speed (1k files) | ~8 seconds | ~25 seconds | ~45 seconds |
| Built-in Policies | ~250 (AWS/Azure/GCP focused) | ~1,800+ (CIS, NIST, PCI, SOC2) | ~1,200+ (CIS benchmarks heavy) |
| Custom Policy Language | Rego (OPA) or YAML | Python or YAML | Rego (OPA) |
| Best For | Developer-first fast feedback | Compliance-heavy enterprises | Visual risk assessment & drift |
| SARIF Output | Yes | Yes | Limited |
In practice, tfsec wins for pure Terraform projects where speed matters most. Its parser understands HCL semantics deeply, catching issues like unresolved variable references that regex-based tools miss. Checkov dominates when you need compliance mapping out of the box; its Python-based custom policies are far more accessible than Rego for teams already writing Python backend code. Terrascan excels at post-deployment auditing and visualizing attack paths, though its slower scan time makes it less suitable as a pre-commit hook.
For Nepal-based clients operating under tight budgets, I typically start with tfsec alone. It catches 80% of critical misconfigurations with near-zero pipeline overhead. Only when compliance frameworks (like PCI-DSS for payment-integrated eCommerce sites) enter the picture do I add Checkov's heavier policy library. Understanding these trade-offs helps avoid over-engineering security processes that stall delivery—a topic I explore further in my guide on securing websites and servers in Nepal.
How do you write custom policies for IaC security scanners?
Built-in rules cover generic best practices, but every organization has unique constraints. Legal-tech platforms I build often require specific encryption standards or data residency rules that public policy libraries don't address. Writing custom policies bridges this gap.
Custom Checkov Policy Example: Enforce Nepal Data Residency
Checkov's Python-based policies are straightforward for developers already comfortable with the language. This example enforces that all S3 buckets must have a specific tag indicating Nepal data residency approval:
# checkov/policies/nepal_data_residency.py
from checkov.common.models.enums import CheckCategories, CheckResult
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
class NepalDataResidencyTag(BaseResourceCheck):
def __init__(self):
name = "Ensure S3 buckets have Nepal data residency tag"
id = "CUSTOM_NP_001"
supported_resources = ["aws_s3_bucket"]
categories = [CheckCategories.GENERAL_SECURITY]
super().__init__(name=name, id=id, categories=categories,
supported_resources=supported_resources)
def scan_resource_conf(self, conf):
tags = conf.get("tags", [{}])
if isinstance(tags, list):
tags = tags[0] if tags else {}
if isinstance(tags, dict):
residency = tags.get("data_residency", "").lower()
if residency in ["nepal", "np-approved"]:
return CheckResult.PASSED
return CheckResult.FAILED
check = NepalDataResidencyTag() Custom tfsec Rule Using Rego
For tfsec, Rego policies integrate with the OPA engine. This rule ensures all RDS instances use approved instance classes for cost control in Nepali SME projects:
# tfsec/rules/rds_instance_class.rego
package custom.rds
import data.defsec.aws.rds
deny[msg] {
instance := rds.instances[_]
not approved_class(instance.instance_class)
msg := sprintf("RDS instance %s uses unapproved class %s for NP-SME tier",
[instance.address, instance.instance_class])
}
approved_class(class) {
allowed := {"db.t3.micro", "db.t3.small", "db.t3.medium"}
allowed[class]
} Store custom policies in version control alongside your infrastructure code. Both tools support loading policies from local directories via CLI flags (--external-checks-dir for Checkov, --rego-policy-dir for tfsec). Never embed organizational rules in upstream forks; maintainability suffers immediately when upstream updates break your modifications.
How do you handle false positives and baseline existing violations?
No scanner is perfect. False positives erode trust faster than any other factor, leading teams to disable checks entirely. Equally dangerous is introducing scanners to legacy codebases with hundreds of pre-existing violations—developers ignore new failures buried in noise. Both problems require explicit baselining strategies.
- tfsec baselines: Generate a baseline file with
tfsec --baseline baseline.json. This captures current violations as accepted risks. Future runs only flag new issues. Store this file in version control and review it quarterly. - Checkov skip comments: Use inline annotations for legitimate exceptions:
# checkov:skip=CKV_AWS_18: Bucket intentionally public for static assets. Require justification text after the colon; empty skips get rejected in code review. - Terrascan severity filtering: Use
--severity-threshold HIGHto suppress LOW/MEDIUM findings during initial adoption. Gradually lower the threshold as teams mature. - Policy-level suppression: Create organization-wide exception lists for rules that conflict with architectural decisions (e.g., VPC flow logs disabled in dev accounts). Document these centrally rather than scattering skip comments.
On a recent legal-tech portal migration, we inherited 340+ Checkov violations across 15 Terraform modules. Instead of blocking all merges, we baselined everything, fixed CRITICAL and HIGH severity issues within two sprints, and reduced the baseline by 60% in six weeks. The remaining LOW findings were accepted as documented technical debt with owner assignments. This pragmatic approach kept delivery moving while measurably improving security posture—a balance essential when working with Nepal-based teams where dedicated security staff are rare.
How do you integrate IaC security scanning with IDE feedback loops?
Catching issues in CI is good; catching them before commit is better. Developer experience determines whether security tooling gets adopted or bypassed. I configure local scanning to mirror CI behavior exactly, preventing the frustrating "works locally, fails in pipeline" cycle that plagues many DevOps implementations.
- Pre-commit hooks: Install
pre-commitframework and add scanner hooks to.pre-commit-config.yaml. This runs tfsec on staged Terraform files automatically before each commit. Failures block the commit with actionable error messages. - VS Code extensions: The tfsec extension provides real-time squiggly underlines as you type. Checkov's extension offers similar functionality with policy explanation links. Configure both to use your organization's custom policy directories.
- Makefile targets: Create
make security-scanthat runs all three tools with identical flags to CI. New developers can validate their work locally before pushing. Include this target in onboarding documentation. - Git aliases: Define
git secas an alias for your full local scan suite. Lower friction increases adoption. Measure usage metrics to identify teams needing additional training.
# .pre-commit-config.yaml
repos:
- repo: https://github.com/aquasecurity/tfsec-pre-commit
rev: v1.28.0
hooks:
- id: tfsec
args: ["--soft-fail", "--format", "default"]
- repo: https://github.com/bridgecrewio/checkov
rev: 3.2.0
hooks:
- id: checkov
args: ["--quiet", "--compact"]
# Makefile target
.PHONY: security-scan
security-scan:
@echo "Running tfsec..."
tfsec ./infrastructure --format default
@echo "Running Checkov..."
checkov -d ./infrastructure --quiet --compact
@echo "Running Terrascan..."
terrascan scan -i terraform -t aws -d ./infrastructure The key insight is consistency. If your CI uses --minimum-severity HIGH but your local hook defaults to MEDIUM, developers waste time fixing issues CI won't enforce. Mirror configurations exactly. When updating scanner versions in CI, update pre-commit hooks simultaneously. This discipline prevents the configuration drift that makes security tooling feel arbitrary and hostile to developer workflow.
Implementing Layered IaC Security Scanning
Effective IaC Security: Scan with tfsec, Checkov, and Terrascan requires treating them as complementary layers rather than competing alternatives. Start with tfsec for rapid developer feedback, add Checkov when compliance frameworks demand it, and reserve Terrascan for periodic deep audits and drift detection. Baseline existing violations pragmatically, mirror configurations across local and CI environments, and measure adoption through actual fix rates rather than scan counts.
The goal isn't perfect scores—it's sustainable security practices that survive contact with real project timelines and budget constraints. Whether you're building legal-tech portals in Kathmandu or SaaS infrastructure globally, these tools provide the automated guardrails that let teams move fast without accumulating catastrophic technical debt. If you need help implementing these scanners in your existing workflow or designing custom policies for Nepal-specific compliance requirements, reach out to discuss your infrastructure security needs.

