Living execution plan for Scrutinix. This file reflects the implemented ship state plus the 2026-03-23 public-repo polish follow-up after the rename cleanup.
[ ]not started[-]in progress[x]completed[!]blocked / needs revisit
- Date: 2026-05-01
- Execution status:
P18 completed and verified - Platform:
- Next.js
16.2.4 - React
19.2.x - Node
22 LTS - NDJSON streaming over
fetch
- Next.js
- Architecture:
proxy.tsenforces rate limits on/api/analyzerequest paths.- Node.js route handlers orchestrate eight signals and stream normalized results.
- IndexedDB stores client-only history, export state, and re-scan sources.
- The home page now renders scanner-first: a compact top band with the scan dock and minimal product framing, a calmer two-column operational workspace, a sticky history rail, and a clearly separate support/method section below the functional surface.
- The public site now shares one editorial shell across
/,/about, and/privacy, so the trust, methodology, and privacy surfaces stay visually aligned with the scanner. - The UI now uses the actual pulled shadcn preset
b1D24VYeas its baseline language: neutralradix-miratokens, compact controls, and smaller radii adapted onto the brandedcomponents/scrutinix/*surface. - Dark/light theme tokens stay in
app/globals.css, whileapp/scrutinix.cssis now limited to the lighter motion/effects layer needed for live scan states. - Body typography defaults to Geist Sans, while mono styling is reserved for telemetry, timings, hashes, and other code-like labels.
- Favicons and manifest are now served from checked-in assets under
public/instead of a generatedapp/icon.tsxroute. - Production headers include CSP, permissions policy, referrer policy, and anti-sniff/frame protections.
- Intentional baseline decision:
package-lock.jsondrift from the platform refresh bootstrap was kept intentionally because the project was fully re-scaffolded onto the new dependency graph.
Completed local verification:
npm run lintnpm run format -- --check .npm run typechecknpm run test:unit -- --runnpm run test:integration -- --runnpm run test:e2e -- --grep @smokenpm run buildnpm auditnpm run lighthouse
Completed deployment verification:
npx vercel env ls --scope aman-thanvis-projectsnpx vercel inspect <protected preview deployment> --scope aman-thanvis-projectsHEAD https://www.scrutinix.netPOST https://www.scrutinix.net/api/analyze
Observed results:
- Unit tests:
8files passed,27tests passed. - Integration tests:
2files passed,6tests passed, including batch per-URL failure isolation. - Playwright smoke:
6tests passed, covering legacy history migration, single-scan, batch-scan, accessibility, keyboard navigation, and history clear undo. - Production build: passed with static metadata routes for
/icon,/opengraph-image,/robots.txt, and/sitemap.xml. - Security audit:
0vulnerabilities reported across prod and dev dependencies after the 2026-05-01 dependency refresh. - Lighthouse:
- Performance
0.91 - Accessibility
1.00 - Best Practices
1.00 - SEO
1.00
- Performance
- Vercel preview deployments: protected and verified via
vercel inspect - Vercel production deployment:
Readyathttps://www.scrutinix.net - GitHub repository:
amanthanvi/scrutinixwith updated description, homepage, and public topics - Vercel project: renamed from
malicious-url-detectortoscrutinixand reconnected tohttps://github.com/amanthanvi/scrutinix - Public production API smoke:
POST /api/analyzereturned NDJSON200, streamed all expected events, and produced a safe verdict forhttps://example.com/ - Post-key-sync production smoke: Google Safe Browsing resolved successfully, URLhaus stopped warning once the
Auth-Keyheader fix shipped, and the threat-feed source set was simplified to URLhaus plus the OpenPhish community feed. - Local production smoke:
redirectChainnow returnssuccessforhttps://example.com/, and the scan no longer reports a false partial failure from TLS chain validation. - Fresh local matrix verification on 2026-03-09 confirmed the UI and verdict semantics across
example.com,neverssl.com,expired.badssl.com, and a known-malicious IP sample; clean verdict confidence now drops tomoderatewhen a primary reputation source times out instead of staying misleadingly high.
- Create
PLAN.mdand keep it current. - Replace stale project-local
AGENTS.md. - Update
SPEC.mdwith audit-resolved implementation notes and feasibility corrections. - Keep the regenerated
package-lock.jsonas part of the intentional restart scaffold.
- Upgrade to current stable Next/React stack and pin Node 22 engines.
- Reduce direct dependencies to the set used by the rebuilt app.
- Replace
next lintwith ESLint CLI. - Add
typecheck,format, and fresh metadata/static asset plumbing.
- Add Vitest for unit and integration coverage.
- Add MSW for network-backed integration tests.
- Add Playwright for E2E smoke and UI regressions.
- Add Lighthouse CI for performance/accessibility gatekeeping.
- Centralize env validation and runtime config.
- Define normalized URL parsing and private-network rejection.
- Define result, signal, event, and verdict types.
- Define cache keys, log redaction, and API error contracts.
- DNS enrichment.
- TLS/certificate enrichment.
- Redirect chain enrichment.
- RDAP-backed registration enrichment behind the public
whoissignal name.
- VirusTotal adapter.
- Google Safe Browsing adapter.
- URLhaus adapter.
- OpenPhish cached feed ingestion.
- Remove the deprecated PhishTank path and standardize on the OpenPhish community feed.
- Hosted Hugging Face classifier plus local lexical scorer ensemble.
- Shared orchestration service for all eight signals.
-
POST /api/analyzeNDJSON stream. -
POST /api/analyze/batchNDJSON stream with concurrency cap of3. - Cache-aware short-circuit path with fresh scan IDs on cached hits.
- Final verdict logic that never fabricates threat info on total failure.
- Proxy-based IP rate limiting with Upstash when configured.
- In-memory fallback when shared Redis is unavailable.
- Structured safe logging and timing metadata.
- Documented cache behavior in code and living docs.
- Distinct visual direction and theme tokens.
- Responsive app shell and navigation.
- Metadata, icon, OG image, robots, and sitemap routes.
- Accessible primitives and loading/error skeletons.
- Onboarding/value framing plus trust/privacy disclosure routes.
- Streamed single-scan UI.
- Summary / Full Report toggle.
- Per-signal loading states plus clear full-scan recovery controls.
- Clear differentiation between provider failure and malicious verdicts.
- Batch streaming UI and drill-down details.
- Batch per-URL failure isolation so one broken URL does not kill the rest of the stream.
- IndexedDB history with search, filter, and re-scan.
- Undo path after clearing local history.
- CSV/JSON export for batch and history.
- Optional client-only share links.
- Remove dependency vulnerabilities.
- Add CSP-safe rendering and security headers.
- Refresh educational content into the side-panel guidance copy.
- Run automated accessibility checks plus keyboard smoke tests.
- Resolve the Claude UI audit findings that still applied to the live branch and remove the now-stale audit artifact.
- Run the full local CI-equivalent chain.
- Reconcile
PLAN.md,SPEC.md,AGENTS.md, and user docs. - Validate actual preview and production deployments on Vercel.
- Rename package and repository metadata to
scrutinix. - Update docs and public links to the
Scrutinixname and GitHub slug. - Migrate IndexedDB history from
malicious-url-detector-v2toscrutinix-v2. - Refresh local Git/Vercel wiring to the current repository and project names.
- Refresh the README and add public contributor/security policy docs for the open-source repository.
- Rework theme tokens and branded CSS toward an editorial security-lab direction with calmer light/dark surfaces.
- Replace the boxed home intro with a full-bleed poster hero and inline scanner dock that still fits inside the first viewport.
- Restyle the operational workspace, verdict surface, signal cards, batch console, and sticky history rail without changing scan behavior or contracts.
- Convert
/aboutand/privacyto the shared editorial shell and align their copy with actual logging, history, and share-link behavior. - Re-run lint, typecheck, unit, build, Playwright smoke, and Lighthouse after the UI overhaul.
- Pull and inspect the generated
b1D24VYepreset output instead of relying on an interpreted direction. - Port the preset's neutral token scale, compact
radix-miracontrol language, and smaller radii into the shared primitives and public shell. - Remove the leftover pill, blur, and terminal chrome that was still masking the preset baseline across
/,/about, and/privacy. - Re-run lint, typecheck, build, Playwright smoke, and Lighthouse against the corrected preset-aligned UI.
- Replace the generated icon route with the provided favicon asset set and a normalized
site.webmanifest. - Reduce the home hero to minimal scanner-adjacent framing so the scan dock is dominant on desktop and mobile.
- Move method/privacy explanation into a clearly secondary support section below the operational workspace.
- Tighten short-height and mobile viewport behavior so the scan dock, input, and primary action stay inside the first viewport.
- Update smoke assertions to target stable functional affordances instead of removed marketing copy.
- Re-run lint, typecheck, build, favicon endpoint checks, and Playwright smoke after the cleanup pass.
- Update Next/eslint-config-next, PostCSS, Vitest/Vite, Playwright, Lighthouse, and MSW patch/minor lanes for Node 22 compatibility.
- Add npm overrides for vulnerable transitive leaf packages where upstream parents still pin stale versions.
- Re-run npm install, audit, typecheck, lint, unit tests, integration tests, and production build after the refresh.
- Fix Redis REST env resolution so both Upstash and Vercel KV aliases construct a client explicitly.
- Prevent incomplete partial-failure scan results from being cached as reusable successful results.
- Harden active TLS and redirect probes against private, local, reserved, rebinding, IPv4-mapped, and private NAT64 targets.
- Move runtime result and stream-event boundaries to Zod schemas while preserving the existing sanitizer APIs.
- Split the analyzer client island into smaller runtime, chrome, workspace, history, and footer modules.
- Add a minimal GitHub Actions CI workflow for install, audit, lint, typecheck, unit/integration tests, and build.
- Run focused checks on each branch, external PR review loops, Vercel previews, and a final merged verification pass.
- 2026-03-06: Next.js
16.1.6deprecates themiddleware.tsconvention in favor ofproxy.ts; the rebuilt app follows the new convention while preserving the same request-gating role. - 2026-03-06: Rate limiting must not import the full analysis orchestrator, or the request proxy bundle will inherit Node-only signal modules and fail build-time edge checks.
- 2026-03-06: A fail-closed production proxy made the first live deploy unusable without Upstash credentials; the shipped behavior now degrades to process-local rate limiting and logs a warning instead.
- 2026-03-06: Metadata routes must be owned by the app (
/icon,/opengraph-image,/robots.txt,/sitemap.xml) or build/runtime drift resurfaces quickly. - 2026-03-06: Hugging Face retired
api-inference.huggingface.co; the hosted classifier now usesrouter.huggingface.co/hf-inference/models/...withDunnBC22/codebert-base-Malicious_URLs. - 2026-03-06: Lighthouse on this repo required the same explicit host-bound production start command that succeeded manually:
npm run start -- --hostname 127.0.0.1 --port 3000. - 2026-03-06:
@lhci/clicarried the only remaining open advisories (lodashandtmpvia oldinquirer); replacing it with a directlighthouse+chrome-launcherscript broughtnpm auditback to zero. - 2026-03-06: Vercel preview deployments in this project return
401to anonymous HTTP requests;vercel inspectis the reliable verification path for preview readiness. - 2026-03-06: Vercel KV exposes Upstash-compatible REST credentials as
KV_REST_API_URLandKV_REST_API_TOKEN; the deployed rate-limit config now honors those aliases in addition toUPSTASH_REDIS_REST_URLandUPSTASH_REDIS_REST_TOKEN. - 2026-03-06: URLhaus authorization is strict about the documented header spelling:
Auth-Keyworks andAuthKeyreturns401 Unauthorized. - 2026-03-06: OpenPhish's free community TXT feed is sufficient for the shipped threat-feed role here, so the PhishTank integration was removed instead of carrying a flaky Cloudflare-challenged path.
- 2026-03-06: Redirect tracing should not depend on fetch-level certificate trust, because SSL trust errors are already captured separately by the SSL signal; the shipped tracer now inspects headers through Node HTTP(S) requests with relaxed certificate validation.
- 2026-03-06: Vercel's Node version setting is major-line based;
package.jsonnow pinsengines.nodeto22.xso deploys stay on Node 22 without silently floating to a future major. - 2026-03-09: The shipped UI moved from ad-hoc control styling to selective shadcn/ui primitives (
Button,Input,Textarea,Tabs,Card,Badge,ScrollArea,sonner) without discarding the custom Scrutinix hero, motion, or signal rendering. - 2026-03-09: Tailwind v4 theme tokens now live in
app/globals.css, whileapp/scrutinix.cssis reserved for the branded atmosphere, radar, marquee, and animation layer. - 2026-03-09: Next.js
themeColormetadata must move to theviewportexport on App Router pages, or builds emit warnings. - 2026-03-09: Moving the home route to a server-rendered shell plus smaller client islands pushed the scripted local Lighthouse score back to
0.99after the Scrutinix redesign had regressed it. - 2026-03-09: The public production host now resolves through the custom domain
https://www.scrutinix.net, with the apexhttps://scrutinix.netredirecting there. - 2026-03-09: TLS signal collection was already correctly identifying expired certificates, but the verdict engine initially underweighted that evidence; invalid or untrusted certificates now land in the suspicious band unless stronger evidence moves the result further.
- 2026-03-09: A safe verdict can still be overconfident if a primary reputation source fails; the shipped confidence model now caps clean-result confidence when VirusTotal, Google Safe Browsing, or threat-feed coverage is missing.
- 2026-03-09: The saved Scrutinix UI audit included several findings that were already obsolete on the current branch; treat old audit artifacts as input to reconcile, not as a literal current-state description.
- 2026-03-21: GitHub had already been renamed to
amanthanvi/scrutinix; local remotes and docs were still relying on redirects and stalemalicious-url-detectormetadata. - 2026-03-21: Renaming the IndexedDB database required a one-time browser migration so existing local scan history survives the Scrutinix rename.
- 2026-03-22: The Vercel project rename can be patched through the Vercel projects API, then the local checkout should run
vercel git connectso the linked GitHub repo metadata follows the new slug. - 2026-03-23: Public repo polish still mattered after the rename; the README needed to lead with product value, and the repo needed explicit
CONTRIBUTING.mdplusSECURITY.mdentry points for external users. - 2026-03-23: The public-site redesign is easiest to keep coherent when the hero, scanner dock, workspace, and trust pages all share one token system and shell language; partial restyles drift quickly.
- 2026-03-23: When a redesign is supposed to follow a shadcn preset, pull the generated preset first; matching the real token scale and control density matters more than loosely matching the mood.
- 2026-03-23: The home route works better as a scanner-first dashboard than as a text-heavy hero; keeping the support/method layer below the workspace preserves readability on short laptop windows and mobile screens.
- 2026-03-24: IndexedDB history and streamed NDJSON events need runtime normalization at the client boundary; stale stored entries and malformed upstream payloads can still bypass TypeScript and crash direct
.metadata,.signals,.length,.map, or string-method reads. - 2026-05-01: Next
16.2.4resolves the direct Next advisories but still pins vulnerablepostcss; keep the npmoverridesblock until upstream package pins move past the audited vulnerable leaves. - 2026-05-01: Active network probes must validate every resolved address and pin outbound sockets to the validated public address; checking only the hostname or first DNS answer leaves room for private-address redirects and rebinding.
- 2026-05-01: Cache only complete non-error analysis results; a clean verdict with provider partial failures can otherwise mask upstream outages for the full cache TTL.
- 2026-05-01: Keeping parallel PRs out of
PLAN.mdavoided artificial merge conflicts; use one consolidated plan update after the code branches land.