docs: ratify v1 architecture and threat model (#2)

Closes #2
This commit is contained in:
KyuubiYoru
2026-07-16 04:11:24 +02:00
parent 2959c7e845
commit 286fbfeb36
7 changed files with 478 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
# README security promise and test matrix
Tracking: #2
This matrix turns each security and lifecycle promise in the README into an
enforceable control and planned evidence. Issue numbers refer to the delivery
backlog where the control is implemented and verified.
| README promise | Enforceable control | Planned evidence |
| --- | --- | --- |
| Per-game credentials and signing keys | Provisioned principals and versioned keys are scoped to game/environment; secrets come from a provider and never a public binary. (#5) | Cross-tenant authorization tests, rotation/overlap/revocation tests, and secret scans. |
| Short-lived, single-purpose tokens resistant to replay | Issuer fixes audience, tenant, attempt, role, issued/expiry times, nonce, and key ID; store atomically consumes nonce/ticket. (#4, #6, #10) | Golden vectors; expired, future, mutated, wrong-role, wrong-tenant, and concurrent replay tests. |
| Strict payload, metadata, and token size limits | ADR 0003 ceilings are checked before allocation/deserialization and again at domain construction. (#4, #15) | Boundary/property tests, malformed corpus, and allocation-aware fuzzing. |
| Registration, query, and introduction rate limits | Layered per-address, principal, tenant, and global token buckets with bounded queues and stable retry guidance. (#15) | Limit partition/isolation tests and overload/soak profiles. |
| Lease expiry removes abandoned servers | Visibility and join eligibility atomically require a fresh lease and fresh authenticated presence. (#6, #7) | Fake-clock expiry, renew/expire race, restart, and stale-host join tests. |
| Validate game, environment, room, and protocol boundaries | Every identifier is a validated type; store keys and authorization decisions include server-derived tenant scope; protocol is exact-match in v1. (#4-#10) | Contract, tenant-isolation, incompatible-version, and confused-deputy tests. |
| Structured audit events without secrets or reusable credentials | Allowlisted audit schema excludes metadata values, raw endpoints, tokens, and key material; event volume is bounded. (#16) | Captured-log/audit assertions and credential canary scans. |
| Public endpoint observation | Only authenticated UDP packets from the gameplay socket establish public endpoint ownership; bounded private local candidates follow ADR 0002 and HTTP claims are never introduced. (#11) | Spoofed-source, arbitrary-target, private-range, and same-LAN/external tests. |
| Authenticated join and punch tokens | Join issuance rechecks compatible visible listing; mediator validates scoped one-time capabilities; host consumes signed ticket. (#10-#12) | Deterministic three-party success, rejection, replay, mismatch, and timeout tests. |
| Clear timeouts and failure results | SDK owns explicit deadlines/cancellation and returns a closed typed outcome set; NAT introduction alone is not success. (#12, #13) | Fake-clock deadline/cancellation and host-rejection tests. |
| Isolation by game, environment, protocol, and region | Tenant and protocol are mandatory exact filters; region is bounded policy/filter data and cannot override tenant compatibility. (#5, #8, #15) | Cross-product browse/register/join isolation tests. |
| Operational health, metrics, logging, administration, and rate limiting | Separate liveness/readiness, bounded privacy-safe metrics/logs, authenticated operator controls, and overload signals. (#15, #16, #27) | Authorization matrix, redaction tests, dashboard queries, and failure-injection checks. |
| Service leaves gameplay path after direct connection | Mediator handles only presence/capability/introduction messages and has no gameplay forwarding API. (#4, #11) | Contract/API review, UDP unknown-message drop tests, and end-to-end traffic-path assertion. |
| Direct traversal is not guaranteed and requires fallback | SDK distinguishes traversal failure from service/host rejection and only returns configured fallback data for caller choice. Relay is absent from v1. (#13, #20, #24) | Typed-outcome tests and TestClient scripted fallback scenarios. |
Release readiness requires the linked implementation tests to exist and pass;
the design documents alone do not satisfy the security promise.
+96
View File
@@ -0,0 +1,96 @@
# Rendezvous v1 threat model
Tracking: #2
## Scope and assets
This model covers the public HTTP API, public LiteNetLib-compatible UDP mediator,
operator API, client SDK, game host integration, reverse proxy, secret provider,
observability pipeline, and the proposed future shared store. Gameplay traffic
after direct connection and game-owned identity/admission systems are outside
the service boundary, but their handoff is in scope.
Assets include tenant isolation, service availability, signing and publisher
keys, lease and connection credentials, raw endpoints, unlisted share codes,
listing integrity, audit integrity, and the guarantee that Rendezvous does not
turn into a reflector or private-network probe.
## Actors and assumptions
- Anonymous Internet attackers can send arbitrary HTTP and UDP traffic, spoof
source addresses where their network permits it, scrape listings, and create
many identities or addresses.
- Malicious publishers possess credentials only for their assigned tenant and
may submit hostile metadata or attempt to target arbitrary endpoints.
- Malicious clients can obtain legitimate join credentials for sessions they can
see and may replay, race, mutate, or share those credentials.
- A compromised game client and its SDK are fully attacker-controlled. No
reusable secret in them is trustworthy.
- Operators are privileged but fallible. Their actions are authenticated,
constrained, and audited.
- The reverse proxy, secret provider, and build/release pipeline are trusted
dependencies. Their compromise is considered and mitigated but cannot be
completely contained by the application.
## Abuse paths and controls
```mermaid
flowchart TD
A["Attacker input"] --> H{"HTTP or UDP?"}
H -->|HTTP| V["Authenticate when required; validate tenant, schema, size, and rate"]
H -->|UDP| U["Parse bounded datagram; validate capability before response"]
V --> S{"Allowed and in quota?"}
U --> E{"Capability valid, fresh, scoped, unused, and endpoint observed?"}
S -->|No| R["Stable bounded rejection"]
E -->|No| D["Silent drop + bounded aggregate metric"]
S -->|Yes| State["Atomic ephemeral state transition"]
E -->|Yes| State
State --> O["Allowlisted audit event; no secrets/endpoints"]
```
| Threat | Example | Required prevention/detection | Planned evidence |
| --- | --- | --- | --- |
| Spoofing and reflection | Forged UDP source causes traffic to a victim | No response before valid capability proof; send responses only to observed authenticated sources; at most two responses and <=2.0 verified byte amplification | Packet-level spoof/reflection tests and amplification accounting |
| Private-network probing | Publisher supplies `127.0.0.1`, link-local, or another victim as a same-LAN candidate | Accept only bounded private-unicast claims inside a scoped authenticated UDP contribution; reject prohibited ranges; disclose only to the opposite role in that attempt; bound SDK probes | Endpoint classification matrix and three-party adverse tests |
| Capability/ticket replay | Reuse a captured token to repeat introductions or connect | Short expiry, role/tenant/attempt scope, atomic one-time consumption, bounded skew, key rotation | Concurrent replay and post-expiry tests with golden vectors |
| Cross-tenant access | Game A browses, renews, or joins Game B | Server-derived principal scope on every lookup and atomic mutation; indistinguishable not-found response | Tenant isolation tests across every endpoint/store operation |
| Listing spam and scraping | Flood registrations or enumerate public sessions | Trust-mode quotas, per-principal/address limits, bounded pages/cursors, rate limits, aggregate alerts | Rate-limit, cursor-tamper, and sustained-load tests |
| Metadata injection | Control characters or markup attack logs/UI | UTF-8/schema/size validation; store as data; exclude values from audit; SDK does not render markup | Malformed Unicode/JSON corpus and TestClient safe-display tests |
| Credential theft | Secret appears in log, URL, metric, crash, or package | Credentials in headers/bodies only; allowlisted logging; secret-provider indirection; no credential metric labels | Log-capture tests, repository/package scans, rotation exercise |
| Parser/resource exhaustion | Oversized, nested, fragmented, or high-rate input | Fixed ceilings, bounded parsers/queues/concurrency, early rejection/drop, no input-sized logging | Fuzz/property corpus, allocation limits, overload tests |
| Stale or crashed host | Dead listing remains joinable | Both lease and recent authenticated presence required; atomic expiry; join rechecks freshness | Fake-clock lifecycle and join-race tests |
| Clock manipulation | Token accepted outside intended lifetime | Server-issued timestamps, monotonic elapsed-time for local expiry, <=30 s wall-clock skew | Boundary and clock-jump tests |
| Operator misuse | Unauthorized enumeration/revocation or secret exposure | Separate strong auth/network policy, least privilege, tenant scope, immutable audit, secrets never readable through API | Authorization matrix and audit completeness tests |
| Reverse-proxy confusion | Forged forwarded address bypasses limits | Trust forwarding headers only from allowlisted proxies; direct traffic uses socket peer | Forwarded-header spoof tests |
| Store race | Renew/revoke/expire/replay operations interleave | Compare-and-swap/transactional interfaces and deterministic outcomes | Parallel race tests with a fake clock |
| Dependency/supply-chain compromise | Malicious or drifting package/build output | Central pinning, lock files, reproducible builds, vulnerability review, signed release provenance | Locked clean restore, dependency audit, artifact verification |
| Availability attack | Valid-looking traffic fills CPU, memory, queues, logs | Layered quotas, bounded queues/tasks, graceful overload, readiness/drain, capacity alerts | Load/soak/resilience gates and forced saturation tests |
## Security invariants
The implementation and its tests must preserve these invariants:
1. No UDP response is sent to an endpoint that has not presented a valid scoped
capability from that observed endpoint.
2. No browse response contains an endpoint, secret, internal attempt ID, or
credential.
3. Every state lookup and mutation includes server-derived game/environment
scope; caller-supplied scope alone is never authoritative.
4. A listing is visible and joinable only while both lease and presence are
fresh at the atomic decision point.
5. A capability or ticket can cause at most one successful state transition for
its intended role and attempt.
6. Join authorization never bypasses host-owned final admission.
7. Input cannot create unbounded memory, work, response bytes, metric labels, or
log volume.
8. Raw endpoints and secrets never enter normal logs, traces, audit payloads, or
metric dimensions.
## Residual risk
Direct traversal cannot work through every NAT, firewall, carrier, or platform
policy. Rate limiting cannot eliminate distributed abuse. A compromised trusted
proxy, secret provider, operator identity, game grant issuer, or host credential
can act within its granted scope until detected and revoked. Unlisted share
codes can be disclosed by recipients. These risks are communicated as typed
outcomes and operational signals rather than hidden behind a success claim.