Skip to content

fix: audit quick-wins + security hardening - #17

Merged
arcabotai merged 1 commit into
mainfrom
fix/audit-quickwins-and-hardening
Jun 22, 2026
Merged

arcabotai merged 1 commit into
mainfrom
fix/audit-quickwins-and-hardening

Conversation

@felirami

Copy link
Copy Markdown
Collaborator

Highest-value findings from a multi-agent codebase audit (6 finders → adversarial verification → synthesis). The audit found no critical fires — verification correctly downgraded the scary-sounding findings (a "hardcoded secret" that's just an event tag, an "opt-out enumeration" gated behind a UUID, a null-deref already handled). This PR lands the real, actionable ones.

Quick wins

  • OG image bug (real, user-facing): superlogo was undefined — masked by a @ts-ignore — so every cast share-preview image render 500'd. Now reuses the existing castoraLogo.
  • Null-guards: search-suggestions + profile/search .replace()'d a possibly-null query (→ 500); notifications accessed response for an invalid mode (→ 500). Now clean 400s / empty results.
  • finish-registration: wrapped its 4 account-creation writes in a $transaction — a partial failure used to orphan rows and, because fid is unique, wedge the retry.
  • Dead code: removed orphaned components (banner, PwaInstallPrompt), the dead super-auth routes (create-connection, create-signer, verify-farcaster), and unused deps (lokijs ~3.5MB, encoding).

Security

  • SSRF guard on url-preview/other (public, unauthenticated): blocks private/reserved/link-local/cloud-metadata IPs (resolved via DNS, not just the literal) and disables redirect-following. Verified that new URL() already normalizes decimal/octal/hex IP encodings (http://2130706433/ → 127.0.0.1 in both the validated hostname and the fetched URL) and throws on IPv6 zone IDs — so the "encoding bypass" the reviewer raised doesn't apply. Block-if-any-resolved-IP-is-private is kept deliberately (allowing a host because some IP is public would let axios still pick the private one).
  • Crypto-checkout webhook (Daimo, money path): constant-time token comparison; payment_completed is now exactly-once via a compare-and-swap claim (updateMany where status = previous) with revert-on-failure — previously it double-provisioned membership/storage on every Daimo retry. Also fixed a bare return; that returned undefined from the handler.

Verification

  • npm run typecheck ✅, npm run build ✅ (138 routes; the 3 deleted routes gone).
  • Adversarial review of the two security changes; both verdicts' "blockers" were investigated:
    • SSRF alt-IP-encoding / zone-id bypasses → confirmed non-issues by testing new URL() normalization.
    • Webhook idempotency → the reviewer's two blockers wanted opposite orderings; resolved with a CAS-claim + revert that's both concurrency-safe and retry-safe.

Follow-ups (need a Prisma migration — kept separate)

  • Make handleSuccessfulPayment idempotent (defense-in-depth for the webhook).
  • Retire the remaining dead super-auth code (AccountConnectionSession model, signer.ts / APP_MNEMONIC).
  • Poll double-vote needs @@unique([pollId, voterFid]); react-query v3→v5 unification; re-enable ESLint-in-build + reactStrictMode (config debt) — all tracked from the audit.

From a multi-agent codebase audit. Quick wins:
- og: fix undefined `superlogo` (every cast OG / share-preview image 500'd; it was
  masked by a @ts-ignore) — reuse the existing castoraLogo.
- search-suggestions, profile/search: guard a missing `query` param (was a 500 via
  .replace on null) — return empty results.
- notifications: reject an invalid `mode` with 400 instead of a 500.
- finish-registration: wrap the 4 account-creation writes in a $transaction so a
  partial failure can't orphan rows / wedge the unique fid on retry.
- dead code: remove orphaned components (banner, PwaInstallPrompt), the dead
  super-auth routes (create-connection, create-signer, verify-farcaster), and
  unused deps (lokijs, encoding).

Security:
- url-preview/other (public, unauthenticated): add an SSRF guard — block
  private/reserved/link-local/metadata IPs (resolved, not just the literal) and
  disable redirect-following. (new URL() normalizes alt IP encodings — verified.)
- crypto-checkout webhook (Daimo, money path): constant-time token compare; make
  payment_completed exactly-once via a compare-and-swap claim with
  revert-on-failure (was: double-provision on every retry); fix a bare `return;`
  that returned undefined from the handler.

Verified: typecheck + build green; adversarial review of the SSRF + webhook changes
(alt-IP-encoding & zone-id "bypasses" confirmed non-issues via new URL()
normalization; webhook reworked to a CAS claim).

Follow-up: make handleSuccessfulPayment idempotent (defense-in-depth) and retire the
remaining dead super-auth code (AccountConnectionSession model, signer.ts) — both
need a Prisma migration, kept separate.
@vercel

vercel Bot commented Jun 22, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
castora Ready Ready Preview, Comment Jun 22, 2026 12:29pm

@arcabotai
arcabotai merged commit 48a71d6 into main Jun 22, 2026
6 checks passed

This branch was successfully deployed

1 active deployment
Preview — 16835f50 Deployed Jun 22, 2026 by vercel[bot]
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