Skip to main content

blastcheck

blastcheck reads a terraform show -json plan and emits an Impact Manifest — a machine-readable change-safety assertion. It is the reference producer of that open format.

terraform plan -out plan.tfplan
terraform show -json plan.tfplan | blastcheck > manifest.json

terraform plan tells you what will change. blastcheck adds the layer the plan can't: for each change, is it reversible, does anything become unrecoverable, does exposure widen, and — honestly — what could it not determine.

What it does, and what it deliberately doesn't

blastcheck is offline: no credentials, no network, no hosted service. It reasons from the plan artifact alone. Wherever a verdict genuinely requires live cloud state — "is this disk attached to something serving traffic?", "does a backup exist?" — it emits unknown / not_verified with a stated reason, rather than guessing.

A direct consequence, and the point of the format: a plan-only run can never emit safe. It never verified live state, so it says caution, blocked, or unknown — never safe. Certifying safe requires the live-state enrichment that a later version (or a paid consumer) layers on. blastcheck is honest about the ceiling of what a plan alone can prove.

What it derives from the plan alone is still substantial:

  • Irreversibility — e.g. a managed-disk grow is one-way (Azure can't shrink), visible in the plan diff.
  • Widened exposure — an inbound NSG rule opening 0.0.0.0/0 to a sensitive port; a storage account turning on public access or lowering TLS.
  • Data-loss risk — deleting a data-bearing resource flags the primary copy as removed and recoverability as unverified, not safe.
  • Cost direction and action semantics.
  • Drift — see below. This is the one place blastcheck reaches a real state determination rather than an unknown.

Drift, without asking you for credentials

terraform plan refreshes by default: before computing a diff it reads live reality for every managed resource, and records anything that moved in a top-level resource_drift array. That is a live-state observation already sitting inside the offline artifact. blastcheck did not perform the read — Terraform did — but the fact is no less true for it.

A resource appearing in both resource_drift and resource_changes is the most dangerous shape blastcheck can find, and it is graded blocking:

azurerm_managed_disk.sql_data   severity: blocking
  state_confidence: drift_detected  (recorded 512 -> live 1024)

The plan is internally consistent. It reads as routine. It was computed against a description of that resource which had already stopped being true, and every other verdict for it was derived from that same stale state.

Two limits, stated rather than papered over:

  • An empty resource_drift is ambiguous. It means either "refresh found nothing" or "refresh did not run" (-refresh=false), and the plan does not record which. So absence never earns state_matches_reality; it stays not_verified.
  • Refresh only sees resources Terraform manages. Anything created outside Terraform is not in state, so nothing refreshes it. Shadow-IT discovery needs a direct cloud query and is out of scope here.

Drift on a resource this plan does not touch is recorded under extensions.drift_outside_this_plan rather than invented into a change.

blastcheck is a producer, not a gate. It emits the manifest and exits 0; turning that into pass/fail is a separate policy layer (a CI gate). Exit codes reflect execution, not the verdict.

Coverage: every provider, in three layers

Every change in a plan is assessed. Nothing is skipped for being an unfamiliar type.

Layer Applies to Confidence
0 — structural any provider ever written high
1 — heuristic any provider, by name and value patterns low, tagged heuristic
2 — precise types with an exact rule high

Layer 0 reads what Terraform states regardless of provider: action semantics (a delete is a delete, a replace implies a destroy), action_reason, replace_paths, resource_drift, unreadable fields, and plan-level errored / complete.

Layer 1 matches on resource-type names (*_disk, *_bucket, *_database) and attribute names and values (publicly_accessible, storage_encrypted: false, acl: public-read, an inbound 0.0.0.0/0). On a real 55-change AWS plan, with zero AWS-specific code, this finds a publicly accessible unencrypted RDS instance, a public-read S3 bucket, and a security group open to the internet.

These findings are graded caution, never blocking, carry confidence: low, and their evidence is tagged source: heuristic. They are leads, not determinations, and saying so is the difference between a tool people read and a tool people mute. In the same spirit, an open egress rule and a default route are not flagged: they are 0.0.0.0/0 by definition, and firing on them would trip on nearly every plan ever written.

Layer 2 is the precise set: Azure managed disks, virtual machines, network security groups (+ rules), storage accounts, SQL databases. Where a precise rule exists it wins, and a heuristic never overrides it.

extensions.assessment records which layer produced each verdict.

Install & use

pip install blastcheck
terraform show -json plan.tfplan | blastcheck            # stdin
blastcheck --plan plan.json > manifest.json              # from a file
blastcheck --compact                                     # single-line JSON

No hosted service, no cloud credentials, no network, no runtime dependencies — it runs entirely against the plan file.

In CI

- run: terraform show -json tfplan > plan.json

- uses: prococonsulting/blastcheck@v0
  with:
    plan: plan.json

The manifest is uploaded as a build artifact and the verdict is posted on the pull request. The action does not fail the build — blastcheck is a producer, not a gate, and what a blocked verdict should do to a pipeline is a policy question that belongs to you. To gate on it:

- uses: prococonsulting/blastcheck@v0
  id: bc
  with:
    plan: plan.json

- if: steps.bc.outputs.verdict == 'blocked'
  run: exit 1

Note what you cannot write: there is no verdict == 'safe' gate to pass on a plan-only run, because a plan-only run never emits safe. A pipeline that proceeds only on a positive safety claim needs the live-state enrichment. That is the honest ceiling of what a plan by itself can prove.

Tests

The suite's primary job is to prove every emitted manifest validates against the vendored schema (blastcheck/schema/impact-manifest.schema.json) on realistic plan fixtures — that is how a schema shape error surfaces.

pip install -e ".[test]"
pytest

Relationship to the spec

blastcheck implements the Impact Manifest specification and vendors a pinned copy of its schema at blastcheck/schema/, which ships inside the wheel. The format is open and vendor-neutral; blastcheck is a reference implementation of it, not its owner.

Status

v0.3 — draft, and evolving alongside the spec (which does not freeze at 1.0 until this tool has run against real Terraform plans).

Contributing and contact

The bar for a change, and how to add a rule: CONTRIBUTING.md. Security reports: SECURITY.md.

If you are implementing the Impact Manifest format in another tool, open an issue on the specification repository — ambiguities in the spec are the most useful feedback it can get while it is still v0.1 draft.

Anything else: kproffitt@prococonsulting.com

License

Apache-2.0. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

blastcheck-0.3.0.tar.gz (49.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

blastcheck-0.3.0-py3-none-any.whl (37.5 kB view details)

Uploaded Python 3

File details

Details for the file blastcheck-0.3.0.tar.gz.

File metadata

  • Download URL: blastcheck-0.3.0.tar.gz
  • Upload date:
  • Size: 49.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.2

File hashes

Hashes for blastcheck-0.3.0.tar.gz
Algorithm Hash digest
SHA256 a960b35a5331351cd5abc68394f48a2796560ae56fa4d6b4d4f3ec2f2f674888
MD5 80c048bae46c6d77b6dd38373da8dd75
BLAKE2b-256 b2362b7840c8d0c6135ca61f92950ceba55febbc971e629ddc952c6e2a9b7a07

See more details on using hashes here.

File details

Details for the file blastcheck-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: blastcheck-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 37.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/7.0.0 CPython/3.13.2

File hashes

Hashes for blastcheck-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 1bc47306215e7d1112053726e22da0a4ed5dc067317dcff70afb22736fedf693
MD5 b9b9a15f8bdcfd1146ee70c3a06ee9fe
BLAKE2b-256 d236dc1d05dab187d479b9389efe3c4a3a7a764b61548f452b12565074a50d4f

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 files

0.2.0

2 files

0.1.1

2 files

0.1.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page