Skip to content

v1.27.21

Choose a tag to compare

@github-actions github-actions released this 18 Aug 03:48
· 50 commits to main since this release

[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 and forget — but POST /ump/forget {"hard":true} (reachable at Write scope via the MCP ump.forget
    tool) and the ingest-replace/vault sweep bypassed the fence and could erase
    it. Both now run refuse_if_held in-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 shared strip_sentinels primitive now removes both
    literals before wrapping on every seam (MCP tool_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 backup now
    defaults to v3) binds the exact header bytes as GCM AAD, and
    validate_kdf_params bounds attacker-controlled Argon2id params before any
    allocation (m 8 MiB..1 GiB, t 1..=64, p 1..=8) so a crafted m = u32::MAX
    errors (kdf_params_out_of_range) instead of OOMing. brain backup accepts
    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 shared global pool, 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
    passes require_dpo_role only 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 400s live_set_empty unless
    the caller sets allow_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 now SHA-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) and kept_set drift detection vs the
    parent catch same-length row swaps.
  • Fence sentinel in the plugin (M7). The plugin resolves its bearer via the
    env ladder BRAIN_TOKEN_FILEBRAIN_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