An ATProto Powered Reputation Network
provenance docs security-review.md
9.1 kB
Markdown

Initial security review #

Addressed risks #

  • OAuth callbacks require an encrypted, single-use state and a browser-bound nonce.
  • The adapter rejects issuer mismatches, DID mismatches, missing atproto scope, and missing access tokens.
  • DPoP responses must contain a server nonce.
  • OAuth secrets use AES-GCM with record identity as authenticated data.
  • Production cookies use __Host-, Secure, HttpOnly, and SameSite=Lax.
  • Mutations require the exact origin and the session's CSRF token.
  • The same-origin referrer policy preserves login form origins and suppresses cross-origin referrers.
  • Login rejects missing, null, and foreign origins.
  • Public account suggestions have bounded query lengths and a five-second request deadline.
  • Private queries and mutations still require a session. Mutations still require the origin and CSRF token.
  • UI error responses and logs omit upstream error bodies and credentials. Reference IDs connect responses to logs.
  • The UI uses text nodes for account names, tags, and scores.
  • The content security policy rejects inline scripts and external scripts.
  • Private endpoints require a service key and an authenticated DID from the UI.
  • PDS requests use the SDK's public-address transport.
  • Normal snapshots reuse verified identities for at most five minutes. Explicit recovery forces fresh participant identity resolution.
  • Actual indexed identity events invalidate the appview identity cache. OAuth login still forces fresh identity resolution.
  • A bounded HTTP worker queue limits external concurrency, response reads, and retries within each process.
  • Retryable failures share per-origin cooldowns. Retries honor Retry-After and use jitter. The queue does not automatically repeat writes.
  • External HTTP clients retain SSRF protections and use the project User-Agent.
  • Record URIs must match the repository DID, collection, and deterministic record key.
  • Incomplete snapshots preserve the previous index.
  • Graph traversal rejects resource-limit errors instead of returning a partial score.
  • Reputation queries resolve all account identifiers under one five-second deadline.
  • Algorithm metadata restricts query options. Ignored lists accept at most 100 accounts and cannot exclude either endpoint.
  • Public API schemas contain no service credentials or private participant values.
  • Public reputation uses threshold 1 or an explicit threshold, never the source's private threshold.
  • Authenticated reputation uses the authenticated user's threshold unless the request supplies an override.
  • SQLite snapshots and schema migrations use transactions.
  • Event mutations and cursors share one transaction.
  • Replay preserves record tombstones and account status timestamps.
  • Invalid record updates cannot preserve an obsolete positive assertion.
  • Microcosm endpoints come only from operator configuration, not browser input.
  • Missing payload lookup requires the stream's exact URI and CID.
  • Vouch comments are public, untrusted text. Clients must not interpret them as HTML.
  • Soft records require valid subjects, timestamps, collection types, and repository-bound record URIs.
  • Soft sources have fixed fractional scores. Explicit writes reject fractional scores.
  • Soft account state and records remain separate from participant enrollment and private thresholds.
  • Cache dependencies include collections so that identical record keys cannot invalidate unrelated sources.
  • Blocked-list imports require DID-based list URIs and repository-bound definitions and membership records.
  • List members depend on their public subscription, moderation-list definition, and membership records, not private mute state.
  • Participant snapshots commit list imports and vouches together. Failed imports preserve the index.

Correctness #

The graph tracks the shortest path for each node and bottleneck score. An additional path with the same bottleneck cannot improve the score unless it has fewer edges. This rule bounds cycles without a fixed separation limit. Negative assertions never multiply, so two negative assertions cannot create positive trust. Direct blocks override paths. Other blocks participate in calculations. Optimistic selects the highest score across paths. ShortestPath selects the fewest hops, then the highest score among equal-length paths. ShortestPath removes ignored intermediaries and their edges before traversal. A direct block remains effective. Unknown targets never become trusted through a negative threshold. The service attaches incoming vouch comments after calculation. Comments do not affect scores, path selection, or record keys. The schema migration gives existing records an empty comment and preserves their assertions. Relative reputation sums non-block direct assertions only from authors whose calculated or self trust meets the threshold. Unknown and blocked authors never qualify, even at negative thresholds. Empty sums return zero without a trust-score clamp. The trust cache includes algorithm metadata, limits, topic selection, normalized identities, and account options in its key. Soft feature modes also isolate cache keys. Explicit assertions override soft assertions, and soft blocks override follows. Soft blocks remain negative assertions, not hard blocks. Duplicate soft records contribute only once per pair. Record mutations invalidate indexed path dependencies in the same transaction as the mutation. Graph revisions reject cache publication after concurrent mutations. Cache errors and traversal errors never produce partial reputation sums.

Architecture #

HTTP handlers call services. Services use storage interfaces and protocol adapters. The graph contains no network or database operations. The service injects the active algorithm. Its metadata controls reputation-query validation and the generated OpenAPI schema. The SQL adapter contains all database queries through generated sqlc code. The tests use temporary databases and simulated protocol IO.

Remaining operational risks #

The service key grants access to every participant's private configuration. Protect that key and restrict internal endpoints to the service network. Public queries need deployment-level rate limits. Worker counts limit HTTP concurrency, not request rates. Replicas have independent limits and in-memory cooldowns. Account search still needs shared response caches and endpoint throttles (tsk-28). Broad referenced-list snapshots remain a follow-up (tsk-27). Without an indexed identity event or explicit recovery, a PDS change can remain cached for five minutes. The index is eventually consistent and trusts the configured Jetstream provider's JSON events. Trust cache entries can retain new-path, new-block, or account-status changes for the full TTL, which defaults to 24 hours. Unknown results have no path dependencies and expire only through the TTL. Expired entries remain on disk until replacement. Operators must account for cache growth from distinct queries. Jetstream does not give this client repository-signature proofs or a reliable replay-gap signal. Long outages need explicit PDS recovery, not scheduled polling. Bootstrap watermarks depend on synchronized UTC clocks. The health endpoint does not measure stream freshness. Monitor cursor lag, disconnect logs, and database growth. All-author soft ingestion has unbounded total record volume. Per-query graph limits do not bound database size. Nonparticipant soft records come only from observed events. The index does not reconstruct their complete repositories after replay gaps. Participant sync recovers soft records after long outages or disabled periods. Blocked-list owners control membership and can change subscribers' calculated trust. A subscription delegates this part of the graph to its owner. Blocked-list imports have bounded record counts, but total indexed list data and retained replay tombstones have no automatic cleanup. An unavailable list owner's PDS can delay a new subscription event and its stream cursor until recovery. Schema version 7 rebuilds soft records and replay versions with compact primary-key storage. The migration needs temporary disk space. Schema version 8 replaces repeated graph DIDs with integer dictionary keys. Transactions preserve identity mappings and replay records. Dictionary entries remain persistent and retain exact DIDs. The adapter does not create dictionary entries for read-only queries. Global graph loads remain a RAM and query-capacity limit. Concurrent uncached queries multiply these allocations. Trust traversal stops at 10 hops. The cap also limits reputation authority paths, but global graph loads still precede traversal. No tombstone retention limit exists. A cleanup policy must preserve replay protection before it removes old versions. Run only one appview ingestor per database. Repository operations serialize within one UI process. Multiple UI replicas need shared refresh coordination. Live PDS OAuth interoperability still needs an account-based smoke test. This review is not an external audit.