BlastRadius Private beta

Your last plan review missed the destroy. This one won't.

BlastRadius reads the terraform plan your CI already produces and leaves one severity-ranked comment on the pull request: what gets destroyed, what changes who can reach what, and nothing else.

blastradiusbot commented on this pull request

๐Ÿ”ด 1 critical

DynamoDB table will be destroyed and recreated โ€” aws_dynamodb_table.leaderboard ยท replace

hash_key cannot be changed in place, so Terraform will destroy this DynamoDB table and create a new one at the same address โ€” the existing data does not come with it. Point-in-time recovery is off, so there is no restore window: every item goes.

  • hash_key: user_id โ†’ player_id
  • point_in_time_recovery[0].enabled: false

3 noise changes filtered out of this review.

Disagree with something here? A .blastradius.yml in your repository root can lower a rule's severity or ignore a resource.

Not a mockup. This page is built by running a real plan through the same analyzer and the same renderer that post to your pull request: 12 resource changes went in, 1 finding came out. Change a rule's wording and the comment above changes with it.

How it works

Three steps, and the second one is the one that matters.

01

Plan the way you already do

Your workflow runs terraform plan -out. Ours runs terraform show -json on the file it wrote, inside your job, in one process's memory.

02

Redacted before it leaves

Everything Terraform marked sensitive, plus a provider secret-attribute denylist, plus every other copy of those values, becomes [REDACTED] in your runner โ€” before anything is uploaded.

03

One comment, one check

Findings ranked worst first, in a single comment rewritten in place on every push โ€” never a thread. The check run carries the same verdict to the merge button.

What it looks at

Six rules, AWS today. Each ships with a plan that must trigger it and a plan that must not, and both are checked on every commit โ€” because a finding a reviewer never needed is what teaches them to skim the rest.

Data that will not survive the apply

stateful-replace

A database, table, volume, filesystem, bucket or key this plan destroys or recreates, and whether anything could bring the contents back. A final snapshot, an automated backup, a point-in-time window: the finding says which of those you have, and the attribute it read to find out.

A firewall rule that started letting the internet in

security-groups

An ingress rule that gains 0.0.0.0/0 or ::/0, or a port range that widens from one service to thousands. A rule being narrowed is not a finding, and neither is a plan that merely contains 0.0.0.0/0 โ€” the trigger is always the direction of the change.

Something that became reachable from outside

public-exposure

A managed database given a public endpoint, a cluster API opened to the world, a bucket whose public-access block came off. Creating one counts: a public database is exposure whether or not it existed yesterday.

A guard rail switched off

deletion-protection

Deletion protection, a backup retention window set to zero, point-in-time recovery turned off. The trigger is the transition to no guard at all โ€” shortening a window is a judgement about your workload, and the plan does not carry your workload.

Encryption that got weaker, or a key that is going away

encryption

A customer-managed key removed from a resource, a server-side encryption configuration downgraded, or a replacement that comes back unencrypted. A key being destroyed is ranked by what depends on it, because a key with nothing behind it protects nothing.

Permissions that got broader

iam-diff

Policies are compared as the set of grants they denote, not as text โ€” so reordering statements, splitting one in two or writing a scalar as an array changes nothing. What is reported is a grant that arrived and matches one of six named reasons, which is the difference between a reviewer and a diff.

Severity is decided by rules, not by a model

The language model writes sentences. It never sees your plan, and it never picks a rung.

  • CRITICAL โ€” data will not survive this. A resource that holds data, destroyed or replaced with nothing to restore from. The only rung that sets the check to failing.
  • HIGH โ€” security posture changed. Something is reachable, or permitted, that was not before.
  • MEDIUM โ€” risky, and recoverable. A guard rail off, a recovery window closed, an audit trail narrowed.
  • INFO โ€” worth a glance before you merge. Still a security or a durability question. "Interesting" is not a rung.

When a call is genuinely close it goes down rather than up. A false positive costs a customer and a missed INFO costs nothing, and a reviewer people have learned to ignore is worth less than no reviewer at all.

Wire it in

Two lines of permissions and one step, in the workflow that already plans.

permissions:
  contents: read
  id-token: write

Then, after the plan step:

- uses: actions/checkout@v7
- uses: hashicorp/setup-terraform@v3
- run: terraform init
- run: terraform plan -out=tfplan

- uses: blastradiusapp/action@v1
  with:
    planfile: tfplan
    token: ${{ secrets.BLASTRADIUS_TOKEN }}
    api-url: https://api.blastradius.example

id-token: write is required. It is how an upload proves which workflow run made it, so that holding the repository secret is not enough to post a passing check on somebody's pull request. Nothing is uploaded without it, and the job says so.

Nothing here can fail your build. Not a wrong token, not an unreadable plan, not our API having a bad afternoon โ€” the worst any of them does is write a warning into the job summary.

What we do with your plan

The short version. The long one names the file behind every claim and ships in the repository.

  • It is redacted in your runner, before the upload. Not on our side, and not as a promise about what we do with it afterwards.
  • It is never written to a disk and never stored. It is parsed in memory, analysed and dropped. The backend names no filesystem module at all, and a test asserts that as a list.
  • What we keep is the findings, for 30 days. The resource a rule named and the attribute values it quoted โ€” enough to re-render your comment, and no more.
  • The model is handed findings, never plan JSON. Turn it off and you get the same findings with our own sentences under them.
  • Uninstalling deletes everything we hold for you. Immediately, and every table cascades from the installation row.