Skip to content
somaz94Public

About

See what your AI agent's infrastructure command would actually destroy before it runs: a Claude Code hook that reads Terraform plans and kubectl state

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

whatdies

CI License: MIT

See what your AI agent's infrastructure command would actually destroy, before it runs.

whatdies gives the same kubectl command opposite verdicts as the volume's reclaim policy changes, blocks that ask under Codex, refuses a helm uninstall that deletes a volume and a terraform apply with no saved plan, and asks before a plan sets force_destroy on a bucket

Same command, opposite verdict. A guard that reads the command text cannot tell these apart, because the text is identical:

$ whatdies check "kubectl delete ns shop"
DENY
  deletes namespace shop on kind-whatdies-demo with 1 pod(s), 0 deployment(s), 0 statefulset(s), 1 PVC(s)
  PVC data -> PV pvc-da35d82e (1Gi) reclaim=Delete: the volume and its data are deleted

# reclaim policy changed to Retain; the command is identical
$ whatdies check "kubectl delete ns shop"
ASK
  deletes namespace shop on kind-whatdies-demo with 1 pod(s), 0 deployment(s), 0 statefulset(s), 1 PVC(s); its volumes have reclaim=Retain, so they outlive it

And the opposite trap, a command that looks routine:

$ whatdies check "terraform apply tfplan"
DENY
  replaces aws_db_instance.main (aws_db_instance holds data)

whatdies is a single Go binary that runs as a PreToolUse hook for Claude Code or Codex. It needs no API key and sends nothing anywhere: it reads your saved Terraform plan and asks your own kubectl read-only questions.


Why

Coding agents have run terraform destroy against production and taken the database and its snapshots with it (AI Incident 1424). The developer in that case watched the command go by and did not stop it. An approval prompt only helps if the person can see what the command does, and terraform apply does not say.


Install

brew install --cask somaz94/tap/whatdies
whatdies install --claude

install adds one hook to ~/.claude/settings.json, keeping everything else in the file and a backup beside it. Use --project for ./.claude/settings.json, and whatdies uninstall --claude to remove it.

For Codex, whatdies install --codex adds it to ~/.codex/hooks.json (or $CODEX_HOME, or ./.codex/hooks.json with --project). Before writing anything, it runs the hook through your shell, plainly and as a login shell, and refuses if a startup file would stop it from blocking. Codex runs a new hook only after you trust it in /hooks, and skips it until then; whatdies uninstall --codex removes it, and Windows is not supported for Codex yet. A Codex hook cannot ask, since Codex runs a command whose hook answers ask, so there every ask becomes a deny and the agent is told to let you run the command yourself.

If the binary moves or is deleted, every shell command is blocked until you reinstall or remove the hook. That is on purpose: a hook that fails open is not a guard.

Try it without installing the hook:

whatdies check "terraform apply tfplan"

Exit codes are 0 no decision, 3 ask, 4 deny.


What it does

Command How it decides Result
terraform apply <planfile> Reads the saved plan file itself, without running terraform Deletes or replaces a resource that holds data: deny. Any other delete or replace, or an update that turns off a guard such as force_destroy: ask. Otherwise no decision
terraform apply, terraform destroy with no saved plan Nothing shows what they would do Deny, and tells the agent to save a plan first
kubectl delete of a namespace, PVC or PV Looks up the volumes behind it reclaim=Delete: deny. Retain or no volumes: ask
kubectl delete of a StatefulSet Reads its PVC retention policy Claims deleted with it and reclaim=Delete: deny. Otherwise ask
kubectl delete of a CRD Counts its instances Ask
helm uninstall, helm rollback Reads the release's revisions and looks up the volumes it owns A volume with reclaim=Delete it would remove: deny. Otherwise ask
helm upgrade, drain, state rm, --prune, --replicas=0 and similar The verb alone Ask
A command it cannot see into: eval "$X", curl ... | bash, kubectl delete pod $POD The shape of the command Ask

In a terminal it colours the verdict and wraps long lines; --color never turns colour off, NO_COLOR is honoured, and --json prints the verdict for a script. Commands that are not infrastructure get no decision, in about two milliseconds. The rules, and how each one is decided, are in docs/verdicts.md.

It never answers allow. It can only add friction, so your normal permission rules still apply to everything it lets through.

Claude Code's bypassPermissions mode does not switch it off: in a headless run under that mode, both a deny and an ask stopped the command. An ask in an interactive session under that mode has not been tested.


How it stays out of trouble

A guard that runs commands on an agent's behalf is itself a risk, so:

  • It does not run terraform plan. That would execute agent-written code, such as a data "external" block, before you approve anything. The agent runs the plan through your normal permissions, and whatdies reads the file. Terraform itself refuses a saved plan that has gone stale.
  • Against a cluster it only calls kubectl get. The one other call, kubectl config current-context, reads the local kubeconfig and contacts nothing. Helm releases are read the same way, from their release Secrets. A server-side dry run of the delete needs delete rights, shows no cascade, and is one flag away from a real delete.
  • It will not probe a target the command chose. With --kubeconfig, --server, an inline KUBECONFIG= or ./kubectl, a credential plugin could run code the agent wrote, so it asks without looking.
  • Plan values stay out of messages. A plan holds secrets in clear text, so only addresses, types, actions and the guard attributes listed in guards.yaml are decoded.
  • It fails closed. A timeout, a crash, missing credentials or an unreadable plan becomes ask.

Limitations

This is a seatbelt for a mistaken agent, not a sandbox. It does not stop an agent trying to get around it.

  • Commands built at run time can get through. Custom shell functions, a tool name assembled from variables, and scripts more than one level deep are not read. Run an agent you do not trust in a real sandbox.
  • It covers Terraform, kubectl and Helm. A direct aws rds delete-db-instance, DROP DATABASE or rm -rf is out of scope. Run a text-matching guard such as destructive_command_guard alongside it. If that guard refuses terraform plan -destroy, which whatdies recommends before a destroy, allow it there.
  • MCP tools are not checked. The hook sees shell commands only.
  • PowerShell commands are read as bash. Claude Code's PowerShell tool is checked, but with a bash parser. PowerShell syntax such as & terraform ..., Invoke-Expression or Start-Process turns a deny into ask, and a tool name PowerShell assembles at run time is not seen.
  • A plan format whatdies does not know weakens deny to ask. It decodes the saved plan itself, so no terraform or provider binary runs before you approve. Anything in the file it does not recognise, such as a field a newer Terraform adds, makes the verdict ask rather than a guess.
  • A plan file can be swapped between the check and your approval of the prompt.
  • The list of resource types that hold data is a judgement call. It lives in stateful.yaml, and a type missing from it still gets ask.
  • Codex gives whatdies less to hold on to.
    • A hook that times out lets the command run, since Codex has no setting that blocks on a failed hook. The installed command turns a crash or a missing binary into a block, and whatdies stops itself well before the timeout, but Codex killing it cannot be caught.
    • Input typed into a session that is already open, such as bash, python3 or ssh host started with a terminal, reaches no hook.
    • The agent can pick the shell a command runs in, which the hook is not told; whatdies reads the command as bash.
    • In a remote Codex environment the hook still runs on your machine, so plan files and the kube context it reads are local ones.
    • Another hook that rewrites the command can change it after whatdies has checked it.
  • Claude Code and Codex only so far. Gemini CLI, helm upgrade analysis and OpenTofu plans are planned. Codex support is checked against Codex's documented hook rules and its source, not yet with a live Codex agent.

Try the demo

Everything above comes from examples/demo/demo.sh, against a throwaway kind cluster and a Terraform config with no cloud account. It uses its own temporary kubeconfig, so your current context is not touched.

make build && examples/demo/demo.sh

Development

See docs/DEVELOPMENT.md.


Related projects

  • agentic-workflow-guard: Audits GitHub Actions workflows that run AI agents for prompt-injection exposure.
  • agentport: Converts Claude Code skills, slash commands and subagents to OpenAI Codex and Google Antigravity formats.

License

MIT

About

See what your AI agent's infrastructure command would actually destroy before it runs: a Claude Code hook that reads Terraform plans and kubectl state

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages