Terraform drift intelligence: detect, attribute, score, and fix IaC drift
Project description
๐ drifty
Terraform drift intelligence โ detect what changed, who changed it, how dangerous it is, and how to fix it.
pip install drifty
The Problem
terraform plan tells you what drifted. It doesn't tell you who changed it, how dangerous the change is, or what to do about it.
Manual changes in the AWS console during incidents, auto-scaling events, and ad-hoc CLI commands silently diverge your infrastructure from your Terraform state. By the time you notice, you don't know if it was a colleague, a runbook, or a security incident.
Enterprise platforms like Spacelift and HCP Terraform Cloud detect drift on a schedule โ but they're full IaC platforms that are heavyweight, expensive, and still offer zero attribution or severity intelligence.
drifty fills this exact gap.
Demo
$ drifty scan --workspace ./infra --attribute
๐ drifty โ Terraform Drift Intelligence
Scanning workspace: ./infra | 2026-06-05 14:00 UTC
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ 3 drifts detected - 1 Critical - 1 High - 1 Low โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
๐ด CRITICAL aws_security_group.main (sg-0abc1234)
Changed: ingress.0.cidr_blocks โ ["0.0.0.0/0"] (was: ["10.0.0.0/8"])
Who: arn:aws:iam::123456789:user/john.doe
When: 2026-06-03 14:22:11 UTC
Action: ModifySecurityGroupRules
Fix: terraform import aws_security_group.main sg-0abc1234
๐ HIGH aws_instance.api_server (i-0def5678)
Changed: instance_type โ t3.large (was: t3.medium)
Who: arn:aws:iam::123456789:role/ops-automation
When: 2026-06-02 09:15:44 UTC
Action: ModifyInstanceAttribute
Fix: terraform import aws_instance.api_server i-0def5678
๐ข LOW aws_s3_bucket.assets (assets-bucket-prod)
Changed: tags.LastModified โ "2026-06-01" (was: "2026-05-15")
Who: attribution unavailable (event outside 90-day CloudTrail window)
Fix: Add tag to Terraform config or run terraform apply to reconcile
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Run `drifty report --format markdown` to export this as a report.
Install
Requirements: Python 3.10+, Terraform 1.1+, AWS credentials configured
pip install drifty
Quick Start
# 1. Initialize config in your Terraform workspace
cd ./infra
drifty init
# 2. Scan for drift
drifty scan
# 3. Scan with CloudTrail attribution (who caused each drift)
drifty scan --attribute
# 4. Filter to critical and high only
drifty scan --severity high
# 5. Output as JSON (for CI/CD piping)
drifty scan --output json | jq '.findings[] | select(.severity=="critical")'
# 6. Export a markdown report
drifty report --format markdown --out ./drift-report.md
How It Works
drifty scan
โ
โโ 1. runs: terraform plan -refresh-only -json
โ
โโ 2. parses JSON Lines output โ extracts resource_drift entries
โ
โโ 3. scores each finding (scorer.py)
โ critical โ IAM, security groups, S3 policies
โ high โ EC2 instances, RDS, load balancers
โ medium โ Lambda, Auto Scaling, CloudWatch
โ low โ tag-only changes
โ
โโ 4. attributes each finding via CloudTrail (if --attribute)
โ boto3 โ LookupEvents by resource ID
โ returns: IAM principal, timestamp, API action
โ
โโ 5. renders output
terminal โ Rich color-coded table
json โ structured output for CI/CD
markdown โ report for Confluence / Notion
Commands
drifty scan
Options:
--workspace PATH Terraform directory (default: current dir)
--profile TEXT AWS CLI profile (default: "default")
--attribute Enable CloudTrail attribution
--severity TEXT Minimum severity: critical | high | medium | low
--output TEXT Output format: terminal | json | markdown
--notify TEXT Send results to: slack
drifty init
Initializes .drifty/config.yaml in the workspace with default settings.
drifty config set KEY=VALUE
drifty config set default_severity=high
drifty config set default_profile=prod
drifty config set slack_webhook=https://hooks.slack.com/...
drifty config set cloudtrail_lookback_days=30
drifty report
drifty report --format markdown --out ./reports/drift-$(date +%F).md
drifty report --format json
Severity Rules
| Resource Type | Severity |
|---|---|
aws_iam_role_policy, aws_iam_policy |
๐ด Critical |
aws_security_group, aws_security_group_rule |
๐ด Critical |
aws_s3_bucket_policy, aws_s3_bucket_public_access_block |
๐ด Critical |
aws_instance (type/AMI change) |
๐ High |
aws_rds_instance, aws_lb, aws_alb |
๐ High |
aws_lambda_function, aws_autoscaling_group |
๐ก Medium |
aws_cloudwatch_metric_alarm |
๐ก Medium |
aws_instance (tag-only change) |
๐ข Low |
aws_s3_bucket (tag-only change) |
๐ข Low |
Override any rule per-workspace:
# .drifty/config.yaml
severity_overrides:
aws_lambda_function: high
aws_cloudwatch_metric_alarm: low
drifty vs. the Alternatives
| Feature | terraform plan |
Spacelift / HCP TF | drifty |
|---|---|---|---|
| Detects drift | โ | โ | โ |
| Who caused it | โ | โ | โ CloudTrail |
| Severity score | โ | โ | โ |
| Remediation hint | โ | โ | โ |
| JSON / Markdown output | โ | partial | โ |
| Works locally / in CI | โ | โ SaaS only | โ |
| Cost | free | $$$ | free |
| Install | N/A | platform setup | pip install drifty |
Configuration Reference
# .drifty/config.yaml
default_profile: default # AWS CLI profile
default_severity: null # minimum severity filter (null = show all)
default_output: terminal # terminal | json | markdown
slack_webhook: null # Slack incoming webhook URL
cloudtrail_lookback_days: 90 # max 90 (CloudTrail API limit)
severity_overrides: {} # per-resource type overrides
Roadmap
-
--notify slackโ post drift summary to Slack webhook -
drifty watchโ continuous drift monitoring on a poll interval - Azure and GCP provider support
- GitHub PR comment integration โ post drift report as a PR comment
- Drift history โ persist findings to
.drifty/history.json -
drifty ignoreโ suppress known/accepted drift entries
Contributing
git clone https://github.com/satyajit-dey-17/drifty.git
cd drifty
poetry install
poetry run pytest -v
poetry run ruff check drifty/
poetry run black drifty/
Please open an issue before submitting a large PR.
See .github/ISSUE_TEMPLATE/ for bug and feature templates.
License
MIT ยฉ Satyajit Dey
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file drifty-0.2.0.tar.gz.
File metadata
- Download URL: drifty-0.2.0.tar.gz
- Upload date:
- Size: 24.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
01df12ca2b956ecb469bd5854f326cfad7a2a346c4c14b30258eb03b06f9452d
|
|
| MD5 |
d9543c7d1554489600d54f8dcda5aab9
|
|
| BLAKE2b-256 |
331715a38cfca233e7b4a7ef98daa18d8c3a2f33701021dd125e19e9516e90ea
|
Provenance
The following attestation bundles were made for drifty-0.2.0.tar.gz:
Publisher:
publish.yml on satyajit-dey-17/drifty
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
drifty-0.2.0.tar.gz -
Subject digest:
01df12ca2b956ecb469bd5854f326cfad7a2a346c4c14b30258eb03b06f9452d - Sigstore transparency entry: 1736194373
- Sigstore integration time:
-
Permalink:
satyajit-dey-17/drifty@32cb41bb3f5338d60f163126afe5ad9c25031f9f -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/satyajit-dey-17
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@32cb41bb3f5338d60f163126afe5ad9c25031f9f -
Trigger Event:
release
-
Statement type:
File details
Details for the file drifty-0.2.0-py3-none-any.whl.
File metadata
- Download URL: drifty-0.2.0-py3-none-any.whl
- Upload date:
- Size: 27.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8863ed557206dcd254ea4bc2498a2a629c6b2dd326ab538ed0f6d670409afee3
|
|
| MD5 |
ab63db09281811c94548c5b8a9051dde
|
|
| BLAKE2b-256 |
c1ccdbf9868eee6fc913910c1ab358c4e5b4cfee4f0ea417cd364aa1284e0438
|
Provenance
The following attestation bundles were made for drifty-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on satyajit-dey-17/drifty
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
drifty-0.2.0-py3-none-any.whl -
Subject digest:
8863ed557206dcd254ea4bc2498a2a629c6b2dd326ab538ed0f6d670409afee3 - Sigstore transparency entry: 1736194399
- Sigstore integration time:
-
Permalink:
satyajit-dey-17/drifty@32cb41bb3f5338d60f163126afe5ad9c25031f9f -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/satyajit-dey-17
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@32cb41bb3f5338d60f163126afe5ad9c25031f9f -
Trigger Event:
release
-
Statement type: