tidaldb/.sdlc/features/m9-leave-revocation/audit.md

2.6 KiB

Security Audit: Leave & Stop-Forward (m9-leave-revocation)

Surface

This feature adds three public API methods (leave_community_layer, rejoin_community_layer, community_layer_status) and one storage tag (Tag::CommunityLeave = 0x11). It modifies two internal signal fan-out paths (try_cohort_attribution, try_community_forwarding) to check a DashMap gate.

Authorization

The TidalDb API is a single-process embedded database. Authorization (who may call leave_community_layer on behalf of a user) is enforced at the application layer above TidalDb, not inside TidalDb itself. This is consistent with the rest of the API (signal_with_context, write_user, etc.). No regression.

Input Validation

  • user_id: u64 — no validation required; all u64 values are valid user identifiers.
  • No string or byte inputs are accepted by the public API methods.
  • persist_leave_record writes a fixed 9-byte value produced by serialize_membership — no user-controlled bytes reach the storage key beyond the 8-byte entity_id encoding (same as all other entity keys).

Storage Key Isolation

Key: encode_key(EntityId::new(user_id), Tag::CommunityLeave, b"") — no suffix. The key is entirely determined by user_id and the tag byte 0x11. No injection surface.

Tag 0x11 is unique (verified by all_tags_have_unique_bytes proptest). No collision with any existing tag.

Denial of Service

The DashMap::get gate on the signal write path is O(1) and lock-free per shard. It does not allocate. A user cannot increase gate check cost by any input.

leave_community_layer and rejoin_community_layer each do exactly one storage put and one DashMap insert. These are not on the hot path and cannot be triggered by untrusted external input without application-layer authorization.

Information Disclosure

community_layer_status reads from the in-memory DashMap only — no storage I/O. The returned CommunityMembership struct contains user_id, status, and left_at_ns. None of this is sensitive beyond what the caller already knows (they supplied the user_id).

Startup Rebuild

rebuild_community_leave_status performs a full scan_prefix(&[]) of the users_engine on startup. It reads all keys and filters on Tag::CommunityLeave. Malformed values are skipped with a debug log — no panic, no error propagation. This is consistent with the pattern established by rebuild_community_memberships and all other rebuild functions.

Verdict

No security concerns. The attack surface is identical to existing TidalDb API methods. No new injection surfaces, no new authorization bypasses, no new DoS vectors.

Approved.