Skip to content

fix(review): security hardening + dead-code cleanup#181

Merged
TortoiseWolfe merged 1 commit into
mainfrom
chore/code-review-fixes
Jul 5, 2026
Merged

fix(review): security hardening + dead-code cleanup#181
TortoiseWolfe merged 1 commit into
mainfrom
chore/code-review-fixes

Conversation

@TortoiseWolfe

Copy link
Copy Markdown
Owner

Systematic code review (security / performance / quality / coverage). Findings verified against the actual code before fixing; by-design patterns left untouched.

Security (3 real fixes)

  • send-payment-email edge fn — MEDIUM auth bypass. authHeader.includes(serviceRoleKey) was a non-constant-time substring match; with an unset key it coerced to includes("undefined") and matched any header — letting an attacker send arbitrary emails (phishing) from the app domain. Replaced with a guarded, exact, constant-time Bearer-token comparison. ⚠️ Deployed fn — needs redeploy via the Management API after merge.
  • group-service.createGroup — PostgREST filter injection. member_ids were interpolated into getConnectedUserIds' .or(...) filter without UUID validation (every sibling service validates). Added a validateUUID guard (after the cheap structural checks, before any query). +injection-guard test.
  • SignInForm — fail-open defeated fail-closed rate limiting. The checkRateLimit wrapper is fail-closed by design (blocks on infra outage to stop brute force); SignInForm wrapped it in a fail-open catch that silently disabled brute-force protection on the highest-value target. Removed it — now consistent with SignUp/ForgotPassword, which call it directly.

XSS defense-in-depth

  • markdown-processor — blog markdown was injected via dangerouslySetInnerHTML with no HTML sanitization (author-controlled today, but a fork rendering untrusted markdown inherits stored XSS). Added a targeted denylist: strips <script>/<iframe>/<style>/<object>/<embed>/<form>, on*= handlers, and javascript: URIs — while preserving the benign structural tags the blog authors use (<div>/<span>/<details>/<summary>/<br>, verified against committed posts). +6 tests.

Performance

  • SEOAnalysisPanel — memoized seoAnalyzer.analyze(post) so the full-body regex analysis doesn't re-run on unrelated re-renders.

Dead code

  • Deleted src/config/blog.config.ts (zero importers repo-wide) and src/tests/unit/sync-service.test.ts.backup (stale, no counterpart); removed a commented-out MSW block in src/test/setup.ts.

Verification

  • Full vitest suite: 3577 passed / 0 failed (+7 net). Type-check + eslint clean.
  • Group-test fixtures updated from fake ids (member-1, …) to valid UUIDs — those ids were never valid; the new guard correctly rejects them.

Deliberately NOT changed (surfaced, not churned)

  • ConversationList N+1 (per-conversation count/preview queries) — real, but constrained by a 1s debounce and living in the hook with the load-bearing "no messages-subscription" re-render guard. A real refactor, not a review one-liner.
  • Group-messaging not yet implemented throws (message-service) — reachable incomplete work (group E2E crypto); a feature, not a review fix.
  • Dead oauth_states table + oauth-state.ts — real CSRF is Supabase PKCE; the custom layer is unused. Deleting a migration table is a bigger call.

Summary

Category Found Fixed Remaining (noted)
Security 5 3 2 by-design (PKCE CSRF, email-keyed lockout)
Performance 3 1 2 (N+1 refactor, trivial re-decrypt cache)
Code Quality 4 4 0
Test Coverage 0 skips in src/; E2E skips tracked in docs

🤖 Generated with Claude Code

…eview

Security:
- send-payment-email (edge fn): replace the broken auth check
  `authHeader.includes(serviceRoleKey)` — a non-constant-time SUBSTRING match
  that, with an unset key, coerced to `includes("undefined")` and matched any
  header — with a guarded, exact, constant-time Bearer-token comparison. This
  closed an arbitrary-email-send bypass (phishing from the app domain).
  NOTE: this fn is deployed; needs redeploy via the Management API.
- group-service.createGroup: validate every member_id is a well-formed UUID
  before it reaches getConnectedUserIds' PostgREST `.or(...)` interpolation
  (filter-injection guard), matching the validateUUID pattern used across
  connection-service / gdpr-service. Placed after the cheap structural checks
  so their specific errors still surface first.
- SignInForm: remove the fail-OPEN try/catch around checkRateLimit. The wrapper
  is fail-CLOSED by design (returns allowed:false on infra outage to stop brute
  force); the catch silently defeated that on sign-in — the highest-value
  brute-force target — while SignUp/ForgotPassword already call it directly.

XSS defense-in-depth:
- markdown-processor: neutralize dangerous RAW HTML in blog prose
  (<script>/<iframe>/<style>/<object>/<embed>/<form>, on*= handlers,
  javascript: URIs) while PRESERVING the benign structural tags the blog
  authors use (<div>/<span>/<details>/<summary>/<br>). Author-controlled today,
  but makes a fork that renders untrusted markdown safe by default. +6 tests.

Performance:
- SEOAnalysisPanel: memoize seoAnalyzer.analyze(post) so the full-body regex
  analysis doesn't re-run on unrelated re-renders (e.g. the copied toggle).

Dead code:
- delete src/config/blog.config.ts (zero importers repo-wide) and
  src/tests/unit/sync-service.test.ts.backup (stale, no live counterpart);
  remove a commented-out MSW block in src/test/setup.ts.

Tests: full suite 3577 passed / 0 failed (+7). Group-test fixtures updated from
fake ids (member-1, etc.) to valid UUIDs — the ids were never valid and the new
guard correctly rejects them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants