Skip to content

[Gap-Audit] Security: crypto/secrets hygiene across messaging + audit logging #12

@TortoiseWolfe

Description

@TortoiseWolfe

Summary

Five separate security findings across the messaging encryption + audit logging stack. None of these is a known incident, but each is a latent risk with high blast radius.

Findings

1. Private ECDH key in plaintext localStorage (HIGH)

src/services/messaging/key-service.ts:54-70 — exports private key to JWK and stores in localStorage. Any XSS anywhere in the app compromises all messages and defeats the E2E zero-knowledge guarantee. The comment acknowledges this was for Playwright support.

Fix: Wrap the localStorage cache with app-level symmetric encryption (derived from a PIN or session-token-hash), OR scope the cache to test environments via build flag.

2. JWT prefix logged on every send (HIGH)

src/services/messaging/message-service.ts:151-157 — debug log includes tokenPrefix: session?.access_token?.slice(0, 20) on every send-message attempt. Visible to DevTools, browser extensions, log shippers.

Fix: Replace with opaque hasToken: !!session?.access_token.

3. Audit-logger has no credential filter (HIGH)

src/services/auth/audit-logger.ts:28event_data: Record<string, any> has no exclusion of password/token/secret/key fields. One accidental caller passing { password } lands a plaintext credential in the database.

Fix: Strip credential-named keys in the log() method before insert.

4. Client-only rate limiter (HIGH)

src/lib/auth/rate-limiter.ts — stores attempt counts in localStorage. Incognito mode or localStorage.clear() resets all counters. This is security theatre — any attacker bypasses it trivially.

Fix: Move rate limiting to server side (Supabase Edge Function or Postgres trigger on auth_audit_logs). Keep client-side as UX hint only; document that it's not a security control.

5. Dead crypto code with latent length bug (HIGH)

src/lib/key-derivation.ts:290-348createPKCS8() is unreachable but contains a hardcoded length byte (0x41 = 65) that's only correct for 32-byte keys. If re-enabled by a future contributor, produces silently broken keys.

Fix: Delete the method.

6. verifyPublicKey null-safety hole (HIGH)

src/lib/messaging/key-derivation.ts:170-188 — JWK x/y comparison: if stored key has missing y field (possible from older schema), comparison silently returns false. User locked out of messaging permanently.

Fix: Add explicit checks for both x AND y on both keys before comparing.

Why this matters

Six items across crypto + audit + rate-limiting; none is a known incident, but each is a latent breach or lockout risk.

Related

  • Code review findings: CR-024, CR-029, CR-030, CR-031, CR-032, CR-033

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggap-auditIdentified during 2026-04-25 planned-vs-shipped auditpriority:p0Critical — fix immediately (blocking, security, post-remake regression root cause)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions