Documentation
¶
Overview ¶
Package safety implements bumper's enforcement layer: verifying a saved Terraform plan (binding a passing scan to the exact plan file by sha256) and guarding `terraform apply`/`destroy` as a Claude Code PreToolUse hook, so an agent cannot apply a plan nobody verified.
The deterministic core (engine/rules/plan) never imports this package; this is the outermost shell.
Index ¶
Constants ¶
const DefaultMaxAge = 24 * time.Hour
DefaultMaxAge is how long a verdict stays valid; past this, guard asks for a re-verify (the plan may predate drift). 0 disables expiry.
const DefaultMinSeverity = "high"
DefaultMinSeverity is the threshold at or above which a finding blocks an apply (and fails `verify` unless --accept is given).
Variables ¶
This section is empty.
Functions ¶
func ResolvePlanData ¶
ResolvePlanData turns a plan reference into `terraform show -json` bytes. Inline JSON is used as-is; a path that already holds JSON is read directly; any other path is treated as a binary plan and run through `terraform show -json`. source is a label describing which path was taken.
func Sha256File ¶
Sha256File returns the lowercase hex sha256 of a file's contents. This is the binding key: the verdict for a plan is stored under the hash of the exact bytes that `terraform apply <plan>` will consume.
Types ¶
type Decision ¶
Decision is guard's verdict on a tool call. A zero Decision means "no opinion" — guard stays silent and defers to Claude Code's normal permission flow.
func Decide ¶
Decide inspects a Bash tool call and blocks unverified terraform apply/destroy. Any other tool or command yields a zero (silent) Decision, which is what makes guard safe to install as a global, always-on hook.
func Guard ¶
func Guard(r io.Reader, w io.Writer, shellTool string, now time.Time, maxAge time.Duration) (Decision, error)
Guard reads a PreToolUse payload, decides, and writes a deny decision (if any) as the hook's JSON output. It is fail-open on malformed input: a bumper bug must never wedge the user's shell. The decision to block is conveyed purely shellTool is the host agent's shell-execution tool name (e.g. "Bash" for Claude Code, "launch-process" for Augment); any other tool yields a silent allow.
It writes the JSON deny decision to w and RETURNS the Decision, so the caller can also surface a block via stderr + exit 2 — the universal backstop every agent honors (and the only mechanism Gemini accepts, since it ignores the JSON).
type HookInput ¶
type HookInput struct {
HookEventName string `json:"hook_event_name"`
ToolName string `json:"tool_name"`
CWD string `json:"cwd"`
WorkspaceRoots []string `json:"workspace_roots"`
ToolInput struct {
Command string `json:"command"`
} `json:"tool_input"`
}
HookInput is the subset of a PreToolUse stdin payload we read. Claude Code provides `cwd`; Augment instead provides `workspace_roots` (an array, no cwd), so we read both and resolve via WorkDir.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is a directory of verdict files keyed by plan sha256.
func StoreForPlan ¶
StoreForPlan returns the verdict store rooted at the plan file's directory (<plan-dir>/.bumper/verified). Both verify and guard derive the store this way from the same plan path, so the lookup is location-independent.
type Verdict ¶
type Verdict struct {
PlanSHA string `json:"plan_sha"`
PlanFile string `json:"plan_file"`
Verdict string `json:"verdict"` // "pass" | "accepted"
MinSeverity string `json:"min_severity"`
FindingsTotal int `json:"findings_total"`
Blocking int `json:"blocking"` // count of findings at/above the threshold
Accepted bool `json:"accepted"` // true when recorded via --accept despite blocking findings
BumperVersion string `json:"bumper_version"`
VerifiedAt time.Time `json:"verified_at"`
}
Verdict is the record written when a plan passes (or is explicitly accepted). It binds a scan result to the exact plan bytes via PlanSHA.
type VerifyResult ¶
type VerifyResult struct {
Verdict Verdict
Findings []engine.Finding // all findings (not just blocking ones)
Blocking []engine.Finding // findings at/above the threshold
Passed bool // true if no blocking findings, or accepted
}
VerifyResult is the outcome of verifying a plan.
func Verify ¶
func Verify(set *rules.Set, planPath, minSeverity string, accept bool, now time.Time) (VerifyResult, error)
Verify scans the plan at planPath and decides whether it may be applied. On a pass (no blocking findings) — or when accept is true despite blocking findings — it writes a verdict bound to the plan's sha256 and returns Passed=true. It does not write a verdict on a hard fail.
now is injected so callers/tests control the timestamp.