TILLFORGE · IAC SCANNING

Catch the open bucket before it exists.

GA

Every pushed commit’s Terraform, CloudFormation and Kubernetes files are judged with the same checks cloud posture runs against live accounts: public buckets, ports and databases open to the internet, unencrypted storage, missing backups and logs, privileged containers and roles that grant everything. A misconfiguration is flagged in the commit that writes it, not after it ships. Files are read on TillDev’s own git servers and never go to a third-party scanner.

01When

What gets scanned, and when

Unless the workspace policy is off, every branch push, every green CI build and every release is scanned, and anyone with read access can start one by hand (30 an hour per workspace). When a commit’s infrastructure files are unchanged since an earlier scan, it is judged again without reading them. Each commit gets a tilldev/iac check, which a branch rule can require like any other.

FormatReadsJudges
Terraform.tf and .tf.json, one module per directoryAWS, Google Cloud, Azure, DigitalOcean and Cloudflare resources
CloudFormationYAML or JSON templates with a Resources sectionS3 buckets and their policies, security groups, RDS and EC2 instances, CloudTrail trails, IAM roles
KubernetesYAML manifests, several documents to a filePods and every workload kind, Services, Roles, ClusterRoles and their bindings

A YAML or JSON file is judged by what it holds, not its name, so other config is skipped. Vendored and build directories are not read. Up to 200 files a commit are read, each up to 1 MiB and 16 MiB in all; a scan past a limit is marked partial, so a clean result is never claimed for files that weren’t read. --files lists every file considered and why any was not read.

bash
# Scan the default branch head's infrastructure files.
tilldev forge iac acme-infra

# A branch or tag; wait for the result. Exits 1 when a finding blocks, 2 while not scanned.
tilldev forge iac acme-infra --ref feature/vpc --wait

# In CI: fail on anything high or worse, whatever the workspace policy says.
tilldev forge iac acme-infra --commit "$GIT_COMMIT" --fail-at high --wait

# Every file considered and why any was not read, and the checks only a deploy could answer.
tilldev forge iac acme-infra --files --unresolved

# Every active finding across the default branches you can read.
tilldev forge iac findings --level high --format terraform
02Honesty

Answered from the file, or not at all

A check is judged only when the file can answer it. A value that is known only at deploy time, such as an input variable, a function, another resource’s attribute, or a CloudFormation parameter or import, leaves that check unanswered for that resource rather than guessed. Terraform locals set to plain values are followed. The scan lists the unanswered checks and the settings each depends on, and cloud posture judges the deployed resource once it exists.

A setting left out is read as the provider’s default for a new resource. A new S3 bucket blocks public access unless told otherwise, so leaving it out is not a finding. A new EC2 instance still answers IMDSv1, so leaving that out is one.

Templates are not rendered
Helm charts and other templated YAML are listed as not read. Render the chart in CI and commit the output, or rely on posture once it is deployed.
03Policy

Warn, or block merges and deploys

ModeWhat it does
warn (default)Every commit is scanned; the check passes and lists what was found.
blockThe check fails, and merges into protected branches are refused, while a finding at the bar or above is active (high by default; critical or medium also possible). A merge waits briefly while its head commit is being scanned.
offScans run only when someone asks for one.

Owners and admins set the policy in TillForge → IaC scanning, with the CLI or through the API. A change re-grades recent commits and re-posts their checks. A check turned off in the posture policies is not judged here either, and turning it back on re-grades recent commits from what was already read. The deploy policy check supply-0006 can also refuse a deployment whose commit has a finding at or above the bar.

bash
# Owners and admins.
tilldev forge iac policy --mode block --block-at high
tilldev forge iac accept acme-infra <scan-id> <finding-id> --reason "static website bucket, public by design"
tilldev forge iac exceptions acme-infra
tilldev forge iac withdraw acme-infra <exception-id>
04Decisions

Accepting a finding

When a finding is intended, an owner or admin accepts it with a reason from the repository’s Findings tab or with forge iac accept. The acceptance follows the check, the file and the resource’s address, so it survives edits to the resource and to other files, and lapses if the resource is renamed or moved. Withdrawing one makes the finding count again. Every decision is in the audit log.

To keep the reason next to the resource, put an allow comment inside it or on the line above:

hcl
resource "aws_s3_bucket" "www" {
  # tillforge:allow-iac posture-aws-s3-public static website, nothing private is stored here
  bucket = "acme-www"
}
An allow comment is a code change
It lands like any other line, so branch protection and review apply to it. Findings accepted this way are shown as accepted, with the comment’s reason, never hidden. JSON has no comments, so a JSON template is accepted from the Findings tab instead.
05Checks

The 59 checks judged from files

These are the posture checks whose answer is in the file. Checks that need the live account, such as unused keys or whether logging is actually delivering, run only in posture. tilldev forge iac checks prints the same list.

CheckLevelResources
A default security group allows inbound traffic
posture-aws-default-sg-rules
mediumEC2 security group
An EC2 instance still answers IMDSv1
posture-aws-imdsv1
mediumEC2 instance
An RDS instance keeps under 7 days of backups
posture-aws-rds-backups
mediumRDS instance
An RDS instance can be deleted without a safeguard
posture-aws-rds-deletion-protection
lowRDS instance
An RDS instance is publicly accessible
posture-aws-rds-public
highRDS instance
An RDS instance stores data unencrypted
posture-aws-rds-unencrypted
highRDS instance
An IAM role can be assumed by any AWS principal
posture-aws-role-open-trust
criticalIAM role
An S3 bucket still honours object ACLs
posture-aws-s3-acls
lowS3 bucket
An S3 bucket has no access logging
posture-aws-s3-logging
lowS3 bucket
An S3 bucket does not block public access itself
posture-aws-s3-public-access-block
mediumS3 bucket
An S3 bucket policy makes the bucket public
posture-aws-s3-public
criticalS3 bucket
An S3 bucket keeps no previous versions
posture-aws-s3-versioning
lowS3 bucket
A CloudTrail trail is not encrypted with a KMS key
posture-aws-trail-kms
lowCloudTrail trail
A CloudTrail trail is not logging
posture-aws-trail-stopped
highCloudTrail trail
A CloudTrail trail does not validate its log files
posture-aws-trail-validation
mediumCloudTrail trail
An Azure SQL server has auditing off
posture-azure-sql-auditing
mediumAzure SQL server
An Azure SQL server allows every IPv4 address
posture-azure-sql-open
criticalAzure SQL server
An Azure SQL server accepts TLS older than 1.2
posture-azure-sql-tls
mediumAzure SQL server
A storage account accepts plain HTTP
posture-azure-storage-https
mediumStorage account
A storage account accepts traffic from all networks
posture-azure-storage-network-open
mediumStorage account
A storage account allows anonymous blob access
posture-azure-storage-public-blob
highStorage account
A storage account accepts shared key authorisation
posture-azure-storage-shared-key
lowStorage account
A storage account accepts TLS older than 1.2
posture-azure-storage-tls
mediumStorage account
A key vault has purge protection off
posture-azure-vault-purge
mediumKey vault
A Linux VM allows SSH password login
posture-azure-vm-password-login
mediumVirtual machine
A zone is not signed with DNSSEC
posture-cf-zone-dnssec
lowZone
A zone does not send HSTS
posture-cf-zone-hsts
lowZone
A zone serves plain HTTP without redirecting
posture-cf-zone-https
mediumZone
A zone accepts TLS older than 1.2
posture-cf-zone-min-tls
mediumZone
A zone sends traffic to the origin without verified TLS
posture-cf-zone-ssl
highZone
A managed database accepts connections from any address
posture-do-db-open
criticalManaged database
A Droplet has backups off
posture-do-droplet-backups
lowDroplet
A Kubernetes cluster does not upgrade patch releases
posture-do-k8s-auto-upgrade
lowKubernetes cluster
A Kubernetes control plane accepts any address
posture-do-k8s-cp-firewall
mediumKubernetes cluster
A load balancer serves plain HTTP without redirecting
posture-do-lb-plain-http
mediumLoad balancer
A Cloud Storage bucket is public
posture-gcp-bucket-public
criticalCloud Storage bucket
A Cloud Storage bucket uses object ACLs
posture-gcp-bucket-uniform
lowCloud Storage bucket
An instance runs as the default service account with full API access
posture-gcp-default-sa-full-access
highCompute Engine instance
An instance forwards traffic for other addresses
posture-gcp-ip-forwarding
lowCompute Engine instance
An instance boots without Secure Boot
posture-gcp-secure-boot
lowCompute Engine instance
An instance accepts serial console connections
posture-gcp-serial-port
mediumCompute Engine instance
A Cloud SQL instance has automated backups off
posture-gcp-sql-backups
mediumCloud SQL instance
A Cloud SQL instance accepts connections from any address
posture-gcp-sql-open
criticalCloud SQL instance
A Cloud SQL instance accepts unencrypted connections
posture-gcp-sql-ssl
mediumCloud SQL instance
A binding grants cluster-admin
posture-k8s-cluster-admin-binding
highKubernetes role binding
A container adds a capability that breaks isolation
posture-k8s-dangerous-capabilities
highKubernetes workload
A workload shares the node's network, process or IPC namespace
posture-k8s-host-namespaces
highKubernetes workload
A workload mounts a path from the node
posture-k8s-host-path
highKubernetes workload
A container has no memory limit
posture-k8s-no-memory-limit
lowKubernetes workload
A container can gain more privileges than it started with
posture-k8s-privilege-escalation
mediumKubernetes workload
A container runs privileged
posture-k8s-privileged
highKubernetes workload
A binding grants a role to anonymous or every signed-in user
posture-k8s-public-binding
criticalKubernetes role binding
A Service opens a load balancer to every address
posture-k8s-public-load-balancer
mediumKubernetes service
A container may run as root
posture-k8s-run-as-root
mediumKubernetes workload
A container image is not pinned to a version
posture-k8s-unpinned-image
lowKubernetes workload
A role grants every verb on every resource
posture-k8s-wildcard-role
highKubernetes role
A remote administration port is open to the internet
posture-net-admin-open
highEC2 security group, Network security group, Cloud firewall, VPC firewall rule
Every port is open to the internet
posture-net-all-ports-open
highEC2 security group, Network security group, Cloud firewall, VPC firewall rule
A database port is open to the internet
posture-net-db-open
highEC2 security group, Network security group, Cloud firewall, VPC firewall rule

Next: Branch protection to require the tilldev/iac check, or the supply chain map. Back to the TillForge overview.