v1.27.21
[1.27.21] — 2026-08-18
Release notes
Security fixes
- Legal-hold fence closed on two erasure paths (S2-03 CRIT / S2-04). A held
chunk was frozen against/purge, DSAR andforget— butPOST /ump/forget {"hard":true}(reachable at Write scope via the MCPump.forget
tool) and the ingest-replace/vault sweep bypassed the fence and could erase
it. Both now runrefuse_if_heldin-tx →409 legal_hold_active, all-or-
nothing. - Fence-forgeability close (S2-02). A stored body containing the literal
=== BRAIN_UNTRUSTED_CONTEXT END ===(or BEGIN) would close the untrusted
region early. The sharedstrip_sentinelsprimitive now removes both
literals before wrapping on every seam (MCPtool_result_payload+
format_response, and the plugin's recall banner), ordered invisible-strip
first so a zero-width split cannot re-heal a marker into the fence. - Backup v3 header bound as GCM AAD + KDF bounds (S2-13 / S2-14). The v2
header was not covered by the GCM tag — any header bit could be flipped
without failing authentication. v3 (same byte layout,brain backupnow
defaults tov3) binds the exact header bytes as GCM AAD, and
validate_kdf_paramsbounds attacker-controlled Argon2id params before any
allocation (m 8 MiB..1 GiB, t 1..=64, p 1..=8) so a craftedm = u32::MAX
errors (kdf_params_out_of_range) instead of OOMing.brain backupaccepts
v1|v2|v3; legacy v1/v2 files keep their read paths. - Auth fail-closed (F-27 class). A single-team wildcard
read:<team>/*now
grants only the sharedglobalpool, never every tenant's named domain (a
flat domain namespace means the team field can never narrow a*domain
grant — naming a domain requires naming it); and a token with no roles
passesrequire_dpo_roleonly when the deployment defines no roles at all,
closing the single-token shape that could ride a bare admin scope.
Bug fixes
- Empty reconcile is an explicit decision (S2/N1). An empty
live_uris
previously retired every active vault source and swept its chunks,
indistinguishable from a failed listing. It now 400slive_set_emptyunless
the caller setsallow_empty: true; the client panel waives it only through
the shared two-step confirm. - Client offline-queue integrity (N5–N8). Retry-park (a persisted counter
parks an auto-replay after 5 failures instead of refiring forever;
destructive actions always park); idempotency key normalizes the volatile
fields out so a re-enqueue collapses onto its twin; the persisted DSAR
subject hash is nowSHA-256(salt ‖ subject)with a per-install salt
(defeats precomputed/rainbow tables, legacy items decode via the empty-salt
form); and the purge owner is persisted so an owner-scoped purge no
longer replays as an empty no-op body that silently erased nothing. - Replay drift (N9/N13). Char-boundary-safe
hash_prefix(a corrupt stored
hash truncates on char boundaries) andkept_setdrift detection vs the
parent catch same-length row swaps. - Fence sentinel in the plugin (M7). The plugin resolves its bearer via the
env ladderBRAIN_TOKEN_FILE→BRAIN_TOKEN→ config, never writes a token,
and its per-turn abstention log logs the query length only (a recall query
is user text and openclaw's log is persistent) — see the plugin 0.4.5
CHANGELOG. - Webhook egress bound. The egress client now enforces a 5 s connect / 15 s
total timeout so a hung sink cannot stall the request path.
Full Changelog: v1.27.20...v1.27.21