The audit ntfy notifier is dark in production — BRAIN_NTFY_URL is unset on ingestion #100
Closed
opened 2026-08-22 06:22:50 +00:00 by mathias
·
2 comments
No Branch/Tag Specified
main
fix/pipeline-writes-158
fix/edit-path-io-192
fix/create-regular-188
fix/guard-special-179
fix/linkcheck-keeps-write-178
fix/write-dir-redirect-173
fix/tunnel-detect-174
fix/pipeline-root-157
w3/154-mcp-auth-wiring
fix/tunnel-traversal
test/in-brain-targets-171
fix/brain-scan-root-157
fix/graph-wiki-root-157
fix/vectorstore-root-157
fix/notes-root-157
fix/search-root-157
fix/brainwide-root-157
fix/raw-symlinks-153
feat/symlink-tripwire-157
fix/rest-auth-149
fix/123-brain-edit-envelope-check
feat/106-page-aware-extraction
fix/105-source-drift-on-main
fix/104-surface-merge-conflicts-on-main
agent/b1-compile-on-ingest
agent/104-surface-merge-conflicts
agent/105-source-drift
agent/107-loud-unconfigured-alerting
feat/brain-read-edit
fix/capture-i1-refusal-names-target
renovate/configure
feat/consolidate-single-harness
icebox/cmd-routing
feat/brain-pending-promote
fix/close-session-classification-refresh
fix/capture-summarywriter
v0.24.0
v0.23.1
v0.23.0
v0.22.0
v0.21.1
v0.21.0
v0.20.1
v0.20.0
v0.19.1
v0.19.0
v0.18.4
v0.18.3
v0.18.2
v0.18.1
v0.18.0
v0.17.0
v0.16.1
v0.16.0
v0.15.0
v0.14.0
v0.13.2
v0.13.1
v0.13.0
v0.12.1
v0.12.0
v0.11.2
icebox/cmd-routing-2026-07-01
v0.11.1
v0.11.0
v0.10.0
v0.9.0
v0.8.0
v0.7.0
v0.6.0
v0.5.0
v0.4.1
v0.4.0
v0.3.0
v0.2.0
v0.1.0
Labels
Clear labels
agent/ready
blocked/client
blocked/supplier
blocked/upstream
risk/high
risk/low
risk/medium
size/l
size/m
size/s
size/xs
status:dispatch
status:dispatched
status:review
status:reviewing
type/chore
type/decision
type/docs
type/feat
type/fix
type/ops
type/research
type/test
Meets the agent-ready contract; authorizes unattended execution
Waiting on the client
Waiting on a supplier
Waiting on an upstream project or vendor
Irreversible, security-relevant, or control plane
Reversible, local, test-covered
Touches shared state, deploys or data
Too big - split it
Under two days
Under half a day
Under an hour
Maintenance, no behaviour change
Human decision required; never agent/ready
Documentation only
New capability
Corrects broken behaviour
Infrastructure, deploy, runtime
Investigate; output is a finding, not code
Test coverage only
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: mathias/hyperguild#100
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found by live-verifying #97 against production at
v0.15.0, which is exactly what that verification was for.What is broken
buildNotifier()returns a nil interface whenBRAIN_NTFY_URLis empty, and every consumer skips a nil notifier silently — by design, so an unconfigured homelab does not error. Theingestiondeployment setsBRAIN_LOKI_URLbut notBRAIN_NTFY_URL:So two alerts that look shipped are not:
brain_edit_rootcalls landed both audit records in loki with correct before/after hashes, and produced no notification. The audit half works; the alert half is dark.DegradingSink.Recordfires the "audit BUFFERED LOCALLY (loki unreachable)" notification through the same nil notifier. That is the alert whose entire job is to tell you the central audit trail is down, and it has never been able to fire. Pre-existing; #97 only made it visible.#97's premise — "brain-sync already posts to ntfy on failure, so the wiring and topic exist" — was true of brain-sync, a different component with its own config. The
ingestiondeployment never had it.DECISION NEEDED — which topic, and how does it authenticate?
ntfy-acl-bootstrap.yamlalready provisions candidates, and the estate has precedent in both directions:ntfy access everyone brain-audit wo(or a newbrain-governancetopic) plus one env var. Cheapest, and consistent with the last two decisions of this shape.brain-audituser. Already haswoonbrain-audit, but authenticates via a standalone access token living in brain-gardener's gitea secret — so this means cross-namespace credential plumbing intosupervisor. The same file flags that user as created ad-hoc and never codified ("exactly the evaporation anti-pattern infra#40 warned about").Recommendation: A, with a dedicated
brain-governancetopic rather than reusingbrain-audit. A governance rewrite is rare and must be read; audit-degradation alerts are operational noise by comparison. Mixing them on one topic is how the rare one gets muted. That does mean two env vars eventually, or accepting that both shareBRAIN_NTFY_URLuntil the notifier takes a per-event topic.Not acting on this unilaterally — it widens write access on a shared notification service and picks a topic taxonomy.
Acceptance
caveman: alarm actually make noise when someone change rulesBRAIN_NTFY_URLset on theingestiondeployment via the infra repo (GitOps, notkubectl edit)ntfy-acl-bootstrap.yaml— codified, not applied ad-hocbrain_edit_rootproduces a notification on a phone, containing path + both hashes and no file contentRisk: LOW to fix. The current state is the risk — a security control whose alert cannot fire, and an outage alert that cannot report the outage.
Refs #97, #96,
ingestion/cmd/server/main.go(buildNotifier),internal/audit/degrading.go, infrak3s/apps/monitoring/ntfy-acl-bootstrap.yaml.Decided and built: option A, one topic — infra PR #297
Taking the DECISION NEEDED on the "keep it simple, keep it robust" principle.
Anonymous write-only to a dedicated
brain-alertstopic. Not thebrain-auditcredential plumbed cross-namespace.ntfyis ClusterIP-only, so only in-cluster pods can reach it — the same reasoning already recorded inntfy-acl-bootstrap.yamlfor brain-sync. Option B additionally depends on a user that file itself flags as created ad-hoc and never codified.One topic, not two, which reverses the taxonomy I proposed earlier. Both alerts are rare, and a second env var is a second thing to forget — which is precisely the failure being fixed here. Still deliberately off
brain-audit: a rare "someone rewrote the rules" alert must not share a channel with routine gardener digest traffic, or it gets muted along with it.The ACL grant is codified in the bootstrap script rather than applied by hand.
This issue does not close on merge
Config is not delivery — that is the entire lesson here. After Flux reconciles:
BRAIN_NTFY_URLpresentbrain_edit_root→ a notification actually arrives on your phone (path + both hashes, no content)Only step 2 closes this. I can drive 1 and 3; step 2 needs you to confirm the message landed, since the failure mode is silence and I cannot observe your phone.
Worth its own issue
buildNotifier()returning nil silently is right for an unconfigured homelab and wrong for a security-relevant alert. A loud-on-unconfigured startup line —governance alerting DISABLED: BRAIN_NTFY_URL unset— would have made this a five-second discovery instead of a live-fire test. That is a code change in this repo; say the word and I'll file it.Confirmed received. Closing.
A real
brain_edit_rootonCLAUDE.mdat 21:28 local (a601ab7f → 9d0056f9) produced a notification that arrived on the phone. That was the one step nobody in this chain could verify but Mathias, and it is the step that decides whether the issue is fixed.Full chain now verified end to end, each hop with its own evidence rather than inference:
WARN brain_edit_root applied principal=local-cliactor: brain_edit_root,items: [CLAUDE.md], before/after hashes chainedBRAIN_NTFY_URL=…/brain-alertson the running podPOST /brain-alerts403 → 200 after theacl-revisionbumpnotify failedin the pod log, i.e.Notifyreturned nilTwo corrections worth keeping on the record
The first "confirmation" was the wrong topic. A
controlmessage seen at 10:59 was mine, posted tobrain-sync-alert— the known-good control. It proved the mechanism worked and said nothing aboutbrain-alerts. Nearly closed this on it.The ACL grant needed more than the ConfigMap edit. The bootstrap runs as an initContainer, and the deployment's own annotation must be bumped to re-run it. Editing the ConfigMap changed the script, not the state it had produced — so the grant sat inert on a 10-day-old pod and anonymous publish returned 403. Caught by testing the publish path rather than reading the manifest; fixed in infra
dfc5940.Both are the same shape as the bug this issue is about: configuration present, effect absent.
Left open deliberately, filed separately
ntfy's topic history returns empty even as admin with
since=all, despite a PVC and a cache file being configured — so the phone is currently the only observer of this channel, and an alert fired while it is offline cannot be recovered or audited after the fact. Unexplained, and not something to close this issue over; filed on its own.The startup warning that would have made this a five-second discovery instead of a live-fire test is #107, shipped in PR #108.