fix(review): security hardening + dead-code cleanup#181
Merged
Conversation
…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>
This was referenced Jul 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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-emailedge fn — MEDIUM auth bypass.authHeader.includes(serviceRoleKey)was a non-constant-time substring match; with an unset key it coerced toincludes("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.group-service.createGroup— PostgREST filter injection.member_idswere interpolated intogetConnectedUserIds'.or(...)filter without UUID validation (every sibling service validates). Added avalidateUUIDguard (after the cheap structural checks, before any query). +injection-guard test.SignInForm— fail-open defeated fail-closed rate limiting. ThecheckRateLimitwrapper 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 viadangerouslySetInnerHTMLwith 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, andjavascript:URIs — while preserving the benign structural tags the blog authors use (<div>/<span>/<details>/<summary>/<br>, verified against committed posts). +6 tests.Performance
SEOAnalysisPanel— memoizedseoAnalyzer.analyze(post)so the full-body regex analysis doesn't re-run on unrelated re-renders.Dead code
src/config/blog.config.ts(zero importers repo-wide) andsrc/tests/unit/sync-service.test.ts.backup(stale, no counterpart); removed a commented-out MSW block insrc/test/setup.ts.Verification
member-1, …) to valid UUIDs — those ids were never valid; the new guard correctly rejects them.Deliberately NOT changed (surfaced, not churned)
not yet implementedthrows (message-service) — reachable incomplete work (group E2E crypto); a feature, not a review fix.oauth_statestable +oauth-state.ts— real CSRF is Supabase PKCE; the custom layer is unused. Deleting a migration table is a bigger call.Summary
🤖 Generated with Claude Code