Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

IaC Security: Scan with tfsec, Checkov, Terrascan

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.

Git PushMerge RequesttfsecFast ValidateCheckovPolicy ScanTerrascanDeep AnalysisJUnit ReportMR Feedback
IaC security scanning pipeline integrating tfsec, Checkov, and Terrascan for layered defense

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.

CriteriatfsecCheckovTerrascan
Primary FocusTerraform HCL native analysisMulti-IaC policy enforcementCloud-native risk & compliance
Supported FrameworksTerraform, CloudFormation, BicepTerraform, CFN, K8s, Docker, ARM, ServerlessTerraform, 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 LanguageRego (OPA) or YAMLPython or YAMLRego (OPA)
Best ForDeveloper-first fast feedbackCompliance-heavy enterprisesVisual risk assessment & drift
SARIF OutputYesYesLimited

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.

Infrastructure Codemain.tf / deployment.yamlScanner EngineParse + AST BuildVariable ResolutionPolicy EvaluationBuilt-in Rules+ Custom PoliciesResults OutputPASS / FAILSeverity + Line RefCustom Policy Sources./policies/*.py./rego/**/*.regoRemote Policy Bundles
Custom policy evaluation architecture for IaC security scanning with built-in and organizational rules

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 HIGH to 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.

  1. Pre-commit hooks: Install pre-commit framework 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.
  2. 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.
  3. Makefile targets: Create make security-scan that runs all three tools with identical flags to CI. New developers can validate their work locally before pushing. Include this target in onboarding documentation.
  4. Git aliases: Define git sec as 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.

IDE EditingReal-time LintingInline WarningsPre-commitLocal ValidationBlocks Bad CommitsCI PipelineEnforced GateJUnit ReportsDeploySafe InfraShared Configuration Source.tfsec/config.json.checkov.yaml.pre-commit-config.yamlCustom Policies/
Layered developer feedback loop ensuring consistent IaC security from IDE through production deployment

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.

Frequently Asked Questions

Tfsec focuses on AWS/Azure/GCP security misconfigurations with fast Go-based scanning. Checkov covers broader policy-as-code including Kubernetes and Docker with 1000+ built-in checks. Terrascan emphasizes compliance frameworks like CIS and NIST alongside cloud security. In my infrastructure work, I often combine tfsec for quick cloud feedback with Checkov for comprehensive policy enforcement across mixed environments.

Install tfsec via brew install tfsec or go install github.com/aquasecurity/tfsec/cmd/tfsec@latest. Install Checkov with pip3 install checkov or brew install checkov. Install Terrascan via brew install terrascan or curl from GitHub releases. All three run as standalone binaries without external dependencies. On Ubuntu servers I manage, I typically install them in /usr/local/bin and verify versions immediately after installation to avoid PATH conflicts during CI runs.

Yes, all three are open-source and free for commercial use. Tfsec uses MIT license, Checkov uses Apache 2.0, and Terrascan uses Apache 2.0. Enterprise editions exist with SaaS dashboards and custom policy builders, but core scanning engines remain fully functional without payment. For Nepal-based agencies budgeting in NPR, this eliminates tool licensing costs entirely, keeping security validation accessible even for smaller client projects.

Checkov typically finds the most issues due to its 1000+ built-in policies covering cloud, containers, and secrets. Tfsec excels at cloud-specific misconfigurations with fewer false positives. Terrascan catches compliance gaps others miss. On a recent Laravel project with AWS infrastructure, Checkov flagged 47 issues while tfsec found 28 overlapping ones. Run all three; they complement rather than replace each other in production pipelines.

Yes, all three provide official Docker images for GitLab CI. Add a scan stage running aquasec/tfsec, bridgecrew/checkov, or tenable/terrascan against your Terraform directory. Configure allow_failure: true initially to baseline findings without blocking merges. On sister sites sharing Deployer 7 pipelines, I added these scans as non-blocking jobs first, then enforced failures only after teams resolved critical alerts over two sprint cycles.

Add inline comments like #tfsec:ignore:aws-s3-no-public-access directly above the offending resource line. Alternatively, create a .tfsec/config.json file with exclude rules for specific check IDs. Document every suppression with justification. In practice, I maintain a SUPPRESSIONS.md file tracking why each ignore exists. This prevents audit confusion later when team members question why certain warnings were deliberately bypassed during security reviews.

Yes, run checkov -f plan.json --framework terraform_plan to scan compiled plans instead of raw HCL. This catches interpolated values and module outputs that static analysis misses. Generate the plan with terraform plan -out=plan.tfplan && terraform show -json plan.tfplan > plan.json. Scanning plans reduced false positives by roughly 40% on a legal-tech portal where dynamic AMI IDs triggered spurious encryption warnings during raw code scans.

Terrascan includes CIS AWS/Azure/GCP benchmarks, NIST 800-53, HIPAA, PCI-DSS, GDPR, and SOC2. Specify frameworks via --policy-type cis_aws_v1.4.0 or similar flags. Custom Rego policies extend coverage for organization-specific requirements. For Nepal-based clients handling sensitive legal data, I prioritize CIS and PCI-DSS profiles since they align closely with international standards expected by cross-border partners and payment processors like Stripe or eSewa integrations.

Use SARIF or JSON output formats for machine parsing. Pipe results to tools like jq for filtering or upload to GitHub Security tab via sarif-upload action. Set severity thresholds to fail only on HIGH/CRITICAL initially. On monorepos with 200+ modules, I split scans by directory and aggregate reports. This prevents 10-minute pipeline timeouts and lets teams own security debt incrementally rather than facing overwhelming backlogs during first adoption.

No, all three scan only IaC definitions, not live cloud state. Use Prowler or CloudSploit for runtime drift detection against actual AWS resources. However, importing existing infra into Terraform first then scanning catches configuration gaps before state management begins. On legacy migrations, I always run tfsec against imported .tf files before applying changes. This validates the imported state matches security baselines before introducing new infrastructure modifications.

Update monthly or when major cloud provider features launch. Pin versions in CI to avoid surprise breaking changes, then test upgrades in a staging branch. New AWS services often lack coverage for 2-4 weeks post-release. On production systems, I schedule quarterly dependency audits including scanner updates. This balances catching new vulnerability patterns against pipeline stability, especially important when multiple teams share CI runners across different project types.

Yes, all three flag obvious patterns like passwords, API keys, and tokens in plain text. Checkov has dedicated CKV_SECRET checks; tfsec includes generic-secret-detection. However, none match specialized tools like TruffleHog or git-secrets for historical leaks. Always pair IaC scanners with secret-specific tools. On client projects, I enforce pre-commit hooks running both Checkov and TruffleHog to catch secrets before they ever reach version control or trigger downstream scanner noise.

Tfsec completes typical modules in 5-15 seconds. Checkov takes 30-90 seconds depending on policy count. Terrascan ranges 20-60 seconds with compliance frameworks enabled. Parallelize scans across separate CI jobs to avoid serial bottlenecks. Cache binary downloads between runs. On shared EC2 runners hosting multiple Nepal client sites, I observed total pipeline time increase by under 2 minutes when adding all three scanners, acceptable tradeoff for catching misconfigurations before deployment.

Create Python classes extending BaseResourceCheck or YAML files using Checkov's declarative syntax. Place custom policies in a dedicated directory and pass via --external-checks-dir. Test locally with checkov -d . --external-checks-dir ./custom before committing. Document policy intent and expected remediation. For Nepal legal-tech portals requiring specific encryption standards beyond default CIS rules, I wrote three custom YAML checks validating KMS key rotation periods matched IRD compliance expectations.

No, start with monitoring-only mode for 2-4 weeks to establish baselines and reduce alert fatigue. Track metrics weekly, fix critical/high issues systematically, then enable blocking once team confidence grows. Premature enforcement causes developers to disable scanners entirely. On a WooCommerce migration project, we ran scanners passively for three sprints, resolved 60% of findings voluntarily, then enabled hard gates. Adoption succeeded because engineers trusted the tooling wasn't arbitrarily punishing legitimate patterns.

Share this article

Quick Contact Options
Choose how you want to connect me: