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

42 lines
2.6 KiB
Markdown

# 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.**