001 / Sample report

Sample code audit report: we audited our own site.

We audited our own MigrateWeb site the way we audit yours. This is the report: what we found, how serious it was, where it lives, how to fix it and whether it is fixed. There is no invented client and no invented number.

Report date 2026-10-07. Source: the git history of the MigrateWeb repository.

002 / Part 1: Summary

The verdict, in one page.

12 findings: 3 Critical, 4 High, 3 Medium and 2 Low. 9 are fixed in the repository and 3 are still open.

Subject
migrateweb.com, our own website-migration service (Next.js)
Method
The repository history, the code each commit touched and a review of the live site, all on 2026-10-07
Audited by
Acefina, the same way we audit a client repository
Client
None. This is our own product, so no client data or names appear here

What matters most

  • Leads were being lost without a trace. The contact page had no submit handler and the mail endpoint reported success when nothing was sent (F1, F2).
  • The numbers we showed ourselves and visitors were wrong. Conversions were counted several times and the pages carried ratings and stats with no source (F4, F5, F6).
  • An unprotected Google Ads dashboard and API sat in the build (F3). It is deleted, and checking that its credentials are revoked is still open.
  • None of it had reached the live site on the report date, which still ran the May 2026 build (F10).

Do first: deploy the fixes, settle one price list, then switch the build’s type check back on.

003 / Part 2: Verdict per area

Six areas, keep, fix or rebuild.

Each area gets a verdict and a reason in a sentence. Nothing here needed a rebuild.

SecurityFix in place
One serious hole, the unprotected ad-account routes (F3), and it is closed. The credential check that follows is still open. This is a read of the code history, not a penetration test.
ArchitectureFix in place
A normal Next.js layout worth keeping. Prices and claims are typed separately into many pages, which is how three price lists and unbacked claims got in (F6, F7).
DataFix in place
There is no database. A submission exists only as one email, so a failed send meant a lost lead (F2). It now fails loudly with a fallback address.
QualityFix in place
One test file, added 2026-10-07 and covering the contact submit helper only. Type errors cannot stop a build (F9) and the tracking page re-fired events (F4).
PerformanceNo verdict
Not measured for this sample. We took no page-speed or load readings, and we will not guess a verdict.
DeployFix in place
Hosted on Vercel from git. The live site trailed the code by about 147 days and nothing checks a change before release (F10).
004 / Part 3: Findings

Every finding: where, why, fix.

Each one names the file or area it affects, what we saw, why it matters and the fix. The severity is always written out as a word, never colour alone.

Critical
Could expose customer data, lose money or take the product down. Fix before anything else.
High
A serious weakness that real use will trigger. Fix before launch or before you add more users.
Medium
Makes the code slower to change or more fragile. Fix in the next round of work.
Low
Housekeeping and tidy-ups. Worth knowing, no urgency.
  1. F1 / QualityCriticalFixed 2026-10-07

    The contact form did nothing when submitted

    Where
    app/contact/page.tsx
    What we saw
    The page rendered a bare <form> with no submit handler, so pressing the button sent no request at all. The service pages used a different form component that did post to the mail endpoint.
    Why it matters
    Every enquiry typed into the main contact page was lost, and the visitor was never told.
    The fix
    A real submit handler that posts to the mail endpoint with shared validation, a redirect to the thank-you page on success, and an error state that shows the email address as a fallback. Tests with a mocked mail transport were added.
    Source commit
    1e107d8
  2. F2 / DataCriticalFixed 2026-10-07

    The mail endpoint said "success" when no mail was sent

    Where
    app/api/send-email/route.ts
    What we saw
    When the mail password was missing the route returned HTTP 200 and "Form submission received", with a code comment saying it did so "for demo purposes". When sending failed it also returned success, with a warning that notification "may be delayed".
    Why it matters
    The form showed a thank-you while nobody received the message, and the thank-you page fires the ad conversion events, so a lost lead was also counted as a win.
    The fix
    The route now returns 503 when the mail transport is missing and 502 when sending fails, with the fallback email address, and it validates the email address it is given.
    Source commit
    1e107d8
  3. F3 / SecurityCriticalFixed 2026-10-07

    Google Ads dashboard and API routes had no access control

    Where
    app/ads, app/ads-dashboard, app/api/google-ads, app/api/check-google-ads
    What we saw
    The routes contained no authentication check and the project had no middleware. robots.txt disallowed the paths, which asks crawlers to stay away but stops nobody. The POST handler accepted create-campaign, create-ad-group and create-ad actions.
    Why it matters
    Anyone who found the URL could call it. If live ad-account credentials were configured on the host, that is read access to the account and the ability to create campaigns that spend money. We did not test the live site or check whether credentials were set there.
    The fix
    The four routes, the Google Ads client library and a static mock-up were deleted from the build. Still open: confirm that any Google Ads API credentials that were ever set on the host are revoked or rotated.
    Source commit
    1e107d8
  4. F4 / QualityHighFixed 2026-10-07

    Conversion tracking re-fired every second on the thank-you page

    Where
    app/thank-you/page.tsx
    What we saw
    When the URL had no ?ref= value, the fallback reference was built with Date.now() in the render body. The effect that sends the Google Ads conversion, the GA4 generate_lead event and the Meta Lead events depended on that value, and the page re-rendered on each tick of its 10-second countdown, so the effect ran again every second. The reference also served as the transaction ID, and it changed each time, so it could not be used to de-duplicate. The contact form redirected with an empty ?ref= whenever the mail endpoint answered without a reference, which its fake-success reply (F2) did, so the two bugs fed each other.
    Why it matters
    One lead could be reported as several conversions. Ad spend and reporting decisions were being made on inflated numbers.
    The fix
    The fallback reference is now created once per page view with a useState initialiser, with a comment explaining why. The pattern dates from 2025-09-22 (d7b68b6).
    Source commit
    96cdb9d
  5. F5 / SecurityHighFixed 2026-09-29

    Star rating markup for reviews that do not exist

    Where
    components/structured-data.tsx and a schema builder in lib/seo/schemas.ts
    What we saw
    Every page carried AggregateRating JSON-LD claiming a 4.9 rating from 127 ratings and 89 reviews. Nothing in the repository holds any review. The unused builder in the schema library repeated the same made-up defaults. Search Console reported the markup as a Review snippets error on every page.
    Why it matters
    Structured data that describes ratings that do not exist is misleading, can cost a site its rich results, and had already produced an error on every page.
    The fix
    Removed from the layout and the unused builder deleted. The markup came in on 2025-09-22 (df3b7e4).
    Source commit
    0481310
  6. F6 / ArchitectureHighFixed 2026-10-07

    Claims on the pages that nothing in the repository supports

    Where
    app/page.tsx, app/about/page.tsx, app/portfolio/, config/site.ts
    What we saw
    The home page showed 500+ migrations, 99.9% uptime, 100% satisfaction, a 5.0 rating, Founded 2014, 10+ years and a 50+ team. The portfolio held placeholder case studies with invented results, and an internal "$29 CPC" badge was visible to visitors. Later copy promised zero downtime, 24/7 support, an SLA and an account manager.
    Why it matters
    A buyer is asked to trust figures nobody can check. If a prospect asks for the source, there is none.
    The fix
    The stats were replaced with verifiable offer terms, the portfolio now redirects to /services, and the about page states who runs the service. On 2026-10-07 the zero-downtime, guarantee, 24/7 and SLA wording was softened to what the service does: a redirect map, a planned cutover and QA.
    Source commit
    0481310 and 1e107d8
  7. F7 / ArchitectureHighOpen, not fixed yet

    Three different starting prices for a migration

    Where
    app/pricing/page.tsx, app/terms/page.tsx and the service pages
    What we saw
    The pricing page lists plans from US$99 and US$500. The terms page says standard migrations "start at $299". The service pages say "From $1,997", with higher tiers at $4,997 and $9,997. Each page holds its own hard-coded numbers.
    Why it matters
    A buyer cannot tell what a migration costs, and the terms page sits closest to a contract. The root cause is that prices are typed into each page instead of coming from one list.
    The fix
    Choose one price list, keep it in one file and have every page read from it. The 2026-10-07 commit left prices untouched on purpose, because the numbers are a business decision for the owner.
    Source commit
    seen at 1e107d8
  8. F8 / ArchitectureMediumFixed 2026-10-07

    Nine pages shared one title and one description

    Where
    Root layout metadata; home, pricing, about, portfolio, contact, blog, docs, privacy and terms
    What we saw
    Nine of the 22 indexable pages used the same title, "Website Migration Services | Zero-Downtime Site Transfers | MigrateWeb", and the same meta description.
    Why it matters
    Search engines cannot tell the pages apart, and the pages compete for the same query. The site had earned no organic clicks in 16 months of Search Console data.
    The fix
    Unique title, description, canonical and social tags per page, with the H1 on money pages matching the page keyword.
    Source commit
    1e107d8
  9. F9 / QualityMediumOpen, not fixed yet

    The build ignores type and lint errors

    Where
    next.config.mjs
    What we saw
    typescript.ignoreBuildErrors is true, and the build also skipped linting. ESLint reported 11 errors and 352 warnings (290 of them formatting) and none of it could stop a deploy. The type checker passes today, but only after dead state in the cookie banner, which the flag had been hiding, was removed.
    Why it matters
    The next type error will ship silently. The build is the last automatic gate and it is switched off.
    The fix
    ESLint and Prettier were replaced with Oxlint and Oxfmt, which report 0 errors and 18 warnings. Still open: remove ignoreBuildErrors and run the type check before every deploy.
    Source commit
    96cdb9d, flag still set after it
  10. F10 / DeployMediumOpen, not fixed yet

    The live site was about 147 days behind the repository

    Where
    Hosting (Vercel) versus the main branch
    What we saw
    The live pages answered with an age header of about 147 days: the May 2026 build. None of the fixes above had reached visitors on 2026-10-07. The repository has no CI workflow, so nothing checks a change before it goes out.
    Why it matters
    Fixes that are not deployed protect nobody, and the live site still carried the rating markup on every page.
    The fix
    Deploy the current main branch after the contact form is tested end to end, then record the date here. Add a CI step that runs the tests, the type check and the linter.
    Source commit
    live build versus 7674125
  11. F11 / ArchitectureLowFixed 2026-09-29

    Empty blog and docs pages were indexable and in the sitemap

    Where
    app/blog, app/docs, app/sitemap.ts
    What we saw
    Both sections were 29-word stubs, listed in the sitemap and open to indexing.
    Why it matters
    Thin pages dilute what a search engine thinks of the rest of the site.
    The fix
    Both are noindex and out of the sitemap until there is content.
    Source commit
    0481310
  12. F12 / QualityLowFixed 2026-10-07

    Dead code and a missing image description

    Where
    app/layout.tsx, components/cookie-consent.tsx, components/theme-switch.tsx, app/thank-you/page.tsx
    What we saw
    The Meta Pixel fallback image had no alt attribute. The cookie banner held a state variable nothing read. The theme switch used a conditional expression as a statement, and the thank-you page defined an icon it never used.
    Why it matters
    Small on their own. They are what a type checker or linter catches, and the type check was off.
    The fix
    The image has an empty alt, with a reasoned lint exception because it is a 1x1 tracking pixel, and the rest was deleted or rewritten.
    Source commit
    96cdb9d

Commit SHAs refer to the MigrateWeb repository, and each date is that commit’s date. Where a finding is marked fixed, it is fixed in the repository, not necessarily on the live site (F10).

006 / Part 5: What we did not cover

The limits of this review.

So nobody reads silence as a clean bill of health. Every client report ends the same way.

  • Performance. No page-speed, Core Web Vitals or load figures were taken.
  • A full read of every file. The findings come from the history of the commits listed here, plus a review of the live site. Silence about another file is not a clean bill of health.
  • The live contact form. Testing it would send a real email, so delivery on the live site was not checked.
  • Whether Google Ads credentials were set on the host, and whether anything was ever called through the removed routes.
  • Dependency vulnerability scans and a scan of the git history for committed secrets.
  • Accessibility beyond what the linter reports, and analytics or Bing Webmaster data.

This is the report format behind our code audit services: one fixed price, one app, one written report in three business days.

Request the $399 audit
007 / Next step

Get this report for your app.

US$399 fixed for one app and one repository. A written report in 3 business days after we have access, credited in full to the fix if you book it within 30 days.