Yegor Gaidar
Available · Q4 2026
Security model · /audit · /status

What the audit touches.
What it refuses to touch.

The live audit sends an HTTP request to a host that a visitor chooses. That is a server-side request to an address someone else picked, so it is built like an attack surface, not like a fetch helper. This page is the rulebook: what is read, what is refused before any connection exists, what is kept, and what is deliberately out of scope.

01 — Scope of one audit

Six requests at most. Nothing else leaves the server.

One audit is: the page you named, up to three redirect hops if the site insists, then robots.txt and sitemap.xml on the same host if the time budget allows. Six requests is the hard ceiling. There is no crawler behind the form.

6
requests maximum
5 s
whole-audit budget
512 KB
body read, then dropped
0
records kept afterwards
What the report is made of
SignalSourceReading
Security headers Response headers of that single GET Whether CSP, HSTS, X-Content-Type-Options, frame and referrer policies are declared. It does not prove they are correctly scoped.
TLS parameters The handshake of the same connection Protocol version, cipher, issuer, days to expiry. It does not rate the configuration beyond that.
HTML size Bytes counted while the body streams How much markup is sent and whether it is compressed. The body itself is counted and discarded, never parsed or stored.
robots and sitemap One GET each, presence only Status code and byte count. Content is not read past the first 16 KB, which is not kept either.
Field performance Chrome UX Report API, only when a key is configured Real-user p75 for LCP, CLS and INP. If the origin has no field data, the report says unavailable instead of showing a number from somewhere else.
Every number in a report is produced by those requests. Nothing is modelled, estimated, or carried over from an earlier run — including the grade, which is a plain count of the checks that passed.
02 — Refusals

Six things the audit will not do.

These are not vague promises about good behaviour: each one is either refused by code before a connection is opened, or absent from the code entirely.

NO 01Port scanningOnly ports 80 and 443 exist as far as this endpoint is concerned. Any other port — 22, 3306, 6379, 8080, 8443 — is rejected by URL parsing, before DNS is consulted. There is no port loop in the code to disable.
NO 02Brute forceNo credential lists, no login endpoints, no repeated attempts against the target, no timing of authentication responses. One GET per URL, and your own rate limit stops a second one for a minute.
NO 03Form submissionThe audit never sends POST, PUT or DELETE to the target. It cannot submit a form, trigger a job, or write anything to a system it does not own.
NO 04Authorisation bypassNo cookie jar, no token replay, no header tricks to reach what sits behind a login. Headers you send to this site are never forwarded to the target; the audit builds its own request from scratch.
NO 05CrawlingNo link following beyond robots.txt and sitemap.xml, no discovery of subdomains, no enumeration of paths. The URL you typed plus those two well-known files is the entire surface.
NO 06Exploitation and fuzzingNo payloads, no injection strings, no vulnerability claims. A passed check means a header was present, not that the site is secure; a failed check means a header was missing, not that it is exploitable.
A passive check is a reading, not a test. If you need an actual penetration test with authorisation and a report you can put in front of a client, that is a different engagement and it starts with a signed scope.
03 — The boundary, as implemented

Every limit, with its number.

These values live in netlify/lib/net-guard.js and netlify/lib/audit-probe.js, and a test suite asserts them on every deploy.

Scheme
http, https
Ports
80, 443
Redirects
3, re-validated
Timeout
5 s total
Body cap
512 KB
Compressed cap
1 MB
Rate limit
5 / min · 50 / day
Concurrency
3 in flight
Request size
2 KB
Runs in
Netlify Function
The browser never talks to the target. It cannot: a cross-origin response exposes no headers, so a client-side audit would be a lie about what it measured.
Address ranges refused by the filter
10.0.0.0/8private
172.16.0.0/12private
192.168.0.0/16private
127.0.0.0/8loopback
169.254.0.0/16link-local — cloud metadata lives here
0.0.0.0/8this network
100.64.0.0/10carrier-grade NAT
192.0.0.0/24 · 198.18.0.0/15reserved · benchmarking
224.0.0.0/4 · 240.0.0.0/4multicast · reserved
::1/128 · ::/128loopback · unspecified
fe80::/10link-local
fc00::/7unique local
ff00::/8multicast
2001:db8::/32 · 2002::/16documentation · 6to4
64:ff9b::/96 · ::ffff:0:0/96NAT64 and IPv4-mapped, judged by the IPv4 inside
The address is judged, not the name. The hostname is resolved once, every returned address must be public, and the connection is then pinned to the address that was approved — so a second DNS answer cannot redirect the socket (rebinding). A name that returns one public and one private record is refused as a whole. Each redirect target goes through the same filter again, which is why a public URL that answers 302 to a metadata address is stopped at the hop instead of after it.
04 — Data

What is kept, and for how long.

Short answer: no record of your audit survives it. The long answer is the table.

DataWhereLifetime
The URL you type Memory of one function call Until the response is written — seconds. It is not written to logs, not cached, and not sent to analytics.
Query string in that URL Same request only Never logged. Audit log lines contain the outcome, status, duration and byte count — no URLs, no parameters, no hostnames.
Your IP address In-memory counter, as a rate-limit key Cleared with the counters within 24 hours, per instance. Never logged, never shown in a report, never joined to anything.
The target's HTML Nowhere Counted to the 512 KB cap while streaming and discarded. It is not buffered for analysis, so there is nothing to store.
Cookies, accounts, sessions None The audit has no session, no login and no cookie jar. There is nothing to attach a report to.
Page analytics Self-hosted Umami, like the rest of this site No cookies, no cross-site identifiers, no personal data. The audit form itself is not instrumented.
No account, no email, no newsletter. The form asks for a URL and nothing else, because a report that costs an identity to obtain is a form nobody should fill in.
05 — The status board

Aggregates, or nothing.

/status shows what my own services do, without showing the services.

Published

  • Uptime over a rolling 30-day window, as a percentage.
  • p50 and p95 response time, in milliseconds.
  • Requests per day.
  • Backup state — ok, fail or unknown — with the date of the last check.
  • Incidents: date and duration only.

Withheld

  • Hostnames, IPs, ports, internal addresses.
  • Service and product names that describe the stack.
  • Log lines and per-request records.
  • Anything belonging to a client — including whether a given client exists.

This is enforced, not promised: the snapshot has a closed schema, and a field that is not on the list fails validation instead of being published. The refresh interval is five minutes, and a page that cannot obtain fresh data says no data rather than showing yesterday's green.

06 — Limits of this model

Where it stops working.

A security page that only lists strengths is marketing. These are the real boundaries.

LimitConsequence
Rate-limit counters live in one function instance Netlify may run several. The published limit is a floor, not a hard ceiling — a determined caller can get more than five per minute across instances. The cap exists to keep the endpoint from being a free load generator, not to run a quota business.
The audit reads what a server says about itself Headers can be missing, misconfigured, or deliberately wrong. A grade is a statement about declarations, not about the safety of the application behind them.
Authenticated areas are not tested Everything behind a login is out of scope by design. There is no way to audit it without credentials, and I do not accept credentials for this feature.
Field data requires a CrUX key When no key is configured, the report says unavailable. It does not substitute lab numbers, and it does not show an old measurement as if it were current.
No history is kept Two audits a week apart are two independent readings. Nothing is compared over time on your behalf, because nothing is stored on your behalf.
IPv6 reachability The filter judges IPv6 addresses too; a target that is only reachable over IPv6 is audited normally, but an IPv6 address that embeds a blocked IPv4 address is refused like the IPv4 itself.
07 — Reporting a problem

Found a hole?

If you can bypass the address filter, make the endpoint fetch something it should not, or get an address into a report or a log line, that is a real finding and I want it. Include the URL you used, the time in UTC, and what you expected the filter to do.

Reports about this endpoint are read before anything else. Please do not test against third-party hosts through the form while doing it — a bypass that works by hammering someone else's server is not a proof I can use.

Machine readable
security.txt
Scope
/audit, /status, this site
Out of scope
third-party hosts, denial of service, social engineering
Disclosure
agreed before publication
No bounty programme. I will credit a finding if you want credit, and I will say plainly when something is a bug and when it is the documented boundary.