Deploy & secret guard

Block irreversible deploys and secret-exfiltration commands before they run.

In an infrastructure repo the costly mistakes are a terraform destroy against prod, a deleted namespace, or a secret copied into the transcript where it lands in logs forever. This page composes three primitives to stop all three on PreToolUse. Two block_command regexes catch the known-dangerous invocations; a declarative hook pairs Tool("Bash") with a SecretsExfil custom condition that checks for exfiltration substrings in plain Python. Every block carries a reason the agent reads and acts on.

"""Block irreversible deploys and secret-exfiltration commands before they run."""

from __future__ import annotations

from captain_hook import (
    Allow,
    Block,
    Event,
    Input,
    LambdaCondition,
    Tool,
    block_command,
    hook,
)

# Block irreversible infrastructure commands before they run.
block_command(
    r"terraform\s+destroy",
    reason="terraform destroy tears down live infrastructure",
    hint="Target a single resource, or run it in a sandbox workspace",
    tests={
        Input(command="terraform destroy"): Block(),
        Input(command="terraform destroy -target=module.cache"): Block(),
        Input(command="terraform plan"): Allow(),
    },
)

block_command(
    r"kubectl\s+delete\s+(namespace|ns)\b",
    reason="Deleting a namespace deletes everything inside it",
    hint="Delete the specific resource instead of the whole namespace",
    tests={
        Input(command="kubectl delete namespace prod"): Block(),
        Input(command="kubectl delete pod web-123"): Allow(),
    },
)


SecretsExfil = LambdaCondition(
    lambda evt: any(s in evt.command.raw for s in ("get-secret-value", "AWS_SECRET", "PRIVATE_KEY"))
)


# Block commands that would copy a secret into the transcript or a log.
hook(
    Event.PreToolUse,
    message="BLOCKED: this prints a secret into the transcript. Read it from your secret store at runtime.",
    block=True,
    only_if=[Tool("Bash"), SecretsExfil],
    tests={
        Input(command="aws secretsmanager get-secret-value --secret-id db"): Block(pattern="secret"),
        Input(command="env | grep AWS_SECRET_ACCESS_KEY"): Block(pattern="secret"),
        Input(command="aws s3 ls"): Allow(),
    },
)

What it catches

terraform destroy                              # tears down live infrastructure
kubectl delete namespace prod                  # deletes everything inside the namespace
aws secretsmanager get-secret-value --secret-id db  # prints a secret into the transcript
env | grep AWS_SECRET_ACCESS_KEY               # leaks a secret into logs

What it allows

terraform plan                # read-only, no infrastructure change
kubectl delete pod web-123    # scoped delete, not a whole namespace
aws s3 ls                     # no secret, no destroy

The block / allow split mirrors the hook inline tests, so it stays true as the hook evolves.

Run it yourself

uvx capt-hook --hooks docs/examples test

See also