Date: 2026-04-07
Target: http://localhost:7420/
Note: The app uses Express. The actual generation endpoint is /api/sessions (POST), not /api/generate.
curl -v http://localhost:7420/api/generate -X POST -H "Content-Type: application/json" -d '{"prompt":"test"}' 2>&1Result: 404 Not Found — Cannot POST /api/generate
Notes: This endpoint does not exist. The real generation endpoint is POST /api/sessions.
curl -v http://localhost:7420/api/generate -X POST -H "Content-Type: application/json" -d '{broken json' 2>&1Result: 400 Bad Request BUG - STACK TRACE LEAK: The response exposes a full Node.js stack trace including:
- Internal file paths:
/Users/abhi/proj/sensei/ship-fast/node_modules/body-parser/lib/types/json.js:92:19 node_modules/raw-body/index.js:238:16- Internal Node.js modules and line numbers
This reveals the server's filesystem layout to attackers.
curl -v http://localhost:7420/api/generate -X POST 2>&1Result: 404 Not Found — Cannot POST /api/generate
curl -v http://localhost:7420/api/generate 2>&1Result: 404 Not Found — Cannot GET /api/generate
curl -s http://localhost:7420/.envResult: 404 — .env not exposed. Good.
curl -s http://localhost:7420/api/configResult: 200 — Returns Firebase client config:
{
"apiKey": "AIzaSyDu1_UIcdl-r0pdf0IYPXCnYE-Sl5rJbo4",
"authDomain": "ship-fast-saas.firebaseapp.com",
"projectId": "ship-fast-saas",
"appId": "1:359066355652:web:62b67a9496716e4d004691"
}Notes: Firebase client config is intentionally public (required for client-side auth). This is expected behavior, not a bug. The code comment says // Public: Firebase client config.
curl -s http://localhost:7420/api/healthResult: 404 — No health endpoint. Not a bug, but could be useful for monitoring.
curl -s http://localhost:7420/.git/configResult: 404 — .git not exposed. Good.
curl -s http://localhost:7420/package.jsonResult: 404 — Not exposed. Good.
Against /api/generate:
for i in $(seq 1 20); do curl -s -o /dev/null -w "%{http_code}" http://localhost:7420/api/generate -X POST -H "Content-Type: application/json" -d '{"prompt":"test site"}'; echo " request $i"; doneResult: All 20 returned 404 (endpoint doesn't exist).
Against /api/sessions (the real endpoint):
for i in $(seq 1 20); do curl -s -o /dev/null -w "%{http_code}" http://localhost:7420/api/sessions -X POST -H "Content-Type: application/json" -d '{"prompt":"A test site for rate limit verification with enough characters to pass the minimum requirement for prompt length"}'; echo " request $i"; doneResult: All 20 returned 200. No rate limiting triggered.
Analysis: Rate limits are intentionally skipped for localhost/development via isLocalDevelopmentRequest() which returns true when NODE_ENV !== 'production' and the host is localhost. In production (NODE_ENV=production), rate limits ARE enforced:
- Anonymous: daily IP limit (2-3/day), 10-min IP limit, concurrent limit
- Authenticated: monthly cap, 5/10min user limit, IP limit, concurrent limit
Not a production bug, but worth noting that dev mode has zero rate limiting.
curl -v -X OPTIONS http://localhost:7420/api/generate -H "Origin: http://evil.com" -H "Access-Control-Request-Method: POST" 2>&1Result: 404 (endpoint doesn't exist).
Against /api/sessions: Result: 200 OK, but no Access-Control-Allow-Origin header returned.
BUG - MISSING CORS CONFIGURATION: No CORS headers are set on any API response. The OPTIONS preflight returns 200 but without Access-Control-Allow-Origin, Access-Control-Allow-Methods, or Access-Control-Allow-Headers. This means:
- Cross-origin requests from the frontend (if hosted on a different domain) would fail
- However, since the app appears to be SSR (same-origin), this may be by design
- No
corsmiddleware is installed (not found in server code)
The lack of CORS headers also means external sites CANNOT make cross-origin API calls, which is actually good security. However, if the frontend ever moves to a separate domain, this would break.
curl -s http://localhost:7420/api/payments
curl -s http://localhost:7420/api/webhook
curl -s http://localhost:7420/api/stripeResult: All return 404 — Not exposed via GET. Good.
Notes: Payment logic exists in src/server/payments.js but is likely mounted under different routes or only accepts specific methods.
curl -sI http://localhost:7420/Response headers:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Cache-Control: private, no-store, max-age=0, must-revalidate
Pragma: no-cache
ETag: W/"5370-UkshlQFaWqHJDy06kfithv2mluk"
X-Powered-By: Express
X-SF-Home-Source: ssr
BUG - SERVER INFO LEAKAGE:
X-Powered-By: Express— Reveals the server framework. Express recommends disabling this withapp.disable('trust proxy')or usinghelmet. Should useapp.disable('x-powered-by').X-SF-Home-Source: ssr— Custom header exposing rendering strategy. Minor info leak.- No
Strict-Transport-Securityheader — Missing HSTS. - No
X-Frame-Optionsheader — Page can be embedded in iframes (clickjacking risk).
Good: X-Content-Type-Options: nosniff IS present on API routes. Content-Security-Policy is present on error pages. X-Robots-Tag: noindex is on API routes.
curl -s http://localhost:7420/api/gallery
curl -s http://localhost:7420/api/previewResult: Both return 404 — Not exposed. Good.
curl -s http://localhost:7420/api/sessionsResult: {"error":"Unauthorized"} — Properly protected with requireAuth.
curl -s http://localhost:7420/api/sessions/recentResult: 200 — Returns recent sessions data (session IDs, prompts, deployment URLs, timing data). POTENTIAL ISSUE: This endpoint has no auth middleware and returns all recent sessions including prompt text. Could be intentional for a public gallery feature, but exposes user prompts.
curl -s http://localhost:7420/api/credits
curl -s http://localhost:7420/api/subscription-statusResult: Both return {"error":"Unauthorized"} — Properly protected.
curl -s http://localhost:7420/api/early-adopter-statusResult: 200 — Returns {"eligible":true,"slotsRemaining":500,"totalSlots":500,"priceId":"price_1TGmRlP46NVnr2Hth4gNOJdI"}.
INFO LEAK: Exposes Stripe price ID and slot availability. The Stripe price ID itself is semi-public (needed for checkout), but the slot count reveals business metrics.
curl -s http://localhost:7420/api/sessions -X POST -H "Content-Type: application/json" -d '{"prompt":"test"}'Result: 400 — {"error":"Please provide a meaningful description of the website you want to build."}. Good input validation.
| # | Bug | Severity | Details |
|---|---|---|---|
| 1 | Stack trace leak on malformed JSON | HIGH | Full Node.js stack trace with filesystem paths exposed in 400 response body. Fix: add custom error handler for JSON parse errors. |
| # | Bug | Severity | Details |
|---|---|---|---|
| 2 | X-Powered-By: Express header exposed | MEDIUM | Reveals server framework. Fix: app.disable('x-powered-by') or use helmet. |
| 3 | Missing security headers | MEDIUM | No HSTS, no X-Frame-Options, no global CSP. Consider adding helmet middleware. |
| 4 | /api/sessions/recent unauthenticated |
MEDIUM | Exposes recent session prompts and deployment URLs without auth. May be intentional (gallery), but worth reviewing. |
| # | Bug | Severity | Details |
|---|---|---|---|
| 5 | Stripe price ID in /api/early-adopter-status |
LOW | Exposes pricing internals. Minor — price IDs are semi-public. |
| 6 | No CORS middleware | LOW | No cross-origin headers set. Currently safe (same-origin SSR), but would break if frontend is ever separated. |
| 7 | X-SF-Home-Source: ssr header |
LOW | Minor info leak revealing rendering strategy. |
.env,.git/config,package.json— all properly blocked (404)/api/sessions,/api/credits,/api/subscription-status— properly require auth- Rate limiting — works in production mode; correctly bypassed in dev
- Input validation — rejects short/gibberish prompts
- Content policy — checks prompts before generation
- Payment endpoints — not exposed via GET