Skip to main content

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 ─│
  1. Scan — DriftWise reads live resources from your cloud account (AWS, GCP, Azure)
  2. Fetch state — Terraform state is pulled from your configured state sources
  3. Compare — Live resources are matched against IaC resources by cloud-native ID
  4. Classify — Each difference is categorized as missing, extra, or changed
  5. Narrate — An LLM generates a plain-English explanation with risk scoring and remediation

Drift Types

TypeMeaningExample
MissingIn Terraform state but not in the cloudSomeone deleted an EC2 instance outside Terraform
ExtraIn the cloud but not in Terraform stateShadow IT — a manually created S3 bucket
ChangedIn both, but attributes differSecurity group rules modified in the console

Running a Scan

Via the UI

  1. Go to the cloud account you want to scan
  2. Click Scan Now
  3. Wait for the scan to complete (status: pending → running → done)
  4. 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: pendingprocessing (transient, while the worker is calling the LLM) → done, error, or skipped
  • 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 the skip_reason field on the snapshot to distinguish — weekly_quota_exhausted / hourly_rate_limited / plan_hard_off for platform-LLM gating, byok_client_error / byok_rate_limited / decrypt_failed / key_unavailable for 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.