We offer a free technical audit, and the reasonable response to a free audit is suspicion — most of them are a scanner run, a PDF of red bars, and a sales call. So here is the whole list. If you never speak to us and just work through it yourself, that is a fine outcome; the checklist is more useful in your hands than in ours.
Everything below except code health can be assessed from the outside, which is why a URL is enough to start. Findings are ordered by what we would fix first, and that ordering is rarely the same as what scores worst. A minor issue on a checkout path outranks a serious one on a page nobody visits.
Core Web Vitals, and what a bad one usually means
Google publishes three field metrics and a threshold for each, assessed at the 75th percentile of page loads and reported separately for mobile and desktop. A single lab run on a fast laptop tells you almost nothing, which is the first reason a red score and a real problem are not the same thing.
- Largest Contentful Paint — good at 2.5 seconds or under, poor above 4.0. When it is bad it is almost never the server. It is a render-blocking stylesheet or font, a hero image served at full resolution in an unoptimised format, or a client-side render that will not paint until a JavaScript bundle has downloaded and executed.
- Interaction to Next Paint — good at 200 milliseconds or under, poor above 500. This replaced First Input Delay as a Core Web Vital in March 2024, and it is stricter, because it measures the whole interaction rather than just the delay before handling starts. When it is bad, the main thread is busy: an oversized hydration pass, an analytics or chat widget doing work on every event, or a handler that recalculates layout in a loop.
- Cumulative Layout Shift — good at 0.1 or under, poor above 0.25. When it is bad the cause is nearly always structural: images and embeds without declared dimensions, a font swap with no size adjustment, or content injected above the fold after paint. Consent banners and late-loading ad slots are the usual suspects.
We report the measurement and the cause together. A number on its own is a symptom, and a list of symptoms is not something you can act on.
Security headers, and what their absence exposes
Response headers are the cheapest security work available — most of them are one line of configuration — which is exactly why their absence is a useful signal about everything else. Four carry most of the weight.
- Content-Security-Policy. Without one, any script that reaches the page executes with the full privileges of your origin: it can read the DOM, read anything in localStorage, and post it anywhere. A CSP is what turns a successful injection into a blocked request in the console. It is also the hardest of the four to add to an existing site, because it will surface every inline script and third-party host you forgot about.
- Strict-Transport-Security. Without it, the first request a browser makes to your bare domain can go out over plain HTTP, and a redirect to HTTPS does not protect a request that has already left. HSTS tells the browser to never try HTTP for this host again, for the duration you set. A short max-age is nearly useless; the preload list removes even the first request.
- X-Content-Type-Options: nosniff. Without it, a browser may ignore your declared Content-Type and guess from the bytes. That matters most where users upload files: an upload stored and served back with a benign content type can still be interpreted as script if the sniffer decides it looks like script.
- Referrer-Policy. Modern browsers default to strict-origin-when-cross-origin, so this is less urgent than it once was — but the default is not universal, and if your application puts tokens, session identifiers or personal data in a URL, a permissive policy sends the whole path and query string to every third party the page loads.
We check the neighbours at the same time: frame-ancestors or X-Frame-Options for clickjacking, and the Secure, HttpOnly and SameSite flags on every cookie that carries a session.
Dependency advisories that matter, and the ones that do not
An audit tool will hand you a count. The count is close to meaningless, and treating it as a score is how teams end up doing weeks of upgrade work that changes their exposure not at all while a real problem sits untouched. Severity is a property of a vulnerability. Risk is a property of your system.
Four questions separate the two, and we ask them of every advisory before it goes in the report.
- Is the vulnerable code path reachable from your application at all? A package can be installed, imported and never call the affected function.
- Does untrusted input reach it? A regular-expression denial of service in a formatter that only ever sees your own configuration strings is not an attack surface. The same flaw parsing a request body is.
- Is it a runtime dependency or a build-time one? A flaw in a test runner or a bundler plugin is a supply-chain concern about your CI environment, not an exposure of your production service. Both are real; they are not the same problem and should not sit in the same list.
- Is there a fix, and what does taking it cost? A patch release is a decision that takes minutes. A fix that only exists two major versions ahead is a migration, and it needs to be scheduled rather than pretended away.
The archetype at one end is Log4Shell: unauthenticated remote code execution, triggered by a string an attacker could get logged, in a library present in an enormous share of Java deployments. Nobody had to weigh that one. The archetype at the other end is a high-severity advisory against a package that only runs in your test suite. Both show up as a red line in a tool. Only one of them is a reason to change what you are doing this week.
Accessibility, including the part with legal weight
We check keyboard operation end to end, focus visibility and order, form labelling and error association, colour contrast, heading structure, alternative text, and whether anything critical is conveyed by colour alone. Automated tooling finds perhaps a third of it; the rest needs someone to put the mouse down and try to complete a task.
The report also says how much of the failure is one shared component. Sites usually do not have a hundred separate accessibility defects — they have three, rendered a hundred times. That distinction is the difference between a fortnight of work and an afternoon.
The legal exposure is not hypothetical and it is not confined to one country. WCAG 2.1 Level AA is the reference standard almost everywhere. In the European Union, the European Accessibility Act obliges member states to apply its requirements to in-scope consumer products and services from 28 June 2025. In the United States, the Department of Justice published a rule in April 2024 adopting WCAG 2.1 Level AA for state and local government web content, and private-sector litigation under the Americans with Disabilities Act has been active for years. In India, the Rights of Persons with Disabilities Act 2016 requires accessible information and services. We are engineers rather than lawyers and the report says so — what we can tell you is which specific failures are the ones that get cited.
Hosting and deployment risk
This is the section that most often changes what a client does next, and it is the one no scanner produces. The questions are dull and the answers are frequently alarming.
- Is there a rollback path? Not in principle — has anyone used it? A deployment process with no tested way back means every release is a one-way door, which is why teams without one deploy rarely and therefore deploy large, dangerous changes.
- Is there a staging environment, and does it resemble production? A staging environment on a different database engine, or with an empty dataset, tells you the code compiles and very little else.
- Can anyone other than one person deploy? If the answer is no, and particularly if the deploy runs from a laptop, that is the single largest risk in most small systems. It is also the cheapest to fix.
- Are secrets in the repository? We check the current tree and the history, because a key removed in a later commit is still in the history and is still valid until it is rotated. Committed credentials in a private repository are a rotation job; in a public one they are an incident.
- Do backups exist, and has anyone ever restored from one? An untested backup is a belief, not a control. This is the question that most often produces a long pause.
- Who holds the accounts? Domain registration, DNS, TLS renewal and the cloud root account sitting in a former contractor’s personal login is a common and entirely avoidable way to lose a business.
Code health, if you give us the repository
We are not counting lines or generating a grade. The question we are answering is narrower and more useful: could a competent engineer who has never seen this code make a small change safely next week?
- Does it run locally, from a clean checkout, following only what is written down? The time this takes is the best single predictor of what everything else will cost.
- Is the build reproducible — a committed lockfile, a pinned runtime, no step that depends on one machine’s state?
- Where is the test coverage, not how much? Tests over the money path and the permission checks are worth more than a high percentage spread evenly over getters.
- How many places does one behaviour live in? Duplicated logic is not a style complaint; it is the mechanism by which a bug gets fixed in two of the three places it exists.
- How much of it is dead? Unreferenced routes, disabled features and commented-out blocks all cost reading time and all hide the shape of the live system.
- What does the history say? Long gaps, a single author on the critical modules, or a burst of commits directly to the main branch each describe an operating reality that the code alone does not.
What breaks first under load
Read from the architecture rather than guessed at, and stated as a specific mechanism rather than a warning that traffic might be a problem. The usual candidates, in roughly the order they arrive:
- A query with no index behind a page that gets popular. Fine at ten thousand rows, fatal at two million, and the failure is sudden rather than gradual.
- A query issued once per row of a result set. It is invisible in development against a seeded database and it multiplies exactly when the page becomes worth visiting.
- A synchronous third-party call inside the request path with no timeout. Your availability is now the product of yours and theirs, and a slow partner becomes an outage rather than a delay.
- A connection pool smaller than the number of concurrent workers, which turns into a queue and then into timeouts under precisely the traffic you wanted.
- State held in process memory — sessions, caches, upload buffers — which quietly forbids running a second instance.
- A scheduled job whose runtime has grown past its interval, so two copies now overlap and contend for the same rows.
Where a genuine load test is the only honest way to answer, the report says that rather than producing a number we cannot support. We would rather write down that we do not know than publish a figure you might plan around.
What this is not
It is not a penetration test and it is not a compliance certificate. Those are regulated exercises with defined methodologies and a signed report at the end, and we are not a certifying body. If that is what you need, the audit will tell you so and we will say what kind of firm to go to.
It is also not a rebuild pitch. The audit sometimes concludes that a system somebody has already decided to replace is sound and needs three fixes. That is the finding, and we will write it down even though the other answer is worth more to us.
The whole checklist above is what runs when you send a URL. A mutual NDA is signed before anyone looks at code, the assessment is written rather than exported from a tool, and there is no obligation to continue afterwards. Request a free technical audit →