# SnapAPI Bug Tracker

## Fixed

### BUG-001: Key cache not shared across pods (HIGH) — FIXED v0.1.1
- **Found:** Session 2
- **Impact:** ~50% of screenshot requests fail with 403 after signup when 2+ replicas
- **Fix:** Cache-aside pattern — check DB when key not in memory cache
- **Verified:** 6/6 requests succeed after signup on 2-replica prod deployment

### BUG-004: CSP blocks all inline event handlers (CRITICAL) — FIXED v0.2.3
- **Found:** Session 4 (investor-reported)
- **Impact:** ALL interactive functionality broken — signup modal, playground, mobile nav, FAQ toggles
- **Root cause:** Helmet's default CSP includes `script-src-attr 'none'` which blocks onclick/onsubmit handlers
- **Fix:** Added `scriptSrcAttr: ["'unsafe-inline'"]` to helmet CSP config
- **Verified:** Signup modal opens, API key generated, playground takes screenshots, mobile nav works

### BUG-005: Mobile nav has no CSS for .show class (HIGH) — FIXED v0.2.3
- **Found:** Session 4 (investor-reported)
- **Impact:** Hamburger menu toggle did nothing — nav links invisible on mobile
- **Root cause:** CSS had `display:none` for `.nav-links` on mobile but no rule for `.nav-links.show`
- **Fix:** Added `.nav-links.show{display:flex; flex-direction:column; ...}` with absolute positioning
- **Verified:** Hamburger opens/closes menu, links visible and clickable

### BUG-006: Signup buttons use href="#" causing scroll (MEDIUM) — FIXED v0.2.3
- **Found:** Session 4 (investor-reported)
- **Impact:** Clicking "Get Free API Key" scrolled to top instead of opening signup modal
- **Root cause:** `href="#"` navigated before onclick could fire (CSP was the bigger issue, but this was also broken)
- **Fix:** Changed all signup links to `href="javascript:void(0)"`
- **Verified:** All 4 signup CTAs correctly open the modal

### BUG-002: No email verification on signup (MEDIUM) — CLOSED v0.3.0
- **Impact:** Anyone can create unlimited keys with fake emails
- **Resolution:** Free signup removed entirely. Playground is the free demo (no keys needed). Paid signup will go through Stripe.

### BUG-003: No API key recovery (MEDIUM) — CLOSED v0.3.0
- **Impact:** Lost API key = create new account
- **Resolution:** Free tier removed. Paid tier key recovery will be handled via Stripe customer portal.

### BUG-008: FAQ accordion double-toggle (MEDIUM) — FIXED (staging)
- **Found:** Session 10 (investor-reported)
- **Impact:** FAQ items don't expand/collapse — clicking toggles class twice (once via inline onclick, once via JS addEventListener), netting zero change
- **Root cause:** Inline `onclick="this.parentElement.classList.toggle('open')"` on `.faq-q` AND a `querySelectorAll('.faq-q').forEach(addEventListener('click',...))` in the script block — both fire on click, double-toggling
- **Fix:** Removed the duplicate JS addEventListener block, keeping only inline onclick
- **Deployed to staging:** Yes (commit db0d4eee). Verified via kubectl exec + curl.
- **Production:** Awaiting investor approval for v* tag

### BUG-009: "Rate limiting" advertised as feature (LOW) — FIXED (staging)
- **Found:** Session 10 (investor-reported)
- **Impact:** "Secure by Default" feature card mentioned "Rate limiting" as a selling point — rate limits are constraints, not features
- **Fix:** Changed to "Strict input validation and secure defaults built in"
- **Deployed to staging:** Yes (commit db0d4eee)
- **Production:** Awaiting investor approval for v* tag

### BUG-010: Privacy Policy page returns 404 (CRITICAL) — FIXED (staging)
- **Found:** Session 12 (investor-reported)
- **Impact:** /privacy returns 404 — legal pages broken. Footer links on terms/impressum/privacy pages used `/privacy` without `.html` extension.
- **Root cause:** express.static only serves files by exact filename; no route handler for extensionless URLs
- **Fix:** Added 301 redirects for `/privacy`, `/terms`, `/impressum`, `/status` → `.html` variants. Also fixed all internal links to consistently use `.html` extension.
- **Deployed to staging:** Yes (commit db1fa8d). Full link crawl: zero 404s across all pages.
- **Production:** Awaiting investor approval for v* tag

### BUG-012: /health reports hardcoded version 0.1.0 (LOW) — FIXED (staging)
- **Found:** Session 27
- **Impact:** Health endpoint shows wrong version, makes monitoring/debugging harder
- **Fix:** Read version from package.json dynamically
- **Deployed to staging:** Yes (commit 2eca4e7, image imported to all nodes). Verified: /health returns version 0.6.0

### BUG-013: OpenAPI spec stale — missing GET /v1/screenshot, /v1/usage; has removed /v1/signup/free (MEDIUM) — FIXED (staging)
- **Found:** Session 27
- **Impact:** API docs misleading for developers
- **Fix:** Removed /v1/signup/free, added GET /v1/screenshot, added /v1/usage
- **Deployed to staging:** Yes (commit 2eca4e7). Verified: OpenAPI spec has correct paths

### BUG-014: /usage.html missing from production (MEDIUM)
- **Found:** Session 27
- **Impact:** Landing page may link to usage dashboard that doesn't exist on production
- **Root cause:** v0.5.2 image was built before usage dashboard commit (5b59a7a)
- **Fix:** Needs new production deploy after staging verified

### BUG-016: Free signup route still mounted in production (HIGH) — FIXED (staging)
- **Found:** Session 53 (self-discovered)
- **Impact:** /v1/signup/free still active on production — anyone can generate unlimited free API keys despite free tier being "removed" in v0.3.0
- **Root cause:** Route was removed from pricing/landing page but `signupRouter` was never unmounted from `index.ts`
- **Fix:** Removed signup import + route registration, deleted dead `signup.ts` file, added test verifying 404. Cleaned up leaked test key + verified 5 remaining keys are all QA artifacts.
- **Deployed to staging:** Yes (commit f3a363f, image imported to w1+w2). Verified: 404 on staging.
- **Production:** ⚠️ STILL VULNERABLE — needs production deploy (v* tag) to fix. This is a security issue.

### BUG-017: Cancelled subscriptions get free tier (100 req/mo) instead of 0 (MEDIUM) — FIXED (staging)
- **Found:** Session 54 (self-discovered)
- **Impact:** Cancelled customers get downgraded to 'free' tier with 100 requests/month forever — free tier was removed in v0.3.0
- **Root cause:** `downgradeByCustomer()` set tier to `'free'` which still had a 100-request limit
- **Fix:** Added `'cancelled'` tier with 0 limit. `downgradeByCustomer()` now sets to `'cancelled'`.
- **Deployed to staging:** Yes (commit 9575d31). 338 tests passing.
- **Production:** Awaiting investor approval for v* tag

### BUG-018: Recovery endpoint logs full API keys (LOW) — FIXED (staging)
- **Found:** Session 54 (self-discovered)
- **Impact:** Full API keys logged during recovery requests — potential key theft via log access
- **Fix:** Removed `key` field from logger call, now only logs email
- **Deployed to staging:** Yes (commit 9575d31)

### BUG-019: No rate limiting on billing endpoints (MEDIUM) — FIXED (staging)
- **Found:** Session 54 (self-discovered)
- **Impact:** Checkout, portal, and recovery endpoints had no rate limiting — could be spammed
- **Fix:** Added IP-based rate limit (10 req/15min) to billing router, excluding webhook endpoint
- **Deployed to staging:** Yes (commit 9575d31)

## Open

### BUG-015: Python SDK missing URL validation for ScreenshotOptions object (LOW) — FIXED
- **Found:** Session 31
- **Impact:** When using `ScreenshotOptions` with empty URL, SDK makes HTTP request instead of raising ValueError immediately
- **Fix:** Added `if not body.get('url'): raise ValueError("url is required")` after options.to_dict()
- **TDD:** Failing test written first, then fix applied. 17/17 Python SDK tests pass.
- **Deployed:** Commit 195a656 pushed to main

### BUG-007: Simultaneous browser restart causes QUEUE_FULL (HIGH) — FIXED (staging)
- **Found:** Session 9
- **Impact:** When both browser instances hit RESTART_AFTER_MS simultaneously (every ~1h), all requests during the ~4s restart window queue for 30s then fail with 503 QUEUE_FULL
- **Root cause:** Both browsers initialized with same `lastRestartTime`, hit restart threshold together. `acquirePage()` triggers `scheduleRestart()` on both simultaneously.
- **Fix:** Staggered initial `lastRestartTime` per browser + one-at-a-time restart guard in `acquirePage()`
- **Deployed to staging:** Yes (commit e49c4073). Verified playground returns 200.
- **Production:** Awaiting investor approval for v* tag

### BUG-011: No URL length limit (HIGH) — FIXED (staging)

## Open

### BUG-020: /status returns 200 instead of 301 redirect (LOW) — FIXED (staging)
- **Found:** Session 65b QA (2026-03-06)
- **Impact:** Inconsistent clean URL behavior, SEO duplicate content
- **Root cause:** `statusRouter` mounted before redirect loop intercepted the request
- **Fix:** Removed statusRouter; existing redirect loop handles /status → /status.html
- **TDD:** Failing test first (expect 301), then implementation
- **Deployed to staging:** Yes (session 66, pending deploy verification)
- **Production:** Awaiting investor approval for v* tag

### BUG-021: Long URL validation untestable via playground due to shared rate limit (LOW) — FIXED (staging)
- **Found:** Session 65b QA (2026-03-06)
- **Impact:** Invalid URL requests consumed rate limit quota before validation
- **Fix:** Added URL validation middleware before rate limiter in playground route chain
- **TDD:** Failing test first, then implementation
- **Deployed to staging:** Yes (session 66, pending deploy verification)
- **Production:** Awaiting investor approval for v* tag
- **Found:** Session 17 QA
- **Impact:** Extremely long URLs could waste server resources
- **Fix:** Added 2048 character URL length limit in SSRF validation (returns 400)
- **Deployed to staging:** Yes (commit 5ec8c92). Verified: 2100-char URL returns 400, normal URL returns 200.
- **Production:** Awaiting investor approval for v* tag
