Your Terraform says the security group allows traffic only from the load balancer. The console says someone added 0.0.0.0/0 on port 22 during last month's outage and never removed it. This gap between what your code declares and what's actually running is configuration drift, and it's one of the quietest security risks in cloud infrastructure. It's dangerous precisely because it's invisible: your IaC scans pass, your code review looks clean, and the actual running state — the thing attackers touch — has silently diverged. This guide covers what causes drift, why it's a security problem specifically, and how to detect and reconcile it.
What Drift Actually Is
Drift is any difference between the infrastructure state described in your code and the state actually running in the cloud. Terraform maintains a state file as its model of reality; drift is when reality stops matching that model. It comes in two flavors: code-ahead drift (you changed the code but haven't applied it) and the more dangerous reality-ahead drift (something changed the live infrastructure outside of Terraform).
Where Drift Comes From
Reality-ahead drift has predictable sources:
- Console hotfixes. Someone clicks to resolve an incident at 3 a.m. and never ports the change back to code.
- Other automation. A separate script, an auto-remediation tool, or a cloud service modifying a resource it manages.
- Manual "temporary" changes that become permanent because nobody tracked them.
- Provider-side defaults changing or resources being modified by AWS/Azure/GCP under the hood.
Every one of these is normal operational behavior. Drift isn't a sign of a bad team — it's an inevitability you have to detect for.
Why Drift Is a Security Problem, Not Just an Ops Problem
Three security consequences make drift worth detecting:
- Your scans audit a fiction. If you scan IaC in CI (and you should), you're validating the code — but drift means the running config is different. A clean scan gives false confidence about infrastructure that's actually exposed.
- The next apply is a landmine. When someone finally runs
terraform apply, it may revert a manual security fix (reopening a hole someone closed by hand) or overwrite an emergency change, causing an outage. Either way, drift makes apply unpredictable. - Manual changes skip review. The console hotfix that opened port 22 never went through a pull request, so no scanner and no reviewer ever saw it. Drift is, by definition, unreviewed change.
How to Detect Drift
The Built-In Way: terraform plan
Terraform's own plan command is a drift detector. Run it against a workspace with no pending code changes and any diff it reports is drift. Use the machine-readable exit code:
terraform plan -detailed-exitcode -refresh-only
# exit 0 = no drift
# exit 2 = drift detected
# exit 1 = error
-refresh-only compares real infrastructure against state without proposing code-driven changes, isolating true drift.
The Continuous Way: Scheduled CI
Detecting drift once is useless; drift accumulates continuously. Run the check on a schedule so drift surfaces within hours, not at the next quarterly apply:
# Scheduled drift check (runs nightly)
on:
schedule:
- cron: "0 3 * * *"
jobs:
drift:
steps:
- run: terraform init
- run: terraform plan -detailed-exitcode -refresh-only || echo "DRIFT" >> $GITHUB_ENV
- if: env.DRIFT == 'DRIFT'
run: ./notify-security-team.sh
Historically, dedicated tools like driftctl filled this role, but that project was archived in 2023, and the pattern has consolidated around scheduled terraform plan plus cloud-native change tracking (AWS Config, Azure Change Analysis).
How to Reconcile Drift
Detection is half the job; reconciliation is the other half. When drift is found, decide deliberately:
- Keep the change? Port it back into code, open a PR so it goes through scanning and review, then apply. The change becomes legitimate and reviewed.
- Reject the change? Run
terraform applyto restore the declared state, and investigate who made the manual change and why.
The one thing you must not do is ignore it. Unreconciled drift compounds — each ignored diff makes the next apply more dangerous and your IaC less trustworthy as a source of truth. Feeding drift signals into a prioritized queue via Griffin AI triage helps your team act on the drift that opened a security hole first.
Drift Management Checklist
| Step | Action |
|---|---|
| Detect | Scheduled terraform plan -refresh-only in CI |
| Alert | Notify on non-zero drift exit code |
| Classify | Security-relevant drift (network/IAM/encryption) first |
| Reconcile | Port to code + PR, or apply to restore |
| Prevent | Restrict console write access; require changes via IaC |
How Safeguard Helps
Safeguard makes drift actionable rather than just visible. It scans your infrastructure-as-code continuously through the pipeline-native CLI, so when running state diverges from your Terraform, the security-relevant drift — a newly opened security group, a disabled encryption setting, an IAM policy that widened — is surfaced against the code it should match, not buried in a raw diff of every changed attribute. Griffin, the AI triage engine, prioritizes that drift by real exposure, so the manually opened SSH port ranks above a cosmetic tag change. The result is that your IaC stays authoritative and your scans keep auditing reality instead of a fiction.
Drift is inevitable; unmanaged drift is optional. Detect it on a schedule, treat security-relevant divergence as urgent, and reconcile every difference deliberately — and your infrastructure-as-code remains the trustworthy source of truth it's supposed to be.
Want your infrastructure drift caught and prioritized automatically? Start free with Safeguard or read the documentation to connect your IaC repository.