Back home
Authorized report access
Human review completeYellow — limited test launch only

Overall code health

6.9/ 10

Do not launch publicly or enable live payments until the ownership and webhook findings are verified.

AR

Reviewed by Alex Rivera

Senior Next.js & Supabase Engineer · 9 YOE

5.8/10
Security

At risk

7.5/10
Performance

Needs attention

7.4/10
Readability

Needs attention

Review ID

VC-PR-001

Completed

14 Jul 2026

Prompt Pack

5 copy-ready prompts

Launch verdict

Yellow — limited test launch only

The application has a workable foundation, but two material trust-boundary failures block public launch. A small invitation-only cohort is reasonable if project export is disabled, Stripe remains in test mode, and authentication/subscription failures are monitored.

Reviewed commit

8d41c2f7a9b4

Standard

0.1 pilot draft

Review date

14 July 2026

Fix before public launch

PR-001PR-002

Conditions for limited testing

  • Disable the project export endpoint until PR-001 is verified.
  • Keep Stripe in test mode until PR-002 and the end-to-end payment lifecycle pass.
  • Limit access to an invitation-only cohort with a named support owner.
Stop or rollback triggers
  • Any cross-user project access or unexpected export succeeds.
  • Subscription entitlement differs from Stripe state.
  • Authentication or account recovery errors exceed the monitored threshold.

Core findings

Evidence, impact, and verification.

3 sections
Location
app/api/projects/[id]/export/route.ts:18-41
Confidence
High
Evidence
The route loads a project by request path ID using the service-role client and returns its content without deriving the current user or filtering by owner_id.
Impact
An authenticated user who obtains another project ID can export that user's project content.
Conditions
The route remains enabled and project identifiers are disclosed, guessed, logged, or shared.
Required remediation
Derive the verified user server-side and query by project ID plus owner ID; return 404 for cross-user records.
Verification
Integration tests prove owner success and unauthenticated/cross-user denial without service-role bypass.
Location
app/api/stripe/webhook/route.ts:44-118
Confidence
High
Evidence
The signature is verified, but event.id is not stored and processing writes subscription state on every delivery without an atomic event ledger.
Impact
Duplicate or concurrent webhook delivery can repeat fulfillment, create contradictory state, or hide a failed partial update.
Conditions
Stripe retries an event, an operator resends it, or the handler commits before its response is received.
Required remediation
Atomically claim event.id, apply state once, and acknowledge duplicates without repeating effects.
Verification
Duplicate and concurrent signed fixtures leave one event and one canonical entitlement state.

The deliverable

Your Prompt Pack.

Copy each prompt into your AI coding tool. They are ordered by priority.

Prompt 01

PR-001 — Enforce ownership on project export

In app/api/projects/[id]/export/route.ts, derive the current user from the verified Supabase server session and load the project by both project ID and owner ID. Return 404 for missing or cross-user records, keep error messages generic, and do not trust a user ID from the request. Add integration tests for owner success, unauthenticated access, cross-user access, and a deleted project. Preserve the current export format and provide a reviewable diff.

Prompt 02

PR-002 — Make Stripe webhook processing idempotent

Add a durable Stripe event ledger keyed by event.id and process checkout/subscription events in one database transaction. Treat duplicate delivery as success without repeating entitlement changes. Check every database result and return non-2xx when durable processing fails so Stripe retries. Add tests for duplicate, concurrent, out-of-order, invalid-signature, and database-failure deliveries. Do not weaken raw-body signature verification.

Prompt 03

PR-003 — Restrict password-recovery redirects

Replace the callback's unrestricted next parameter with an allow-list of relative application paths. Reject absolute URLs, protocol-relative URLs, encoded bypasses, and backslash variants. Keep /dashboard and /settings/account as valid destinations, default safely when invalid, and add table-driven tests for accepted and rejected redirects.

Prompt 04

PR-004 — Make account deletion recoverable

Refactor account deletion into an authenticated, auditable server workflow that derives the user ID from the current session, records progress, and does not report success while Stripe cancellation or required database updates are unresolved. Make retries idempotent and add tests for unauthenticated, cross-user, partial-provider-failure, retry, and successful completion paths.

Prompt 05

PR-005 — Prevent stale project-search results

Update the project search effect to cancel superseded requests with AbortController, ignore AbortError, and prevent older responses from overwriting the current query. Preserve the last valid result during refresh and add tests with deferred promises for rapid query changes and component unmount.

Readiness checklist

What was ready, not ready, or unavailable.

Ready

Build and deployment

Clean install and production build succeed; environment validation is present.

Not ready

Authentication and authorization

Core session handling is credible, but PR-001 breaks object ownership and PR-003 affects recovery redirects.

Not ready

Data and privacy

Normal project writes are owner-scoped; export and deletion lifecycle require correction.

Ready

Secrets and dependencies

No committed production credential was identified; critical dependency advisories were not applicable to reviewed flows.

Not ready

Payments and high-value actions

Webhook signatures are checked, but durable idempotency is missing.

Not ready

Critical flows and correctness

Happy paths work; retry, cross-user, and partial-failure behavior need coverage.

Not tested

Maintainability and observability

Repository structure is understandable, but alerting, restore evidence, and production dashboards were unavailable.

Remediation sequence

A prioritized 7-day and 30-day plan.

Within 7 days

  1. 1Disable project export and keep Stripe in test mode.
  2. 2Fix and verify PR-001 and PR-002 with integration tests.
  3. 3Restrict recovery redirects and complete the authentication regression suite.
  4. 4Run signup, project ownership, checkout, duplicate webhook, cancellation, and deletion flows in staging.

Within 30 days

  1. 1Implement the durable account-deletion workflow and customer-facing status.
  2. 2Add Stripe-to-database reconciliation and alerts for entitlement divergence.
  3. 3Complete an isolated backup restore and document rollback ownership.
  4. 4Re-review the fixed commit before enabling public access or live payments.

Next checkpoint

Repeat the Production Readiness Review after PR-001 and PR-002 are fixed and the staging payment lifecycle, backup restore, and rollback checks have evidence.

Accepted scope

Scope version 1

sample

Determine whether the application can safely accept its first 100 public users and identify the changes required before enabling live payments.

Critical flows: Sign up, sign in, password recovery, and account deletion, Create, read, update, and export user-owned projects, Start checkout and synchronize Stripe subscription entitlement

  • No penetration test, compliance certification, or legal opinion.
  • No production customer data or live payment credentials were used.
  • No load test, destructive test, or implementation work was performed.

This report was written by a human.

Delivered through an approved reviewer account.

Review my code

Your privacy choices

We use necessary cookies to run the service. With your permission, we also use Google Analytics to understand how the site is used. Analytics stays off unless you accept.

Privacy and cookie notice

Privacy controls

Necessary cookies

Always active

Required for authentication, security, preferences, and remembering your cookie choice.

Google Analytics

Measures visits and navigation using sanitized route names. We do not send query strings, fragments, or record identifiers.

Current choice: Rejected

Read the privacy and cookie notice