Terraform, CloudFormation, and Pulumi hardening: state as a secret store, pinned providers and modules, encryption and network defaults, drift, and the privilege of the pipeline that applies the plan. Use when generating infrastructure code, reviewing IaC changes in a pull request, configuring a state backend, or setting up a new cloud account or workspace.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add ShieldNet-360/secure-vibe --skill iac-security --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Iac Security?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/shieldnet-360-iac-security-secure-vibe)More formats (shields.io, HTML) on the badges page.
---
name: iac-security
description: "Terraform, CloudFormation, and Pulumi hardening: state as a secret store, pinned providers and modules, encryption and network defaults, drift, and the privilege of the pipeline that applies the plan. Use when generating infrastructure code, reviewing IaC changes in a pull request, configuring a state backend, or setting up a new cloud account or workspace."
---
<!-- Native skill bundle for Claude Code. Generated by `secure-vibe dev regenerate`. -->
<!-- Do not edit by hand; the source of truth is skills/iac-security/SKILL.md. -->
# Infrastructure-as-Code Security
Terraform, CloudFormation, and Pulumi hardening: state as a secret store, pinned providers and modules, encryption and network defaults, drift, and the privilege of the pipeline that applies the plan. Use when generating infrastructure code, reviewing IaC changes in a pull request, configuring a state backend, or setting up a new cloud account or workspace.
## ALWAYS
- Treat the **state file as a secret store**. Every value that passes through Terraform — a generated password, an RDS master credential, a private key, the contents of a data source — is written to state in plaintext. `sensitive = true` only hides a value from CLI output; it changes nothing about what is stored. So read access to the backend is read access to those secrets, and the backend's access policy has to be written on that basis.
- Configure a **remote backend** with encryption at rest, state locking, and versioning. Prefer the backend's own locking where it exists — recent Terraform supports S3-native locking, so a separate DynamoDB lock table is no longer required for new stacks. `references/backends-and-state.md` has the per-backend form.
- Pin providers and modules to an exact version or a pessimistic constraint (`~> 5.42`), and pin a third-party module sourced from a git URL to a **commit SHA**, not a branch or tag. A module is code that runs with your apply credentials, and a registry or repository you do not control is a supply-chain input like any other.
- Give the **apply identity** the least privilege that the stack actually needs, and keep it separate from the plan identity. A pipeline role with account-wide admin turns any CI compromise into a full account compromise — `cicd-security` owns hardening the pipeline itself, `iam-best-practices` owns the role's shape.
- Encrypt persistent resources by default with a customer-managed key: object storage, block storage, managed databases, queues, log groups. Prefer a module or default that applies it, so a new resource is encrypted because of how the code is organised rather than because someone remembered.
- Deny inbound by default and open ports deliberately. Administrative and database ports reachable from `0.0.0.0/0` are the finding — SSH, RDP, and the managed database ports especially — and "it is only dev" does not change the exposure. `references/backends-and-state.md` lists the ports worth failing a build over.
- Run `plan` (or `pulumi preview`, a CloudFormation change set) in CI, require human approval before applying to production, and run scheduled **drift detection** that raises an issue when the cloud diverges from the code. Drift is where a break-glass change made at 3am quietly becomes the permanent configuration.
- Scope roles and resource policies with the provider's own condition mechanism — source account, source ARN, organization ID, TLS-only access on storage. `iam-best-practices` owns policy design; what belongs here is that the condition is expressed in the code rather than clicked into the console.
## NEVER
- Hardcode provider credentials in code or `.tfvars` (`access_key`, `secret_key`, `client_secret`, `service_account_key`). Use OIDC federation from CI, the platform's workload identity, or a secret manager.
- Commit `terraform.tfstate`, `terraform.tfstate.backup`, `.pulumi/`, or a `.tfvars` holding real values — see the state rule above for why the file is a credential dump even when the code only references variables.
- Use `local-exec` / `null_resource` to fetch a secret at apply time. It lands in state, and the provisioner runs on whatever machine ran `apply`, with that machine's identity.
- Disable provider TLS verification (`skip_tls_verify`, `insecure = true`).
## KNOWN FALSE POSITIVES
- A bastion deliberately reachable on SSH is not the same finding as a database open to the world — but "bastion" is not the exemption, its configuration is: key-only authentication, MFA, and a narrow source range. A session-manager or identity-aware-proxy path that removes the public port entirely is better than hardening one.
- Resources that are meant to be internet-facing: CDN distributions, load-balancer listeners on 80/443, API gateways, function URLs.
- Backend bootstrap resources — the bucket and lock mechanism the backend itself uses — cannot live in the state they provide. A one-time local backend that is then migrated is the normal resolution, not a finding.
- An unencrypted resource that holds nothing but public data, where the tag or classification says so explicitly.
## Reference files
Read these only when the task calls for them.
- `references/backends-and-state.md`
- `references/verifying-findings.md`
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!