Skip to content

registry: constant-time admin compare, WAL doc fix, opt-in heartbeat freshness and expiry binding - #112

Merged
TeoSlayer merged 2 commits into
mainfrom
sec/findings-rendezvous
Jul 26, 2026
Merged

registry: constant-time admin compare, WAL doc fix, opt-in heartbeat freshness and expiry binding#112
TeoSlayer merged 2 commits into
mainfrom
sec/findings-rendezvous

Conversation

@TeoSlayer

Copy link
Copy Markdown
Contributor

Security findings L3, L4, L2, M12. Recreated against main (the original PR auto-closed when its base branch was merged and deleted).

  • L3 checkAdminToken used != — now subtle.ConstantTimeCompare, matching the sibling checks.
  • L4 Append doc corrected: the write is synchronous, the fsync is batched. Deliberately not switched to per-append fsync (throughput change, not a doc fix).
  • L2 heartbeat freshness behind default-off -strict-heartbeat-freshness. Note: enabling it refuses the binary heartbeat encoding, which has no timestamp field — fully closing that needs a wire format revision.
  • M12 expires_at bound into the set_key_expiry challenge behind default-off -strict-expiry-binding.

🤖 Generated with Claude Code

teovl and others added 2 commits July 26, 2026 00:50
Live mutex/block profiles from the production registry under a fleet
reconverge showed two single global locks dominating:

  handshakeMu   50.6% of all block time (HandlePollHandshakes)
  trust st.mu   32.1% of all mutex time (HandleReportTrust)

Every agent polls its handshake inbox on a timer, so at fleet scale all
polls serialised on one mutex despite each touching only its own node id;
report_trust did the same for trust pairs. Both are now sharded 256 ways
by node id / pair key. Every mutating critical section already touched
exactly one key, so no path needs two shards and there is no lock order
to observe.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…doc, opt-in heartbeat freshness + set_key_expiry binding

L3 — trust.checkAdminToken compared the supplied admin token with the
string operator, so a rejection returned as soon as a byte differed and
the time it took leaked how much of the token was right. Switched to
subtle.ConstantTimeCompare, matching every sibling admin-token check in
the repo (authz, membership, dashboard, accept). This was the only
divergence.

L4 — wal.Append's doc claimed the entry is fsync'd. The write(2) is
synchronous, the fsync is batched (walSyncInterval / walSyncBatch), so
host power loss can lose a bounded tail. Corrected the comment on Append,
the package doc, and the constants rather than fsyncing every append,
which would change throughput on the write path.

L2 and M12 both change what a client has to sign, so both are behind
default-off flags mirroring -strict-registration-auth. Default behaviour
is unchanged.

L2 — the heartbeat challenge covered only the node id, so a captured
heartbeat stayed valid indefinitely and could hold a node that is gone in
the "seen recently" state. -strict-heartbeat-freshness /
RENDEZVOUS_STRICT_HEARTBEAT_FRESHNESS=1 requires a "ts" field within
heartbeatMaxSkew and binds it into the challenge. Enabling it also
bypasses the 120s signature-verification cache, which would otherwise
wave through anything sent after one accepted heartbeat, and refuses the
binary heartbeat encoding: its payload is a fixed node id + signature
with no field for a timestamp, so leaving it open would make the gate
decorative. Carrying a timestamp there needs a registry wire-format
revision and is not attempted here.

M12 — the set_key_expiry challenge covered only the node id, so one
signature authorized any expiry for that node, including substituting a
distant one. -strict-expiry-binding / RENDEZVOUS_STRICT_EXPIRY_BINDING=1
appends the requested expires_at to the challenge, the way
HandleRotateKey's challenge appends the new public key.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@TeoSlayer
TeoSlayer merged commit 6370415 into main Jul 26, 2026
9 checks passed
@TeoSlayer
TeoSlayer deleted the sec/findings-rendezvous branch July 26, 2026 14:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants