Águia fleet · re-audit on Fable · 24 Aug 2026

Re-audit: what yesterday's pass missed

Yesterday's six-auditor sweep (on Opus) shipped 8 fixes. This pass re-checked those fixes and the fleet with fresh eyes. It found that two of the flagship fixes had latent bugs — one silently deleting the very task it promised to protect — plus an internet-exposed unauthenticated code-execution path and a whole second scheduler that a crontab-only audit could not see. Three closed today and verified live; the rest are ranked below.

1unauth RCE path closed (internet-exposed)
10fixed & verified live
2of yesterday's own fixes had bugs
9systemd timers invisible to a crontab audit

Verdict

Yesterday's work was real — the money pipeline, the priority-aware prompt shedding, the disabled fake heartbeats all hold. But three of its conclusions were incomplete in ways that matter: the prompt-shedding fix reintroduced silent deletion of the task at the tail; the WhatsApp hardening closed the fromMe spoof but left the identical member-JID spoof open as unauthenticated RCE; and every scheduling and liveness conclusion was drawn from crontab -l while a second scheduler (systemd user timers) ran the same agents in parallel, unseen.

None of this is a criticism of shipping fast under fire — it is exactly what a second model on a second day is for. The pattern to take away: a fix verified by a happy-path smoke test is not verified. Both buggy fixes passed their Aug 23 check and were failing in production the whole time.

Closed today — verified live

P0

WhatsApp webhook — unauthenticated remote code execution, closed

Yesterday's fix made fromMe authority require a signature. But builder_authorized() and wiki_capture_authorized() have a second branch — member JID — that granted full build / brain-write authority with no signature check at all. The port is internet-reachable (ufw ALLOW 8086 Anywhere, bound 0.0.0.0), missing signatures are accepted (only a wrong one is 401'd), and participant is read straight from the unsigned body. Anyone who knows the one builder-member's phone number could POST an unsigned request spoofing that JID and [[BUILD]] an arbitrary agent — arbitrary code, deployed as ubuntu — or [[WIKI]]-poison the fleet brain every agent then reads as instruction. Wiki-capture is on by default; a real build launched through this handler earlier the same day.

Fixed fail-closed: no authority is ever granted on an unsigned request, for either branch. All legitimate traffic is relayed by WASender, which signs every delivery, so nothing real breaks — only the direct forged POST is refused, and logged.

Unit-tested 7 cases before deploy, then restarted the live service: unsigned member-spoof build → REFUSED unsigned member-spoof [WIKI] → REFUSED signed member build → allowed signed fromMe build → allowed non-member signed → refused Surfaced by the parallel audit team; independently verified against the code and closed here.
HIGH

Prompt-shedding was deleting the Current Task — yesterday's flagship fix had a tail-cut bug

The Aug 23 rewrite of context-budget.py protects Current Task and Standing Corrections while shedding — but it appended its "what was dropped" note after trimming to exactly --max. So every truncated run overflowed by the note's length and fell into Pass 2's blind out[:max-90] head-cut. Because ## Current Task is assembled last, that head-cut clipped or deleted the task itself — the same silent-deletion the rewrite was built to kill, moved to the tail. The production log proves it fired on nearly every heavy run: outputs landing at exactly budget − 49/50 chars is the hard-cap's own signature.

Before — Aug 23 fix, live
Same 118 KB overflow prompt
Current Task headingDELETED
Task's final instructionDELETED
Output sizebudget − 50
After — today's fix, live
Same 118 KB overflow prompt
Current Task headingKEPT
Task's final instructionKEPT
Output sizeunder budget

Fixed by reserving the note's budget up front (monotone, no oscillation), charging each trim marker against its own cut, and replacing the blind tail-cut with a tail-safe last resort — the char budget is well inside the model window, so a bounded overrun that keeps the task intact beats slicing it off. Confirmed on the box: old backup deletes the task sentinel, new code preserves it.

HIGH

The usage breaker never tripped on the weekly limit — the Aug 22 fleet-day loss

The dispatch backoff breaker matched out of extra usage, usage limit reached and similar, but not the actual banner Claude Code emits: "You've hit your weekly limit · resets 4am (UTC)" — six of them in the logs. So on a weekly exhaustion the breaker never stamped, the fleet kept firing every cron into a dead window, and it never routed to the grok fallback. Yesterday's report named this in prose but never patched the regex. Both breaker sites now match the weekly phrasing (anchored, so an agent merely mentioning "weekly limit" doesn't false-trip), which both stops the hammering and enables the fallback.

Real banner now trips the breaker → routes to grok; benign "weekly limit of 1000 calls" → ignored. bash -n clean.

Every changed file has a timestamped .bak beside it: context-budget.py.bak-20260824-passcut, dispatch.sh.bak-20260824-weeklylimit, wa-webhook-handler.py.bak-20260824-memberauth.

Follow-up pass — fixed on request

After the first three, the rest of the safely-fixable set was closed too. Everything here is live, compiled/verified, with a timestamped .bak.

✓

The nightly paging is fixed — cadence is now the real gap, not the average

The reconciler set expected_cadence_minutes = 1440 / runs_per_day (the average interval) and counted helper crons that never emit a heartbeat, so clustered schedules crossed "offline" every night and weekend. It now computes the max gap between heartbeat-producing firings over a week, crediting only crons that actually reach dispatch.sh.

falcao offline-threshold 13h → 142h · condor → 246h · cb-sales → 216h · arara → on-demand (systemd-dispatched) Ran live; second dry-run shows drift=0. Added a retire-orphans sanity floor (refuse if local empty or >50% orphaned).
✓

The dashboard's false-offline banner is gone

command_center.py now uses the same cadence rule as the rest of the API and only flags a genuine offline (idle and stale are not failures). runs.py records an ingest with no status as unknown, not a silent ok.

Verified live after deploy: agent-offline insights 31 → 0; status {online:1, offline:31} → {online:24, idle:8}.
✓

The money-server can no longer load into an untrusted-email call

crm-reply-responder.py, crm-sequence-tick.py and llm_dispatch_server.py carried a stale 10-tool deny-list and no --strict-mcp-config. All three now match the WhatsApp handler's full 21-tool list plus strict-mcp-config (and llm-dispatch gained --max-turns 1). llm-dispatch restarted, health OK.

✓

A failed CRITICAL alert is no longer deduped away

notify.sh recorded the dedup key before sending, so a failed tier-0 alert was suppressed for 6h. It now records the key only after delivery, and never on a failed send.

The blind spot: a second scheduler

Yesterday's scheduling and liveness findings were all drawn from crontab -l. But 9 systemd --user timers dispatch the same fleet in parallel and appear in no crontab. Proof: arara was dispatched twice today, yet crontab -l | grep arara returns nothing — it runs entirely off aguia-arara.timer.

Yesterday's other fixes — where they're incomplete

FixStatus
cadence livenessFixed — cadence now max-gap over heartbeating crons; the nightly/weekend false pages are gone (see follow-up pass above).FIXED
dashboard statusFixed — command_center.py migrated to the cadence rule; 31 false "offline" warnings → 0, verified live.FIXED
RCE fencesFixed — crm-reply-responder.py, crm-sequence-tick.py and llm_dispatch_server.py now carry the full deny-list + --strict-mcp-config; mcp__bitpanda can no longer load.FIXED
reconcilerRetire floor added. Still open: the aguia row (id 0) never gets a cadence but is stamped online every minute by the box heartbeat, so orchestrator death is invisible while the box lives. Low urgency, needs a tmux-aware heartbeat.PART
dispatch.sh:933The universal path still runs bypassPermissions with no --disallowed-tools. Held deliberately — a blanket Bash ban breaks wiki-remember and memory writes; this needs the audit-mode rollout yesterday's report itself recommended (log would-deny for a week, then enforce).HELD

Systemic holes — named yesterday, still open, with new instances

No trust boundary on the universal dispatch path

Yesterday counted 103 tool-less call sites. The one that matters most — dispatch.sh:933, which every agent runs through — carries --permission-mode bypassPermissions and no --disallowed-tools. arara (inbound email), tucano (scraped transcripts), falcao (scraped social) and the outreach agents (inbound replies) all feed untrusted text into a full-Bash process. And there is zero fencing on the wiki→prompt path: a poisoned sentence in one scraped article is compiled into the brain and injected verbatim as trusted instruction into every agent on the next run. This is a live second-order injection channel, not a hypothetical.

Delivery paths declare success without checking

Against your own "fail hard over silent fallback" rule: instagram-dm-send.py, linkedin-dm-v3.py and falcao-x-media-post.py each log SUCCESS / Posted after pressing Enter, with no post-condition check. The LinkedIn one returns success unconditionally and writes the result into outreach-ledger.jsonl — the same ledger that gates one-contact-per-person — so a UI change turns the whole DM pipeline into a log of phantom sends.

More monitors that fail toward green

SignalWhy it can't move
run success rateruns.py:197 records an ingest with no status/exit info as "ok" — unknown counted as success.
Instagram tileconnected:true because a token row exists; the token expired 24 May.
Telegram ConversationsFrozen at 19 Jul; the */5 sync "succeeds" syncing an inbox the plugin stopped writing to 5 weeks ago.
crons/overduePredicate reads next_run, which has no writer → always []; a dead cron can never be overdue.
notify.shRecords "already notified" before sending → a failed CRITICAL alert is deduped away for 6h.
agent_messagesMarked acknowledged the moment they're returned in a heartbeat body the caller discards — read status asserts a read that never happened.

Backups still have holes

Still open from yesterday — confirmed unfixed, need you