Catch the open bucket before it exists.
GAEvery 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.
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.
| Format | Reads | Judges |
|---|---|---|
| Terraform | .tf and .tf.json, one module per directory | AWS, Google Cloud, Azure, DigitalOcean and Cloudflare resources |
| CloudFormation | YAML or JSON templates with a Resources section | S3 buckets and their policies, security groups, RDS and EC2 instances, CloudTrail trails, IAM roles |
| Kubernetes | YAML manifests, several documents to a file | Pods 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.
# 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 terraformAnswered 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.
Warn, or block merges and deploys
| Mode | What it does |
|---|---|
| warn (default) | Every commit is scanned; the check passes and lists what was found. |
| block | The 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. |
| off | Scans 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.
# 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>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:
resource "aws_s3_bucket" "www" {
# tillforge:allow-iac posture-aws-s3-public static website, nothing private is stored here
bucket = "acme-www"
}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.
| Check | Level | Resources |
|---|---|---|
A default security group allows inbound trafficposture-aws-default-sg-rules | medium | EC2 security group |
An EC2 instance still answers IMDSv1posture-aws-imdsv1 | medium | EC2 instance |
An RDS instance keeps under 7 days of backupsposture-aws-rds-backups | medium | RDS instance |
An RDS instance can be deleted without a safeguardposture-aws-rds-deletion-protection | low | RDS instance |
An RDS instance is publicly accessibleposture-aws-rds-public | high | RDS instance |
An RDS instance stores data unencryptedposture-aws-rds-unencrypted | high | RDS instance |
An IAM role can be assumed by any AWS principalposture-aws-role-open-trust | critical | IAM role |
An S3 bucket still honours object ACLsposture-aws-s3-acls | low | S3 bucket |
An S3 bucket has no access loggingposture-aws-s3-logging | low | S3 bucket |
An S3 bucket does not block public access itselfposture-aws-s3-public-access-block | medium | S3 bucket |
An S3 bucket policy makes the bucket publicposture-aws-s3-public | critical | S3 bucket |
An S3 bucket keeps no previous versionsposture-aws-s3-versioning | low | S3 bucket |
A CloudTrail trail is not encrypted with a KMS keyposture-aws-trail-kms | low | CloudTrail trail |
A CloudTrail trail is not loggingposture-aws-trail-stopped | high | CloudTrail trail |
A CloudTrail trail does not validate its log filesposture-aws-trail-validation | medium | CloudTrail trail |
An Azure SQL server has auditing offposture-azure-sql-auditing | medium | Azure SQL server |
An Azure SQL server allows every IPv4 addressposture-azure-sql-open | critical | Azure SQL server |
An Azure SQL server accepts TLS older than 1.2posture-azure-sql-tls | medium | Azure SQL server |
A storage account accepts plain HTTPposture-azure-storage-https | medium | Storage account |
A storage account accepts traffic from all networksposture-azure-storage-network-open | medium | Storage account |
A storage account allows anonymous blob accessposture-azure-storage-public-blob | high | Storage account |
A storage account accepts shared key authorisationposture-azure-storage-shared-key | low | Storage account |
A storage account accepts TLS older than 1.2posture-azure-storage-tls | medium | Storage account |
A key vault has purge protection offposture-azure-vault-purge | medium | Key vault |
A Linux VM allows SSH password loginposture-azure-vm-password-login | medium | Virtual machine |
A zone is not signed with DNSSECposture-cf-zone-dnssec | low | Zone |
A zone does not send HSTSposture-cf-zone-hsts | low | Zone |
A zone serves plain HTTP without redirectingposture-cf-zone-https | medium | Zone |
A zone accepts TLS older than 1.2posture-cf-zone-min-tls | medium | Zone |
A zone sends traffic to the origin without verified TLSposture-cf-zone-ssl | high | Zone |
A managed database accepts connections from any addressposture-do-db-open | critical | Managed database |
A Droplet has backups offposture-do-droplet-backups | low | Droplet |
A Kubernetes cluster does not upgrade patch releasesposture-do-k8s-auto-upgrade | low | Kubernetes cluster |
A Kubernetes control plane accepts any addressposture-do-k8s-cp-firewall | medium | Kubernetes cluster |
A load balancer serves plain HTTP without redirectingposture-do-lb-plain-http | medium | Load balancer |
A Cloud Storage bucket is publicposture-gcp-bucket-public | critical | Cloud Storage bucket |
A Cloud Storage bucket uses object ACLsposture-gcp-bucket-uniform | low | Cloud Storage bucket |
An instance runs as the default service account with full API accessposture-gcp-default-sa-full-access | high | Compute Engine instance |
An instance forwards traffic for other addressesposture-gcp-ip-forwarding | low | Compute Engine instance |
An instance boots without Secure Bootposture-gcp-secure-boot | low | Compute Engine instance |
An instance accepts serial console connectionsposture-gcp-serial-port | medium | Compute Engine instance |
A Cloud SQL instance has automated backups offposture-gcp-sql-backups | medium | Cloud SQL instance |
A Cloud SQL instance accepts connections from any addressposture-gcp-sql-open | critical | Cloud SQL instance |
A Cloud SQL instance accepts unencrypted connectionsposture-gcp-sql-ssl | medium | Cloud SQL instance |
A binding grants cluster-adminposture-k8s-cluster-admin-binding | high | Kubernetes role binding |
A container adds a capability that breaks isolationposture-k8s-dangerous-capabilities | high | Kubernetes workload |
A workload shares the node's network, process or IPC namespaceposture-k8s-host-namespaces | high | Kubernetes workload |
A workload mounts a path from the nodeposture-k8s-host-path | high | Kubernetes workload |
A container has no memory limitposture-k8s-no-memory-limit | low | Kubernetes workload |
A container can gain more privileges than it started withposture-k8s-privilege-escalation | medium | Kubernetes workload |
A container runs privilegedposture-k8s-privileged | high | Kubernetes workload |
A binding grants a role to anonymous or every signed-in userposture-k8s-public-binding | critical | Kubernetes role binding |
A Service opens a load balancer to every addressposture-k8s-public-load-balancer | medium | Kubernetes service |
A container may run as rootposture-k8s-run-as-root | medium | Kubernetes workload |
A container image is not pinned to a versionposture-k8s-unpinned-image | low | Kubernetes workload |
A role grants every verb on every resourceposture-k8s-wildcard-role | high | Kubernetes role |
A remote administration port is open to the internetposture-net-admin-open | high | EC2 security group, Network security group, Cloud firewall, VPC firewall rule |
Every port is open to the internetposture-net-all-ports-open | high | EC2 security group, Network security group, Cloud firewall, VPC firewall rule |
A database port is open to the internetposture-net-db-open | high | EC2 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.