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.
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.
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.
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.
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.
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.
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.
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 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.
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.
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.
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.
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.
clawfix runs ~5×/day and arara
3×/day once both sources are counted; keepalive and fleet-metrics each run
twice as often as intended.acp-heartbeat.timer still POSTs
status:"online" every 5 min — for the box row only (agent 0), so not the old
"everything green" disease, but it carries a hardcoded bearer token in a world-readable
script (~/.openclaw/acp-heartbeat.sh, mode 775).| Fix | Status | |
|---|---|---|
| cadence liveness | Fixed — cadence now max-gap over heartbeating crons; the nightly/weekend false pages are gone (see follow-up pass above). | FIXED |
| dashboard status | Fixed — command_center.py migrated to the cadence rule; 31 false "offline" warnings → 0, verified live. | FIXED |
| RCE fences | Fixed — 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 |
| reconciler | Retire 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:933 | The 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 |
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.
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.
| Signal | Why it can't move |
|---|---|
| run success rate | runs.py:197 records an ingest with no status/exit info as "ok" — unknown counted as success. |
| Instagram tile | connected:true because a token row exists; the token expired 24 May. |
| Telegram Conversations | Frozen at 19 Jul; the */5 sync "succeeds" syncing an inbox the plugin stopped writing to 5 weeks ago. |
| crons/overdue | Predicate reads next_run, which has no writer → always []; a dead cron can never be overdue. |
| notify.sh | Records "already notified" before sending → a failed CRITICAL alert is deduped away for 6h. |
| agent_messages | Marked acknowledged the moment they're returned in a heartbeat body the caller discards — read status asserts a read that never happened. |
/home/ubuntu/clawd (1.8 GB of trading + season state) is a git repo with no remote,
last commit April — backed up nowhere.aguia-private is plaintext and includes tracked
.env.bak-* files and a secret-bearing .json that dodge the
.gitignore globs — an active exfil path for the secrets already pending rotation.ssh.brunopessoa.com answers HTTP 200 with no Cloudflare Access
header; coolify.brunopessoa.com is still a bare login form. Both unprotected today.acp-heartbeat.sh and a live-format sk-ant-oat… token in 14
world-readable systemd unit backups from 6 May — rotate, then delete the backups. Rotation only;
do not rewrite history (that repo is the 9.5 GB nightly backup).arara are on-demand (unmonitored, but no longer false-paged);
and whether BUILDER_ENABLED stays on now that the member-JID hole is closed.F10 (reject all
unsigned) would risk your whole WA ingest and the RCE is already closed; the backup gaps
(clawd remote, crontab dump, tracked .env.bak) touch the backup repo and want the
secret rotation done first.