Architecture & internals
bumper is one static Go binary with three jobs: scan a Terraform plan, scan a dependency lockfile, and gate both at an agent's tool layer. A separate hosted service — the Advisor — supplies the knowledge/CVE/malware data over HTTP. Everything below is the binary unless noted.
- Package layout
- Data flow
- Reading the plan JSON
- The hosted Advisor
- Tech stack
- Releases and provenance
- Roadmap
Package layout
cmd/bumper/ CLI: subcommand dispatch, flags, exit codes, the hook runners
internal/plan/ terraform-json -> normalized {address, type, actions, before, after}
internal/rules/ YAML loader + CEL compile (go:embed builtins by provider + --rules dir)
internal/engine/ evaluate changes × rules -> ranked findings
internal/safety/ verify + guard — the sha256-bound apply gate
internal/deps/ dependency guardrail: lockfile parsers + Advisor REST client + hooks
internal/catalog/ embedded advisory catalog (federated Trivy/Checkov/KICS/Prowler)
internal/search/ cross-corpus BM25 search over enforced + advisory
internal/report/ text / json / sarif / markdown reporters (plan + deps)
internal/enrich/ AI-CLI adapters (claude/gemini/…) — optional --explain enrichment
internal/style/ terminal color palette (truecolor + 16-color fallback, TTY/NO_COLOR aware)
internal/setup/ `bumper init` — merge-safe hook + advisor-MCP + skills wiring (Claude + Augment + Gemini)
internal/skills/ embedded SKILL.md playbooks (go:embed) — `bumper skills` get/list/install
internal/tui/ the hazard-console TUI and the init wizard
The deterministic core (plan, rules, engine, safety) never imports the
presentation or AI layers (report, enrich, tui, style) — the verdict can never depend
on a model or on rendering. That boundary is the whole trust story: the same plan always
yields the same findings. The dependency path (deps) is deterministic too — the Advisor
returns facts (advisory IDs, fixed versions); the AI insight is fetched separately, on demand,
and is never part of the pass/fail decision.
Data flow
Two independent scan paths feed a shared set of reporters and the same hook layer:
TERRAFORM
terraform show -json → internal/plan normalize each change to {address, type, actions, before, after}
internal/rules load + CEL-compile builtins (+ --rules)
internal/engine for each change × matching rule, eval when(…) → Finding; rank
internal/safety verify → verdict (sha256) ; guard → allow/deny
DEPENDENCIES
lockfile → internal/deps parse locally → POST coordinates to the Advisor /scan
internal/deps deps guard (pre-install malware block) / deps watch (post-install)
BOTH → internal/report text / json / sarif / markdown (+ internal/style colorization)
internal/search + internal/catalog sit alongside the engine: they power bumper search
and the search_rules MCP tool, spanning the enforced rules and the embedded advisory
corpus. They have no part in either scan's pass/fail verdict.
The hooks (guard, deps guard, deps watch) are thin runners in cmd/bumper over
internal/safety and internal/deps: read a tool-call payload on stdin, emit a JSON
decision on stdout, and exit 2 + stderr on a block (the universal backstop — see
agents.md).
Reading the plan JSON
The single highest-leverage thing to understand when writing rules — the shape of
terraform show -json output:
- HCL nested blocks become arrays of objects. A single
ingress { … }block arrives asafter.ingress=[{…}]. Traverse with CEL's.exists(r, …)/.all(r, …), notafter.ingress[0]orsize(...)assumptions. - Unset optional fields render
null, not absent. Alwayshas(...)-guard, and preferx != trueoverx == falsefor optional booleans (anullis neither). - Computed / "known after apply" fields render
null. A rule keyed on a value Terraform can't know yet simply won't fire — don't depend on it. beforeisnullon create;afterisnullon delete. Destruction rules (on: [delete, replace]) readbeforeand theactions, neverafter.
internal/plan flattens all of this into the five variables a rule sees (address, type,
actions, before, after); see rules.md for the authoring side. A rule that
errors on a resource is treated as "no match", so these guards are about correctness, not
just style.
The hosted Advisor
The dependency scan and the agent's knowledge lookups are backed by the Advisor — a
separate open-core service (the bumper-advisor repo, Apache-2.0), live at
advisor.bumper.sh. It serves the federated IaC catalog + a CVE/malware mirror built from
OSV, over both REST (api.md) and MCP (mcp.md). It is
lookup-not-upload: the binary sends a query or package coordinates, never your code, plan,
or state. You can self-host it and point --advisor-url (or
$BUMPER_ADVISOR_URL) at your own instance. The AI insights are the hosted instance's
value-add and aren't part of the open distribution — a self-host serves the complete
deterministic data without them. The binary degrades gracefully if the Advisor is unreachable —
Terraform scanning is fully offline and never needs it.
Tech stack
Built with Go 1.26, cel-go for predicates,
terraform-json for plan parsing, the official
MCP Go SDK, and
Bubble Tea for the TUI. Single static binary,
CGO_ENABLED=0 — clean cross-compile, fully offline for Terraform scanning, air-gap
friendly. (The Advisor is a separate open-core service — Python + Postgres/pgvector — in the
bumper-advisor repo.)
Releases and provenance
bumper takes its own supply chain seriously. Every release is checksummed, the checksum file is cosign-signed (keyless, via GitHub OIDC), and each artifact carries a SLSA build-provenance attestation — so you can prove the binary you're about to trust with your infra came from this repo's CI:
# verify the signed checksums, then the artifact against them
cosign verify-blob \
--certificate checksums.txt.pem --signature checksums.txt.sig \
--certificate-identity-regexp 'https://github.com/gnana997/bumper/.*' \
--certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
checksums.txt
sha256sum -c checksums.txt --ignore-missing
# or verify the build provenance directly
gh attestation verify bumper_*_linux_amd64.tar.gz --repo gnana997/bumper
The install.sh / get.bumper.sh installer verifies the sha256 against checksums.txt
before installing. Dependencies are watched by Dependabot + govulncheck (bumper also scans
its own go.sum in CI), and the code by CodeQL. Report vulnerabilities via private disclosure
— see SECURITY.md.
Roadmap
Shipped and live: the Terraform apply gate, the dependency guardrail (vulnerable + malicious), the hosted Advisor (MCP + REST), and agent wiring for Claude Code, Augment, and Gemini CLI. Next:
- More agents — Codex next. Hook contracts differ only in the deny envelope / event names
(Gemini's
BeforeTool/AfterToolvs Claude'sPreToolUse/PostToolUse); the exit-2 + stderr backstop already covers basic blocking for any agent that honors exit codes. - Grow the multi-cloud rule set from the embedded advisory catalog
(internal/catalog/, rebuilt with
make catalog) — port high-value intents into enforced CEL rules. - Reachability beyond a single security group (SG → ENI → public subnet → IGW), to rank "actually reachable from the internet" above config-only exposure.
- A continuous, read-only account-posture watcher (the stateful tier) — distinct from this single-shot plan gate.