Skip to content

Repository files navigation

Audityxe

This is the original work of Zelvior, open-sourced at github.com/zelvior/audityxe. Licensed under the custom Audityxe Custom Open-Source License — free to use, modify, and redistribute, provided Zelvior is always credited as the original author with a link back to the canonical repository above. The Software is provided as-is, with no warranty, and Zelvior is not responsible for anything arising from its use. See LICENSE.md for the full, binding terms before forking, deploying, or redistributing this project.

Instant site audit & Pro promo kit — with a live, deterministic scoring engine, a real browser-rendered performance/accessibility pass, evidence attached to every finding, full public methodology, and a privacy-first design that stores nothing about your audit beyond the minimal score+date record used to power the embeddable badge.

Setup

npm install
cp .env.example .env.local   # fill in Firebase Admin creds, GitHub OAuth app, etc.
npm run dev

Auditing requires a signed-in, email-verified account. See Authentication & accounts below before running locally, or /api/audit will reject every request with a 401/403.

🛡️ System Trust & Verification Signals

This section contains live status indicators, security scores, and architectural specifications for the standalone audit engine.


Infrastructure & Security Verification

Metric Provider Live Status Badge
Uptime Status UptimeRobot Audityxe Status
SSL/TLS Security Qualys SSL Labs Qualys SSL Labs Grade A+
Security Score Mozilla Observatory Mozilla HTTP Observatory Grade
Domain Risk Audit OMNIntel OMNIntel security verdict for audityxe.vercel.app
Domain Security Grade Webscan Radar Webscan Radar Security Grade

⚡ Engine Performance & Architecture

Audit Speed Engine Type Zelvior Runtime (Custom) Next.js


🔒 Privacy & Infrastructure Guarantees

Privacy Promise Zero Trackers Deployed on Vercel

See Privacy — audits aren't stored (with one narrow exception) below for exactly what "minimal retention" means.


🔗 Public Transparency Reports

Security Note: All scans run statelessly against public HTTP response headers and site manifests. No server access or internal code execution is permitted during testing.


Embed the OMNIntel badge

<a href="https://omnintel.net/scan/audityxe.vercel.app" target="_blank" rel="noopener">
  <img src="https://omnintel.net/api/badge/audityxe.vercel.app.svg" alt="OMNIntel security verdict for audityxe.vercel.app" width="320" height="84" />
</a>

Embed the Webscan Radar badge

<a href="https://webscan-radar.com">
  <img src="https://webscan-radar.com/badge/audityxe.vercel.app"
       alt="Webscan Radar Security Grade">
</a>

Embed the UptimeRobot status badge

<a href="https://stats.uptimerobot.com/PHQOGeVpYz?utm_source=status_badge&utm_medium=referral" target="_blank" rel="noopener"><picture><source media="(prefers-color-scheme: dark)" srcset="https://badge.uptimerobot.com/psp/f6267da51916d8f2a4d06001bac44cba.svg?style=logo&theme=dark"><img src="https://badge.uptimerobot.com/psp/f6267da51916d8f2a4d06001bac44cba.svg?style=logo&theme=light" alt="Audityxe"></picture></a>

"Audited by Audityxe" badge for your own site: generate one at /badge — it links back to our live re-audit flow rather than making a static, unverifiable claim.

Pages

Route Purpose
/ Landing + audit tool (gated behind sign-in + verification) + trust/differentiation section
/login, /register Auth (email/password, Google, GitHub)
/verify-email Dedicated email-verification flow, with spam-folder guidance
/forgot-password Real password reset via Firebase, enumeration-safe (never reveals whether an email is registered)
/account Plan, usage, expiry
/settings Profile, password, linked providers, data export, account deletion
/pricing Plans, dynamic local-currency pricing, manual upgrade flow
/bulk Pro-only bulk audit (up to 20 URLs) with CSV export
/methodology Full transparency on what's measured, how, and its limits
/faq Common questions, answered honestly
/sample-report A real, live, unedited audit — regenerated hourly
/about, /contact, /privacy, /terms, /cookies, /disclaimer Standard/legal pages

Authentication & accounts

Audityxe requires sign-in to use the audit tool at all — there is no anonymous/guest path. Three sign-in methods, each on their own page: /register and /login (email/password, Google, or GitHub).

Firebase Auth (client SDK) handles identity. Every authenticated request to /api/audit, /api/account, /api/settings, and /api/audit/bulk sends the user's Firebase ID token as Authorization: Bearer <token>; the server verifies it with the Firebase Admin SDK (lib/auth-server.ts) before doing any work.

Email verification required before running audits

New email/password accounts get a verification email on sign-up (sendEmailVerification). Until it's clicked, /verify-email blocks the audit form and offers "Resend email" / "I've verified — check again" (the latter calls Firebase's reload() so the app notices without a logout/login), and explicitly tells the user to check their spam/junk folder. This is enforced server-side, not just in the UI: /api/audit and /api/audit/bulk call requireAuth(req, { requireEmailVerified: true }), which checks the email_verified claim on the decoded ID token and rejects with a 403 (code: "EMAIL_NOT_VERIFIED") if it's false. Google and GitHub sign-ins are effectively pre-verified by their provider in almost all cases.

Retention — keeping Firebase free of abandoned signups

Two mechanisms, both free:

  1. No Firestore record until verified. ensureUserDoc() (lib/rate-limit.ts) deliberately does nothing for an unverified account — the users/{uid} document (plan, usage counters) is only created the first time a request comes in from a verified account. An account that never verifies never accumulates any Firestore footprint at all.
  2. Scheduled cleanup of stale unverified Firebase Auth accounts. /api/cron/cleanup-unverified, wired to run daily via Vercel Cron (vercel.json, free on Hobby), deletes any email/password account that was created more than 7 days ago and still hasn't verified. Federated (Google/GitHub) accounts are never targeted. Protected by CRON_SECRET — see .env.example.

We can't prevent the Firebase Auth record from being created at sign-up time (that's how Firebase's client SDK works — building a fully custom pre-verification flow would require running our own transactional email infrastructure, which isn't free), but between these two mechanisms, an abandoned signup leaves no lasting trace after a week.

OAuth reliability (Google & GitHub)

Google/GitHub sign-in tries a popup first, and automatically falls back to a full-page redirect if the popup is blocked, closed, or fails for reasons unrelated to your Firebase config — this covers browsers/ webviews that block third-party popups even when everything is configured correctly on the console side.

Required setup in the Firebase Console

  1. Authentication → Sign-in method → enable Email/Password, Google, and GitHub.
  2. For GitHub: create a GitHub OAuth App (see .env.example for the exact callback URL) and paste its Client ID/Secret into the GitHub provider config in the Firebase Console.
  3. Authentication → Settings → Authorized domains → add every domain you'll sign in from.
  4. Project settings → Service accounts → generate a private key and fill FIREBASE_PROJECT_ID / FIREBASE_CLIENT_EMAIL / FIREBASE_PRIVATE_KEY in .env.local.
  5. Firestore Database → create a database, then deploy firestore.rules (denies all direct client access — every read/write goes through the Admin SDK server-side).

Settings

/settings (app/settings/page.tsx) — every control is fully wired to a real backend:

  • Profile — display name, updated via the Firebase client SDK and mirrored server-side.
  • Security — shows linked sign-in methods; password change re-authenticates with the current password before calling updatePassword (federated-only accounts see an explanatory message instead).
  • Plan & billing — links to /account and /pricing.
  • Data & privacy — "Export my data" downloads your profile + usage snapshot as JSON. "Delete my account" removes the Firestore users/{uid} and usage/{uid} docs server-side first, then deletes the Firebase Auth account client-side — ordered so a failure never strands an account that can't reach its own data to retry.

There is no tone/personality selector — a single, direct verdict is generated for every audit; a constructive/roast toggle was tested and added nothing useful, so it was removed.

Plans, pricing & rate limiting

Every account is created on the Free plan by default (lib/plans.ts). Limits are enforced server-side, per account, atomically, via a Firestore transaction (lib/rate-limit.ts) — never trust-the-client:

Plan Daily audits Competitor comparison 30-day price 365-day price
Free 3 No $0 $0
Standard 20 Yes $5 $39
Pro 50 Yes $12 $99

Prices are defined in USD in lib/plans.ts and converted to the visitor's local currency client-side on /pricing using two free, keyless public APIs — ipapi.co for geolocation and exchangerate-api.com's open endpoint for the live conversion rate.

Manual upgrade flow (no payment gateway wired up)

There's no Stripe/PayPal integration — upgrades are approved manually: a signed-in user picks a plan/duration on /pricing, which opens a pre-filled mailto: with their account details; they attach a payment screenshot and send it. To approve, open Firebase Console → Firestore → users/{uid} and set plan and planExpiresAt (an ISO date 30 or 365 days out). Access reverts to Free automatically the moment planExpiresAt passes — checked live on every request, no cron needed for this part.

Firestore layout: users/{uid} (plan, planExpiresAt, displayName, createdAt — created only once verified, see Retention above) and usage/{uid} (date, count).

Environment variables

See .env.example for the full commented list. Highlights:

Variable Purpose
FIREBASE_PROJECT_ID / FIREBASE_CLIENT_EMAIL / FIREBASE_PRIVATE_KEY Required. Admin SDK service account.
GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET GitHub OAuth App (configured in Firebase Console).
CRON_SECRET Protects the retention cleanup cron job. Free — generate any random string.
IP_HASH_SALT Optional. Salts the anonymous per-IP daily-quota hash (see lib/ip.ts). Falls back to a fixed built-in string if unset.
GOOGLE_SITE_VERIFICATION Optional. Emits the google-site-verification meta tag for Search Console. The HTML-file verification method is also supported via public/google*.html.
PAGESPEED_API_KEY Optional. Free (no billing) key for a higher quota on the real browser-rendered audit. Works keyless at a lower rate limit. Pro users can also set their own key in Settings (BYOK) for an even higher personal weekly cap — see below.
GEMINI_API_KEY (+ per-task variants) Optional. Powers the written verdict/promo/banner copy. Falls back to a rule-based generator if unset or unavailable — the app always returns a complete result either way.

Failover behavior

Any GEMINI_API_KEY* variable accepts a comma-separated list of keys, tried in order on failure, with a model-fallback chain per key. If every key for a task fails or none is configured, that task falls back to the deterministic generator in lib/analyze.ts.

BYOK (bring your own key) — Settings page

Pro users can add two independent keys in Settings, each encrypted (AES-256-GCM) before being stored in Firestore and only decrypted server-side at request time:

Key Unlocks Weekly PSI cap without it → with it
AI key (aiApiKey / aiBaseUrl / aiModel) Promo copy + banner generation (Pro-only; runs on the user's own AI spend, not Audityxe's). 1 → 5
PageSpeed Insights key (psiApiKey) Raises the user's own weekly PSI (Lighthouse) audit cap on top of the shared PAGESPEED_API_KEY. 1 → 10 (5 if only the AI key is set)

Both are managed via GET/PATCH /api/settings and never returned to the client in full — only a masked last-4 preview.

What gets analyzed

Every score, fix, and finding is computed live — nothing requires any paid API, and nothing is mocked or random.

6 top-level score categories (result.categories)

Messaging & Copy Clarity, UI/UX & Visual Hierarchy, Conversion Rate Optimization, Technical & Metadata Health, Brand Distinctiveness, Security & Performance.

23-area deep audit breakdown (result.modules)

SEO, Performance, Security Headers, SSL/TLS Certificate (live TLS handshake), Email Authentication (live DNS SPF/DKIM/DMARC, including SPF DNS-lookup-limit counting against RFC 7208's hard cap of 10), Server Hardening (dangerous HTTP methods, exposed config files, directory listing), DNS Security & Zone Health (CAA records, DNSSEC, dangling CNAME/subdomain takeover detection, MX records, nameserver provider-diversity, SOA presence), Subresource Integrity & Source Maps (cross-origin asset integrity, exposed .js.map files), Accessibility, Mobile Responsiveness, UX/UI, AI-Generated / Vibe-Coded Pattern Detection (judged on combined signal severity, not any single occurrence), Technical Stack (CMS + version, JS framework, CSS framework, e-commerce platform, page builder, A/B testing tool, cookie-consent tool, hosting provider, and a confidence rating based on how many independent signals agree), HTML Structure, Meta Tags, Sitemap & Robots.txt, Structured Data, Broken Links, Image Optimization, Third-Party Scripts, Social Metadata, Basic Monetization Setup, and Browser-Rendered Audit (real Chrome, via PageSpeed Insights — see below).

Every finding also carries its own severity (critical / high / medium / low), independent of the module's aggregate score — a module's numeric score is a weighted deduction based on those severities, while its status ("good" / "warning" / "critical") is driven by the worst severity present, not by the score. A module can score a 7 and still be flagged critical because one finding (an exposed .env file, an expired certificate, HTTPS entirely absent) is severe enough on its own to warrant it.

Generated fixes are stack-aware, not generic: the same "X-Powered-By is exposed" finding produces a different, correct fix depending on what the Technical Stack module actually detected (php.ini/.htaccess for WordPress/PHP, next.config.js for Next.js, app.disable() for Express, web.config for ASP.NET, a proxy-level strip as the honest fallback when the framework can't be conclusively identified). The Content-Security-Policy fix is generated the same way: it's built from the actual third-party script/style/image/frame/font origins found in the page's HTML, not a fixed boilerplate policy.

Objective checks (WCAG-adjacent structure, security headers, TLS correctness, font-family limits) score every site the same way. Contextual/heuristic checks (the vibe-coded signals) are judged by combined severity, never a single occurrence. Personal design preferences — specific colors, spacing scale, layout taste — are never imposed as universal scoring rules.

Real evidence on every finding

Every fix (result.fixes) and every module finding (result.modules[].findings[].evidence) carries concrete, checkable proof — the exact URL fetched, the HTTP status returned, or a literal count of elements found — not just an assertion. If a fix says your sitemap is missing, the evidence field states exactly which URL was requested and what (if anything) came back.

Real browser-level auditing (free)

Rather than bundling a headless Chromium binary into a serverless function (fragile, slow cold starts, a real risk of silently failing in production), lib/pagespeed.ts calls Google's PageSpeed Insights API. This is not a simulation: PSI actually launches real Chrome, renders the page, and runs a full Lighthouse audit — the same engine behind Chrome DevTools — on Google's infrastructure. Free with no key at a modest rate limit, or with a free (no billing required) API key for a much higher quota. If PSI is slow or unavailable for a given request, the rest of the audit still completes normally using Audityxe's own signal-based checks.

Privacy — audits aren't stored (with one narrow exception)

Full audit results are computed fresh for every request and returned directly to the browser. There is no server-side database of full results, no public report page, and no cross-account history — once a result reaches your browser, it's yours; use the copy/export/share/email buttons on any result to keep your own copy.

The one exception: lib/badge-store.ts persists a minimal domain → last overall score → audit date record per domain, solely to power the embeddable badge at /badge. Nothing else about the audit is stored — no categories, no fixes, no HTML, no requester identity. The domain key is normalized (protocol/www./trailing-path stripped) identically on write and read, so re-running an audit for the same domain correctly overwrites its existing badge record instead of silently writing under a different key. See /privacy, /trust-center, and /methodology for the full statement.

Copy, export, share & email

Every audit view includes an icon-only action bar (components/AuditActionBar.tsx):

  • Copy copies a plain-text summary (with a document.execCommand fallback for browsers/contexts where the async Clipboard API is unavailable — this was a real bug in an earlier version that made the button silently do nothing in some environments).
  • Export downloads a complete JSON snapshot (scores, all 23 modules with evidence and per-finding severity, fixes).
  • PDF generates a full, styled multi-page PDF report client-side (lib/pdf-export.ts, via jspdf/jspdf-autotable — no server round trip) covering everything the JSON export has: cover page with score ring and category bars, executive summary, per-module findings tables color-coded by status/severity, PageSpeed lab metrics (explicitly labeled as lab data, not real-user field data), fix snippets rendered as syntax-colored diffs, promo copy, and competitor comparison.
  • Share uses the native Web Share API where supported, falling back to a clipboard copy.
  • Email opens a pre-filled mailto: with the summary.

None of these depend on a hosted URL — consistent with the no-storage privacy design above.

SSRF hardening & abuse prevention

Every outbound fetch to a user- or site-supplied URL — the audited page itself, each redirect hop, robots.txt/sitemap.xml, sampled links/images, and og:image — is validated by lib/url-safety.ts before the request is made: only http:/https: schemes, known-internal hostnames blocked outright, and every resolved DNS address checked against the full private/loopback/link-local/reserved IP ranges (including the cloud metadata endpoint 169.254.169.254). This check runs again on every redirect hop, not just the initial URL.

Additional protections: an 8MB streamed response cap, a 45-second overall audit-pipeline timeout, URL/body length limits, and /api/banner-bg requiring authentication (it has a real per-call cost and would otherwise be an open, unmetered proxy).

Known limitation: IP validation happens at check time, not at socket-connect time — a malicious DNS server could in theory change its answer in between (DNS rebinding). Full protection requires IP-pinning at the socket level, which isn't exposed by native fetch. Re-validating on every redirect hop closes the most common practical exploitation path.

Bulk audit (Pro plan)

/bulk accepts up to 20 URLs and audits them in one request (/api/audit/bulk, 4 concurrent fetches), gated to the Pro plan and checked server-side against the account's real, non-expired plan. Each URL consumes one slot from the same daily quota as single audits, reserved transactionally before any network work starts. Results include a CSV export button with per-category score columns.

Security headers on Audityxe itself

next.config.js sets a scoped Content-Security-Policy, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, and Permissions-Policy, plus poweredByHeader: false.

Brand assets

The logo (favicon, Apple touch icon, in-app mark, banner watermark, and Open Graph image) is a real PNG, not a generated placeholder — public/logo-mark-trimmed.png is the source of truth, auto-trimmed of padding, reused via components/Logo.tsx. app/icon.png and app/apple-icon.png are pre-sized static exports of the same source image, picked up automatically by Next.js's file-based icon convention.

AI-generated banner backgrounds

The shareable banner's background art is generated on demand via Pollinations.ai (free, keyless) using a prompt derived from the audit's score. Proxied server-side through /api/banner-bg (auth required) to avoid canvas CORS-tainting. Falls back to a designed gradient if generation is slow/unavailable — the download button always produces a complete image either way.

Code quality & dependency security

  • ESLint is configured and enforced (next/core-web-vitals, .eslintrc.json) — previously this project had no lint config at all, so real issues (unused variables, missing hook dependencies) could go undetected. npx next lint currently reports zero warnings or errors, and next build runs linting as part of the build, so a regression fails the build rather than shipping silently. The one rule deliberately disabled is react/no-unescaped-entities — it flags plain apostrophes in JSX text (e.g. "don't"), which render perfectly correctly; converting every contraction to &apos; would only hurt readability for zero functional benefit.
  • npm audit: all vulnerabilities in the Firebase dependency chain are resolved — firebase/firebase-admin were bumped to their latest major versions, and the remaining transitive uuid/teeny-request/ retry-request advisories (pulled in by firebase-admin's bundled, unused Google Cloud Storage client) are pinned to patched versions via overrides in package.json.
  • Next.js: pinned to 14.2.35, the latest patch on the 14.x line, which resolves the disclosed CVEs that 14.x does receive backports for. A handful of newer (July 2026) CVEs are only patched in the 15.x/ 16.x lines and won't be backported to 14.x — however, all of them are scoped to features this app doesn't use: Server Actions ("use server"), a custom Node HTTP server, and Turbopack middleware. This app uses none of the three (API routes only, no middleware.ts, standard Vercel deployment), so it isn't exposed to those specific vectors on 14.x. A move to Next 15 is a reasonable follow-up, but is deliberately not bundled into this change set as a forced, unverified major-version jump — App Router behavior changes enough between majors that it deserves its own dedicated test pass.

Zelvior Runtime

Loaded via next/script in the root layout for lightweight client-side performance instrumentation. Remove the two <Script> tags in app/layout.tsx (and drop cdn.jsdelivr.net from the CSP) if you fork this without needing it.

About

Audityxe is a high-performance, privacy-first site auditing engine and promo kit builder. Features a live, deterministic scoring engine, real Chrome browser rendering via PageSpeed Insights, and a 23-area deep security & UX inspection — complete with proof-backed evidence, stack-aware fixes, and zero persistent audit storage.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages