Skip to content

Security audit fixes: rate limits, IP spoofing, CSS bypass, guestbook block - #5

Merged
zowskyy merged 7 commits into
mainfrom
claude/audit-2
Aug 21, 2026
Merged

Security audit fixes: rate limits, IP spoofing, CSS bypass, guestbook block#5
zowskyy merged 7 commits into
mainfrom
claude/audit-2

Conversation

@zowskyy

@zowskyy zowskyy commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Findings fixed (9 total, all from second codebase audit)

High

  • X-Forwarded-For spoofing (lib/rateLimit.ts) — the leftmost XFF entry is client-supplied and trivially forgeable, bypassing all anonymous rate limits including login brute-force protection. Fixed to use X-Real-IP first, then the rightmost XFF entry (appended by our own proxy, not forgeable).

Medium

  • No DM rate limit within existing threads (lib/messages.ts) — once a conversation existed, there was zero rate limit on messages within it. Added 60/min per-sender cap covering all sends.
  • CSS overlay check bypassed by split rules (lib/cssScope.ts) — position: fixed and z-index could be placed in separate rules for the same element, each passing inspection individually while the browser combined them into a full-screen overlay. Fixed by blocking fixed, absolute, and sticky positioning unconditionally (no z-index requirement, no split possible).

Low

  • makeFlowAction no rate limit (make/actions.ts) — each call does multiple DB writes. Added 20/min cap.
  • Studio write actions no rate limit (studio/actions.ts) — saveDraftAction, saveAndPublishAction, importPageAction each parse a full JSON page document on every call. Added 30/min cap (shared key).
  • ensureModeratorSeed() before auth check (moderation/page.tsx) — unauthenticated requests triggered the seed logic. Moved after auth and moderator check.
  • Redirect from raw URL param before validation (messages/[handle]/page.tsx) — raw handle used in login redirect before parseHandleParam was called. Parse first, redirect with validated handle.
  • Anonymous guestbook bypass of block check (lib/guestbook.ts, [handle]/guestbook/actions.ts) — authorId=null skipped the block lookup entirely; blocked users could sign anonymously. Added blockCheckId parameter; action passes viewer.id regardless of anonymous flag.

All 194 tests pass.


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added safeguards to limit page creation, studio actions, and direct messages.
    • Anonymous guestbook entries now apply account-based blocking checks when available.
  • Bug Fixes

    • Improved message-page handle validation and login redirects.
    • Prevented unnecessary moderator setup for existing moderators.
    • Strengthened CSS positioning validation.
    • Improved anonymous request identification for rate limits.

claude added 6 commits August 21, 2026 07:25
Nav links, buttons, and step labels now use -webkit-text-stroke to match
the heading style: hollow/outlined letters in Vandal Blow font throughout.
- .top-bar controls: Vandal Blow + text-stroke 1.5px
- .btn primary: white fill, paper-stroke on ink bg
- .btn.secondary: transparent fill, ink-stroke
- .splash-step-label: Vandal Blow + text-stroke 1.5px

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWWTyKs6t8zyYbVYavKSBn
The .splash-feature-card h3 override was setting solid color: var(--accent),
cancelling the stroke. Now uses color: var(--paper) + text-stroke like all
other headings.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWWTyKs6t8zyYbVYavKSBn
Critical:
- globals.css: add [data-theme="dark"] guard to reader-mode dark styles
- rateLimit.ts: throw RateLimitError inside try block with explicit ROLLBACK,
  not after COMMIT outside the exception handler

High:
- friends.ts: listPublicFriends privacy check now allows page owner to see
  their own private-paged friends (viewerId === userId || viewerId === f.userId)
- globals.css: remove 4 hardcoded border-radius: 2–4px values (→ 0)
- guestbook.test.ts: 13 new tests covering sign/moderate/delete/count paths

Medium:
- globals.css: .btn.secondary text is now color: var(--ink) (was var(--paper)
  — invisible white text on transparent background)
- pageDocument.ts: importPageData validates parsed value is a plain object
  before casting, not just truthy

Low:
- globals.css: .studio-pixel-clear uses var(--ink)/var(--accent-ink) instead
  of hardcoded rgba/hex
- guestbook.ts: remove duplicate JSDoc comment on countPendingGuestbookEntries

Tests: 194 passed (13 new)

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWWTyKs6t8zyYbVYavKSBn
Rewrites all hero copy, feature cards, step labels, CTAs, and section headers
for the 20-25 urban youth/artist demographic. Swaps corporate startup language
for raw manifesto tone: "Claim your spot", "Tag it up", "Find your people",
"Join the pool" — no feed, no algorithm, nothing sanitized.
Mobile:
- Viewport meta tag (was missing — critical; mobile browsers were
  rendering at desktop width and scaling down)
- Hamburger nav drawer (NavDrawer client component) on ≤640px — collapses
  the full right-side link list into a tap-to-open drawer with badge count
- Responsive typography: h1/h2/h3 scale down on ≤480px screens
- 44px min-height enforced on all tappable elements (Apple HIG / WCAG 2.5.5)
- SiteNav: explicit width/height on logo img to prevent CLS

Performance:
- <link rel="preload"> for both self-hosted fonts (VandalBlow, BebasNeue)
  so they don't block first paint
- Replace filter:brightness() hover on .btn with opacity (avoids GPU
  compositing layer promotion on every hover)
- will-change:transform on marquee animation only (legitimate use)
- contain:layout style on explore-card and ask-card (paint containment
  for large card grids)

PWA:
- /manifest.json — standalone display, theme color, icon
- /sw.js — service worker: cache-first for fonts and static assets,
  network-first for navigation, stale fallback for offline
- PwaRegistrar client component registers the SW after mount
- Apple web app meta tags (appleWebApp) in Next.js metadata
HIGH:
- rateLimit: use rightmost X-Forwarded-For / X-Real-IP only — leftmost
  value is client-supplied and was trivially forgeable, bypassing all
  anonymous rate limits including login brute-force protection

MEDIUM:
- messages: add per-sender 60/min rate limit on all sends within existing
  threads, not just new-conversation creation
- cssScope: block position:fixed/absolute/sticky unconditionally instead
  of requiring z-index in the same rule — the old check was defeated by
  splitting the two properties across separate rules for the same element;
  also closes the sticky+z-index gap

LOW:
- make/actions: add 20/min rate limit to makeFlowAction — each call does
  multiple DB writes with no prior guard
- studio/actions: add 30/min rate limit to saveDraftAction,
  saveAndPublishAction, importPageAction — each parses a full JSON page
  document (up to 50KB) on every call
- moderation/page: move ensureModeratorSeed() after auth+moderator check
  so unauthenticated requests cannot trigger the seed logic
- messages/[handle]/page: parse and validate handle before using it in
  the login redirect URL to prevent open redirect via raw URL segment
- guestbook: add blockCheckId param to signGuestbook() — blocked users
  could previously bypass the check by signing anonymously (authorId=null
  skipped the block lookup); the action now passes viewer.id separately
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6add242c-3610-41d9-916a-8b3d48487b71

📥 Commits

Reviewing files that changed from the base of the PR and between 2c841ec and 586b8f5.

📒 Files selected for processing (9)
  • app/src/app/(platform)/make/actions.ts
  • app/src/app/(platform)/messages/[handle]/page.tsx
  • app/src/app/(platform)/moderation/page.tsx
  • app/src/app/(platform)/studio/actions.ts
  • app/src/app/[handle]/guestbook/actions.ts
  • app/src/lib/cssScope.ts
  • app/src/lib/guestbook.ts
  • app/src/lib/messages.ts
  • app/src/lib/rateLimit.ts

📝 Walkthrough

Walkthrough

The change adds rate limits to page, studio, and messaging actions; improves anonymous identity resolution; validates message handles; reorders moderation checks; supports guestbook block checks for anonymous entries; and rejects additional CSS positioning rules.

Changes

Platform hardening

Layer / File(s) Summary
Request throttling
app/src/lib/rateLimit.ts, app/src/app/(platform)/make/actions.ts, app/src/app/(platform)/studio/actions.ts, app/src/lib/messages.ts
Page creation allows 20 requests per viewer. Studio actions allow 30 requests per user. Direct messages allow 60 per sender per minute. Anonymous keys use trimmed trusted proxy values.
Route and access validation
app/src/app/(platform)/messages/[handle]/page.tsx, app/src/app/(platform)/moderation/page.tsx
Message handles are validated before login redirects. Moderator seeding runs only for authenticated non-moderators.
Guestbook block identity
app/src/lib/guestbook.ts, app/src/app/[handle]/guestbook/actions.ts
Guestbook block checks accept a separate viewer identity. Anonymous entries pass a null display handle.
CSS positioning validation
app/src/lib/cssScope.ts
Rules using fixed, absolute, or sticky positioning are rejected.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: claude

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/audit-2

Comment @coderabbitai help to get the list of available commands.

@zowskyy
zowskyy marked this pull request as ready for review August 21, 2026 15:42
@zowskyy
zowskyy merged commit 6b043dd into main Aug 21, 2026
1 check was pending
zowskyy pushed a commit that referenced this pull request Aug 22, 2026
… design

MAJOR Bug #5: Missing Error Handling in signGuestbook/recordEdge
- Issue: Guestbook entry was inserted before recordEdge() was called
  If recordEdge() threw, entry existed but edge was missing (inconsistent state)
- Fix: Wrapped recordEdge call in try-catch block
  Entry is preserved, error is logged for monitoring
  Preferable to losing guestbook entries if graph system has temporary issue
- Impact: Prevents database inconsistency while protecting data integrity

MINOR Bug #6: blockCheckId Parameter Confusion
- Issue: blockCheckId parameter defaulted to authorId, creating confusion
  Callers could forget to pass it and block checks would be silently skipped
  Pattern: signGuestbook(..., blockCheckId: string | null = authorId)
- Fix: Made blockCheckId required (not defaulted)
  Pattern: signGuestbook(..., blockCheckId: string | null)
- Impact: Forces callers to explicitly consider block checking
  Prevents accidental security bypasses from forgotten parameters
- Updated: All callsites in guestbook.test.ts and bugs.test.ts to pass explicit blockCheckId
- Note: Production callsite in actions.ts already passed blockCheckId correctly

Test Results:
- All 14 guestbook tests passing
- Changes prevent future bugs from missing block checks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018V1rEEt5QTC2ww5ZikioWZ
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