Date: 2026-08-21
Scope: Auth, XSS, injection, localStorage hardening, dependency risk, secrets hygiene, build config
Context: Public deploy is a pure client-side demo (luxe-disparity-tracker.vercel.app). No API routes, no database, no environment variables.
| Area | Risk | Notes |
|---|---|---|
| Authentication | N/A (by design) | No login; portfolio dashboard |
| Authorization | N/A | No mutating server surface |
| XSS | Low | One first-party dangerouslySetInnerHTML (theme bootstrap). React text escaping elsewhere |
| Injection (SQL) | N/A | No database. Prisma/Supabase were removed |
| localStorage | Low (hardened) | Watchlist / alerts / spend parsed through allow-lists |
| Dependency CVEs | Low | Lean graph — Next, React, Recharts, Lucide, Tailwind utilities only |
| Secrets in repo | None | Zero env vars; .env* gitignored |
| Build config | Hardened | ignoreBuildErrors is false — type errors fail CI/build |
Overall (public Vercel demo): Low residual risk — browser-only snapshot, no backend secrets, no auth boundary to break.
Findings
- Public site requires no login (expected for a public demo).
- Watchlist, alerts, annual-spend, and theme persist in
localStoragekeyed underluxe-tracker:*. - There is no server session, cookie, or token.
Verdict: Auth is intentionally absent. Do not claim the demo is “secured with NextAuth / Supabase Auth”.
If auth is added later: any future mutating API must check a server session; never trust client-only flags or localStorage user ids.
Findings
src/app/layout.tsxinjects a first-party constant theme script viadangerouslySetInnerHTML. The string is authored in-repo, contains no user input, and only readsluxe-tracker:theme(dark/light) orprefers-color-scheme.- Brand names, SKUs, notes, watchlist labels, and alert messages render as React text nodes → default escaping.
- No Markdown / MDX / HTML sanitizer in the dependency graph.
Hardening applied
- Theme bootstrap remains a static IIFE; it is not interpolated with storage values beyond an allow-listed
'dark' | 'light'check. - Watchlist / alert payloads are allow-listed before they enter React state (
src/lib/validation.ts).
Untrusted JSON in localStorage is the only writeable input on the public demo (extensions, shared-device DevTools, pasted payloads).
Hardening applied (src/lib/validation.ts)
- Watch type allow-list:
product·brand·region - Alert type allow-list:
price_drop·launch_reminder·stock_change·arbitrage - Brand / region allow-lists match the seeded snapshot
- Product ids must match
prod_<n> - Entry ids restricted to
[A-Za-z0-9:_-] - Control characters stripped; string lengths capped
- Arrays capped at 50; unknown keys dropped;
__proto__/ non-objects rejected - Annual spend clamped to
[0, 10_000_000]
Malformed payloads hydrate as empty / default rather than crashing the dashboard.
Findings
- No SQL, no ORM, no child processes, no server actions.
- Analytics are pure functions over
src/lib/data-snapshot.ts(mulberry32 seed0xc0ffee).
Verdict: N/A on the public path.
Runtime:
next,react,react-domrecharts,lucide-reactclsx,class-variance-authority,tailwind-merge
No NextAuth, Prisma, Supabase client, Markdown, or unused Radix/shadcn widgets. Nothing to drop in this pass.
bun audit (2026-08-21) reports residual advisories in Next 15.5.19 (SSRF / DoS / cache-confusion on Server Actions, Image Optimization, rewrites — this app has no Server Actions, no rewrites, no image optimizer usage) and transitive sharp / postcss. Dev-only: eslint → brace-expansion / js-yaml. Majors are ignored by Dependabot; patch/minor upgrades (e.g. Next 15.5.21) will arrive as grouped PRs.
bun install
bun auditFindings
.gitignoreexcludes.env,.env*.local,.vercel, logs.package.jsonscripts andREADME.mdstate zero environment variables.- Vercel production is a static-data Next.js app; the live URL stays green without secrets.
| Endpoint | Auth | Notes |
|---|---|---|
Dashboard / |
None | Client-side snapshot + 17 panels |
| API routes | None | No src/app/api |
next.config.ts: typescript.ignoreBuildErrors is false. reactStrictMode is off (intentional — snapshot is built once at module load).
Accepted for portfolio demo
- No user authentication on the public site.
- Synthetic brand names / prices (not live boutique feeds).
- Theme script uses
dangerouslySetInnerHTMLfor zero-FOUC (first-party only).
Not accepted
- Shipping a backend later without auth on mutations.
- Re-enabling
ignoreBuildErrors. - Parsing
localStoragewith rawJSON.parseinto React state.
- Done: SECURITY.md.
- Done: Watchlist / alerts / spend allow-lists.
- Done: Unit tests (
bun test). - Done:
ignoreBuildErrors: false(already set). Lean deps (already set). - Done: GitHub Dependabot (majors ignored) + Playwright e2e on CI.
bun install
bun test
bun run typecheck
bun run test:e2e
bun run auditTo report a vulnerability, open a GitHub security advisory or an issue. There are no production secrets to rotate.