v0.7.87 — Audit iteration 3 (auth/SSRF/secret-leak + correctness/DoS)
Third adversarial-audit sweep over v0.7.86 — API auth/IDOR, channels, kernel
scheduler/RBAC/workflows, memory/knowledge, types/config, MCP-server, and
structured-output/routing. 36 candidate findings (the 19-item iter-3 backlog
plus 17 fresh finder findings) were each gated behind an independent 3-lens
refute panel; 27 confirmed and fixed, 8 rejected. Full CI green
(macOS/Ubuntu/Windows test+check, clippy, fmt, cargo audit, secrets scan).
1758 tests passing.
Security
- Auth bypass (HIGH): with no API key configured, the localhost gate
trusted a spoofableX-Forwarded-Forfrom any private-net / Docker-bridge
peer — so a sibling container or LAN host could send
X-Forwarded-For: 127.0.0.1and obtain unauthenticated Owner. The gate now
uses the real transport peer IP (ConnectInfo), never forwarded headers. - SSRF via redirect (HIGH×2): the A2A client and cron-webhook delivery
followed HTTP redirects without re-checking — a public URL could 3xx-redirect
to127.0.0.1/169.254.169.254/ RFC1918. Both now re-validate every hop
(matching the web-fetch/browser fix from v0.7.86). - PSK leak (HIGH): the RHP HMAC
shared_secretwas serialized in plaintext
through the in-process MCPconfig_getpath. Now#[serde(skip_serializing)]
likeapi_key/proxy.password. - MCP privilege escalation:
schedule_create/schedule_deletedropped from
the safe-default allowlist (they persist Admin-gated recurring agent turns). - MCP file read:
file_read/file_list/file_statare confined to the
agent-workspaces dir instead of the daemon's process CWD (which exposed
config.toml/.env/.sshto a remote MCP caller). - Telegram token leak: bot token redaction now also covers the
/file/bot{token}/download-URL shape and the channel-agnostic bridge log
sites where reqwest errors escape via?.
Correctness
- Cost quota over-counted ~24×: the hourly window compared an RFC3339
string lexically against SQLitedatetime(), collapsing "last hour" into a
midnight-resetting "today". Normalized withdatetime(timestamp). - Cron:
update_job(PUT) now enforces the same caps as create (no
fire-every-tick DoS / chrono overflow); the IANA timezone is honored
(chrono-tz) instead of silently firing UTC; unparseable / never-matching
expressions are rejected at create/update; crash-orphaned claimed jobs are
rescheduled on load instead of stuck forever. - Workflows: runs no longer leak as perpetually
Runningon
agent-resolution failure or caller timeout (which defeated the eviction cap);
definition persistence serializes via a mutex + unique temp file (no
concurrent-write corruption / lost definitions). - Knowledge graph:
query_graphhonorsmax_depth(breadth-first
neighborhood traversal — it was single-hop only); the full-graph view reports
the true edge total;list_entitiesis capped. - Vector recall: embedding queries no longer pre-select the candidate pool
by recency, so a best-matching but not-recently-accessed memory is actually
returned. - Session search: a Unicode-aware
lowerUDF makes case-insensitive search
match capitalized non-ASCII (Cyrillic etc.); pre-v8message_countfallback
fixed. - OpenAI-compat: an unknown model returns a deterministic 404 (was an
arbitrary agent); streamingtool_callindices stay monotonic across loop
iterations (no client-side merge corruption); image content is rejected
rather than silently dropped. - Config:
exec_policy.timeout_secs = 0is clamped to 30 (a 0 made every
shell_exectime out instantly);max_restartsis now enforced in
restart_agent.
Deferred: mid-turn budget enforcement — the correct fix needs incremental
metering plus a mid-turn turn-cutoff, a behavior change that interacts with the
hourly-window fix above; out of scope for this hardening batch.