Drift Detection
Drift detection compares your live cloud infrastructure against Terraform state to find resources that are missing, unmanaged, or changed. DriftWise generates AI-powered narratives explaining what drifted and why it matters, plus remediation steps to fix it.
How It Works
Cloud Account State Source DriftWise
│ │ │
│──── scan ───────────>│ │
│ (live resources) │ │
│ │──── fetch state ──>│
│ │ (IaC resources) │
│ │ │
│ │ compare ───────│
│ │ │
│ │ drift items ───│
│ │ narrative ─────│
│ │ import blocks ─│
- Scan — DriftWise reads live resources from your cloud account (AWS, GCP, Azure)
- Fetch state — Terraform state is pulled from your configured state sources
- Compare — Live resources are matched against IaC resources by cloud-native ID
- Classify — Each difference is categorized as missing, extra, or changed
- Narrate — An LLM generates a plain-English explanation with risk scoring and remediation
Drift Types
| Type | Meaning | Example |
|---|---|---|
| Missing | In Terraform state but not in the cloud | Someone deleted an EC2 instance outside Terraform |
| Extra | In the cloud but not in Terraform state | Shadow IT — a manually created S3 bucket |
| Changed | In both, but attributes differ | Security group rules modified in the console |
Running a Scan
Via the UI
- Go to the cloud account you want to scan
- Click Scan Now
- Wait for the scan to complete (status: pending → running → done)
- Click Compute Drift to compare against Terraform state
Via the API
Step 1 — Start a scan:
curl -X POST "https://app.driftwise.ai/api/v2/orgs/$ORG_ID/scans" \
-H "x-api-key: $DRIFTWISE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"cloud_account_id": "<account-uuid>"
}'
Returns a scan object with an id and status: "pending". The scan worker picks it up and runs it asynchronously.
Step 2 — Check scan status:
curl "https://app.driftwise.ai/api/v2/orgs/$ORG_ID/scans/<scan_id>" \
-H "x-api-key: $DRIFTWISE_API_KEY"
Wait for status: "done" before computing drift.
Step 3 — Compute drift:
curl -X POST "https://app.driftwise.ai/api/v2/orgs/$ORG_ID/scans/<scan_id>/drift/compute" \
-H "x-api-key: $DRIFTWISE_API_KEY"
Returns a drift snapshot with items, counts, and a narrative (if an LLM is configured).
Step 4 — Get drift results:
curl "https://app.driftwise.ai/api/v2/orgs/$ORG_ID/scans/<scan_id>/drift" \
-H "x-api-key: $DRIFTWISE_API_KEY"
Drift Response
{
"id": "drift-snapshot-uuid",
"scan_run_id": "scan-uuid",
"missing_count": 2,
"extra_count": 1,
"changed_count": 3,
"total_live": 45,
"total_iac": 47,
"coverage_pct": 95.7,
"risk_level": "high",
"narrative": "## Drift Summary\n\n3 resources have changed...",
"narrative_status": "done",
"items": [
{
"id": "item-uuid",
"drift_type": "changed",
"resource_name": "prod-web-sg",
"resource_type": "AWS::EC2::SecurityGroup",
"provider": "aws",
"region": "us-east-1",
"attribute_diffs": { "ingress": { "old": "...", "new": "..." } }
},
{
"id": "item-uuid",
"drift_type": "extra",
"resource_name": "vpc-048783f9c43b52b6b",
"resource_type": "AWS::EC2::VPC",
"provider": "aws",
"region": "us-east-1"
}
]
}
resource_type on each drift item is the provider-native type string — AWS::<Service>::<Resource> for AWS, compute.googleapis.com/<Kind> for GCP, Microsoft.<Namespace>/<Resource> for Azure, and k8s/<kind> for Kubernetes. The broad category (compute, storage, network, etc.) that Cloud Discovery assigns lives on the underlying live_resources row as normalized_type and is exposed separately on the resources API.
Narratives
When an LLM is configured (either the platform LLM or BYOK), drift computation triggers narrative generation:
- Narrative status progresses:
pending→processing(transient, while the worker is calling the LLM) →done,error, orskipped skipped— returned in two cases: (1) the org has no BYOK and the platform-LLM weekly quota is already exhausted for this scan; (2) the org has BYOK configured but the upstream provider returned an error — DriftWise does not silently fall back to the platform LLM. Drift items, counts, and import blocks are still available in both cases. Check theskip_reasonfield on the snapshot to distinguish —weekly_quota_exhausted/hourly_rate_limited/plan_hard_offfor platform-LLM gating,byok_client_error/byok_rate_limited/decrypt_failed/key_unavailablefor BYOK-side failures.error— LLM call failed (timeout, 5xx, parse error). Drift data is still available.- No drift produces a fixed message: "No drift detected. Your live infrastructure matches the Terraform state exactly."
- With drift produces a structured summary covering what changed, risk assessment, and recommended actions
- Narratives are best-effort — if generation fails or is skipped, drift data is still available
Import Blocks
For extra (unmanaged) resources, DriftWise generates Terraform import blocks to bring them under management:
import {
to = aws_vpc.imported_vpc_048783f9c43b52b6b
id = "vpc-048783f9c43b52b6b"
}
resource "aws_vpc" "imported_vpc_048783f9c43b52b6b" {
# Placeholder — run 'terraform plan' after import to see actual attributes
}
These appear in the narrative under a "Bring Under Management" section. Copy the blocks into your Terraform config and run terraform plan to populate the resource attributes.
Bulk Scans
Scan multiple cloud accounts at once:
curl -X POST "https://app.driftwise.ai/api/v2/orgs/$ORG_ID/scans/bulk" \
-H "x-api-key: $DRIFTWISE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"account_ids": ["account-1-uuid", "account-2-uuid"]
}'
Omit account_ids to scan all accounts in the org. Bulk scans respect your plan's concurrent scan limit.
Provider Scoping
Drift detection is always scoped to a single cloud provider. When a scan runs against an AWS account, only AWS resources are compared — GCP and Azure resources are untouched. This prevents cross-provider contamination in drift results.