sec(api): strip /healthz filename + drop env-var names from public 401s (API-21/90/217)#179
Merged
mastermanas805 merged 2 commits intoMay 29, 2026
Conversation
…1s (API-21/90/217)
BUG-API-090 + BUG-API-217 — /healthz is anon-reachable and emits the
raw migration filename, e.g. "063_forwarder_sent_audit_link.sql". That
literally tells an attacker which feature shipped at which migration
number — recon a public probe should not provide. Add
migrations.State.PublicVersion() that strips to the numeric prefix
("063"). Canaries keep the commit_id+count+version drift tuple,
attackers learn nothing about the schema. migration_count and
migration_status unchanged.
BUG-API-021 (plus siblings on webhook_secret_mismatch and
webhook_signature_mismatch) — the public 401 envelope returned by
/webhooks/brevo/<wrong-secret> literally named the BREVO_WEBHOOK_SECRET
env var in agent_action. Same recon-leak problem: anyone probing the
public URL learned the exact env-var name. Drop env-var vocabulary
from all three webhook-config error agent_actions; point at the docs
page where the operator-side rotation procedure lives.
Wire contract preserved: error codes, statuses, messages unchanged —
only agent_action sentences are softened. No new endpoints, no new
fields, no auth changes.
Surface checklist (rule 22):
- api/internal/migrations/state.go — PublicVersion helper + Strings import
- api/internal/router/router.go — /healthz emits PublicVersion()
- api/internal/router/healthz_test.go — pinned shape updated + new regression
- api/internal/handlers/helpers.go — 3 agent_action sentences softened
- api/internal/handlers/brevo_webhook_test.go — explicit assertion that BREVO_ env names never reach wire
- OpenAPI / dashboard / marketing — no surface change (envelope shape unchanged)
Coverage block:
Symptom: /healthz.migration_version "063_forwarder_sent_audit_link.sql"; brevo 401 names BREVO_WEBHOOK_SECRET
Enumeration: rg -F 'BREVO_WEBHOOK_SECRET' internal/handlers/helpers.go ; rg -F 'mstate.Filename' internal/router/
Sites found: 3 agent_action strings + 1 router emit site
Sites touched: 4 / 4
Coverage test: TestHealthzMigrationVersionStripsFilenameSuffix (asserts no '_' or '.sql' in output across 7 cases); brevo_webhook_test BREVO_* + BREVO_WEBHOOK_SECRET assertion
Live verified: pre-merge: curl https://api.instanode.dev/healthz returned "063_forwarder_sent_audit_link.sql"; curl https://api.instanode.dev/webhooks/brevo/x returned "BREVO_WEBHOOK_SECRET" in agent_action.
post-merge: verify in PR comment.
Local gate:
- go build ./... PASS
- go vet ./... PASS
- go test ./internal/migrations/ PASS
- go test ./internal/router/ PASS
- go test -run 'Brevo' ./internal/handlers/ PASS
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The package-external test in router_test exercises PublicVersion but diff-cover attributes coverage by package, so the migrations/state.go PublicVersion body landed at 0% for diff-cover. Add a package-internal test (state_public_version_test.go) iterating the same 7 cases so the patch-coverage gate sees migrations/state.go at 100%. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two recon-leak fixes on public anon-reachable surfaces.
BUG-API-090 / BUG-API-217 — /healthz migration_version filename leak
Before:
That literally tells an attacker which feature shipped at which migration number. An anon-reachable probe must not provide that recon. Add
migrations.State.PublicVersion()that strips to the numeric prefix only ("063"). Canaries keep thecommit_id+migration_count+migration_versiondrift tuple; attackers learn nothing about the schema.BUG-API-021 (+ siblings) — webhook 401 envelope literally named env vars
Before:
```json
"agent_action": "...Operators must verify the Brevo dashboard webhook URL matches the configured BREVO_WEBHOOK_SECRET..."
```
The public 401 returned by `/webhooks/brevo/` — reachable by any brute-forcer — named the exact env-var key. Soften 3 agent_action sentences (`brevo_secret_mismatch`, `webhook_secret_mismatch`, `webhook_signature_mismatch`). Point at the docs page where operator-side rotation lives.
What did NOT change
Surface checklist (rule 22)
Coverage block
```
Symptom: /healthz.migration_version "063_forwarder_sent_audit_link.sql";
brevo 401 agent_action names BREVO_WEBHOOK_SECRET
Enumeration: rg -F 'BREVO_WEBHOOK_SECRET' internal/handlers/helpers.go
rg -F 'mstate.Filename' internal/router/
Sites found: 3 agent_action strings + 1 router emit
Sites touched: 4 / 4
Coverage test: TestHealthzMigrationVersionStripsFilenameSuffix (asserts no '' or '.sql'
in output across 7 cases — would fail if a future refactor reverts to
emitting filename); brevo_webhook_test asserts no BREVO* in body.
Live verified: pre-merge above (curl evidence shown)
```
Local gate
Live verify plan (post-merge)
```bash
curl https://api.instanode.dev/healthz | jq .commit_id # must match merge SHA
curl https://api.instanode.dev/healthz | jq .migration_version # must be "063"
curl https://api.instanode.dev/webhooks/brevo/x -X POST | jq .agent_action # must NOT contain BREVO_
```
🤖 Generated with Claude Code