Loading report…
← nittim
[](https://nittim.com)Critical issues block production use. Do not ship as-is.Serious problems block using this for real users. Don't make it live as-is.Fix these serious problems before shipping.
Your code scores 54. One blocker stands between it and shipping — the Not Safe for Production verdict above holds until it is resolved.
Resolving this blocker is necessary but not sufficient: even setting it aside, at 54 this code sits in the High Risk band on its own and needs broader work before it is ready to ship. Fix this blocker first — it forces the Not Safe for Production verdict outright — then keep going.
JSON Web Token committed to the repository
src/integrations/supabase/client.ts:6Small (hours)package.jsondepends on react-router-dom ^6.26.2; OSV scan reports react-router@6.27.0 and @remix-run/router@1.20.0 with GHSA-2w69-qvjg-hvjx (XSS via open redirects), GHSA-9jcx-v3wj-wh4m and GHSA-337j-9hxr-rhxg. These are production (shipped) dependencies. Reachability was not verified — this is a version match against OSV advisories, not a confirmed exploit path in this app's own code.Full coverage — every eligible source and configuration file was audited.
5 other files aren’t code this audit reads — images, lockfiles, generated output, and similar non-source content.
Dependency manifests: 1 package root found, 1 scanned.
nihongo-blocks ("Grammar Quest") is a Lovable-generated Vite + React + TypeScript single-page app that teaches Japanese grammar/vocabulary, backed entirely by Supabase (auth, profiles, vocabulary, quiz, daily-challenge tables). There is no server-side code in the snapshot: all data access happens client-side through the Supabase JS client, so the app's entire security and privacy posture rests on Supabase Row-Level Security policies that are NOT present in this snapshot and cannot be verified. The app collects personal data (email, chosen username, learning progress, streaks) with no visible consent, retention, deletion, or data-subject-rights mechanism. The committed Supabase key is the anon/publishable key (designed for client exposure), so it is not a true secret leak — but its safety depends entirely on RLS being correctly configured. Dependency scanning found production-reachable advisories including a React Router XSS-via-open-redirect (HIGH) and ws memory-exhaustion DoS. Code quality is typical of unreviewed AI generation: TypeScript strictness disabled, duplicated components/hooks, large hand-inlined data files, no tests, no CI, no observability, happy-path error handling, and a forced window.location.reload(). The codebase carries little novel, defensible IP. Overall this is an early-stage hobby/learning project that is not production-ready without RLS verification, a privacy/compliance baseline, dependency upgrades, and basic testing/observability.
src/pages/Auth.tsxcollects email, password and username; src/contexts/AuthContext.tsx stores username in user metadata; the profiles table (src/integrations/supabase/types.ts) stores username and activity dates. No privacy policy, consent capture, or terms link appears anywhere in the audited snapshot (Auth.tsx, index.html, README.md).src/integrations/supabase/client.tscreates a browser client with the anon key; hooks such as src/hooks/useUserProgress.ts, src/hooks/useComprehensiveVocabulary.ts and src/services/challengeService.ts read/write profiles, user_vocabulary_progress, user_lesson_progress and daily_vocabulary_challenges directly with .eq('user_id', user.id) filters supplied by the client. No server/edge function or RLS policy is present in the audited snapshot.src/pages/Auth.tsxsets minLength={6} on the password input only; signUp in src/contexts/AuthContext.tsx passes the password straight to supabase.auth.signUp with no strength validation.src/integrations/supabase/client.ts:6hardcodes SUPABASE_PUBLISHABLE_KEY (a JWT with role "anon"), flagged by the secret scanner. The file header notes it is auto-generated; the anon key is intended to be shipped in client code.src/integrations/supabase/client.tspoints to a Supabase-hosted project (https://bwfgdxztmrfgzenugeye.supabase.co); index.html loads https://cdn.gpteng.co/gptengineer.js on every page and preconnects to Google Fonts. No subprocessor disclosure, DPA reference, or EU/EEA transfer mechanism (SCCs/adequacy) is documented.src/components/VocabularyMode.tsxcalls window.location.reload() after completing vocabulary; many async flows only console.error on failure (e.g. useUserProgress.ts fetchProfile, challengeService.ts) with no user-facing recovery. No React error boundary is present in src/App.tsx.tsconfig.jsonand tsconfig.app.json set strict:false, strictNullChecks:false, noImplicitAny:false, noUnusedLocals/Parameters:false; eslint.config.js disables @typescript-eslint/no-unused-vars; hooks use any[] extensively (useComprehensiveVocabulary.ts state is any[], useQuizzes questions typed loosely, ExpandedGrammarLesson uses staticLesson: any).src/components/DailyVocabularyChallenge.tsxgetCurrentWord() falls back to substring matching on an ID split ('wordId.split('_').pop()') against japanese/romaji/english fields; ExpandedGrammarLesson.tsx renders a 'practice' section that is a literal placeholder ('Practice exercises will be added here!'); ProgressTree.tsx merges hardcoded static lessons with DB rows.src/components/VocabularyCard.tsxand EnhancedVocabularyCard.tsx duplicate card logic; src/hooks/useVocabulary.ts and useComprehensiveVocabulary.ts both fetch vocabulary_words; src/data/comprehensiveVocabulary.ts, expandedVocabulary.ts, lessonData.ts, expandedLessonData.ts, advancedLessons.ts and quizData.ts hold large overlapping static datasets that also exist in the database schema.src/utils/challengeAlgorithms.tsand src/services/challengeService.ts implement difficulty adjustment from recent completion rate, review-word selection for items scored <0.7, and new-word rotation avoiding recently used words — in a PUBLIC repo.Weighted blend of 4 dimensions → ~54/100. Dimensions are weighted by importance (Security and Privacy weigh heaviest). Published figures are approximate; nittim's exact weighting and precise scoring are proprietary, while the verdict is computed on exact internal values.4 categories combine into ~54/100. Some categories count for more than others (Security and Privacy count the most). The published numbers are approximate; nittim's exact math is kept private, while the verdict itself is worked out from the exact internal numbers.Weighted blend of 4 dimensions → ~54/100 (Security and Privacy weigh heaviest). Published figures are approximate; nittim's exact weighting is proprietary. The verdict runs on exact internal values.
IP Protection is an independent IP-defensibility lens and is excluded from this blend.IP Protection (checking whether your ideas are exposed) is judged separately and isn’t
part of this combined number.IP Protection is judged separately, excluded from this blend.
Scores are a judgement, not a measurement: a re-audit of the same commit can land a few
points either side of this figure. The evidence underneath it is deterministic — the same
commit always yields the same committed-secret and vulnerable-dependency findings.Scores are a judgment call, not an exact measurement: checking the same saved snapshot of
your code twice can land a few points either side of this number. The evidence underneath
it is not — the same snapshot always turns up the same passwords-in-code and known-security-bug
findings.Scores are a judgment, not a measurement — a re-audit can land a few points either side. The evidence underneath is deterministic: the same commit always yields the same secret and vulnerable-dependency findings.
Each score is the model's overall assessment of the dimensions it covers, across everything it reviewed — not a tally of the findings in this report. Findings are the subset worth acting on, so a score can sit above or below what the listed findings alone would suggest.Each score is the AI's overall judgment of everything it reviewed in that category — not a
count of the problems listed in this report. The listed problems are the ones worth acting
on, so a score can sit above or below what they alone would suggest.Each score is the model's overall assessment across everything it reviewed — not a tally of this report's findings. A score can sit above or below what the findings alone suggest.
nittim guardrail — nittim's safety check — nittim guardrail — The model proposed “high_risk”; nittim's deterministic layer set the final verdict to “not_safe” because of a committed production secret. The written rationale below reflects the model's proposal — the verdict above is the one that governs.
Verdict rationale — Why this verdict — Verdict rationale — The application handles real user accounts and personal data (email, username, learning history) but its entire authorization model is delegated to Supabase RLS policies that are not present in the audited snapshot and cannot be verified — a client-only architecture where anyone holding the bundled anon key can reach whatever RLS allows. Combined with no privacy/compliance baseline (no consent, retention, or deletion path), production-reachable dependency advisories including a React Router XSS-via-open-redirect, and the absence of tests, CI, error handling and observability, this is not safe to run in production as-is. None of the issues are individually catastrophic and the app is small in scope, so it is high_risk rather than not_safe: verifying and committing RLS policies, adding a privacy baseline and deletion path, upgrading vulnerable dependencies, and introducing basic testing/observability would move it toward ready. The IP and AI-authorship assessments (high AI-generation likelihood, little defensible IP) are independent lenses and did not affect this verdict.