What leaves your device
There is no backend, so the app has nowhere of its own to send your input. 36 of the 48 tools make no network request at all. This page covers the 12 networked tools, plus the two things that run on every page.
How the declaration works
Section titled “How the declaration works”Each tool in lib/tool-registry.ts carries an optional runtime block, and omitting it means the tool is fully offline:
export interface ToolRuntime { // false when the tool performs network i/o of any kind offline: boolean // hosts that receive user input, so the ui can say so before a request thirdParty?: string[] // capabilities that only exist in the desktop build, named honestly desktopOnly?: string[]}That block is what the UI reads to warn you before a request, and it is what the tools pages in these docs are generated from. So the disclosure in the app, the disclosure in the docs, and the routing table all come from one declaration.
| Guard | What it actually checks | What it does NOT check |
|---|---|---|
tests/unit/tool-registry.test.ts |
runtime.offline === false is set for exactly the tools whose source does network I/O |
that the host names listed are the host names actually requested |
| The same test | every non-offline tool discloses at least one destination, counting either a thirdParty host or a desktopOnly capability |
that the list is complete |
| The same test | the reported offline count matches the registry | anything about request bodies |
Read the right-hand column as the honest scope limit. The test catches a tool that starts making requests without declaring it, which was the original failure mode; it does not diff the host list against the URLs in the component. So treat thirdParty as a declaration the project holds itself to, verified for existence rather than for accuracy. If you need certainty for a specific tool, read its source, or watch your own network.
One tool uses the second half of that rule. The conflict checker declares offline: false with no thirdParty entry at all, and only desktopOnly: ["reading the local ARP cache"]. That is accurate rather than a loophole: its browser path parses text you paste and contacts nobody, and its one piece of I/O is the desktop build shelling out to arp -a on your own machine. There is no third party to name, so naming one would be worse than naming none.
The hosts, by capability
Section titled “The hosts, by capability”Grouped by what they are for rather than by tool, because the same resolver serves several tools.
| Host | Capability | What it receives | Operator |
|---|---|---|---|
cloudflare-dns.com |
DNS over HTTPS, default resolver | the domain you look up, your IP | Cloudflare |
dns.google |
DNS over HTTPS | the domain you look up, your IP | |
dns.quad9.net |
DNS over HTTPS, wire format | the domain you look up, your IP | Quad9 |
doh.opendns.com |
DNS over HTTPS, wire format | the domain you look up, your IP | Cisco |
dns.adguard-dns.com |
DNS over HTTPS, filtering | the domain you look up, your IP | AdGuard |
api.hackertarget.com |
HTTP header, security header and redirect inspection | the URL you enter | unaffiliated third party |
observatory-api.mdn.mozilla.net |
security-header grade | the hostname you enter | Mozilla |
api.certspotter.com |
Certificate Transparency issuance history | the domain you enter | SSLMate |
rdap.org |
WHOIS replacement lookups for domains, IPs and ASNs | the query you enter | RDAP bootstrap service |
api.maclookup.app |
MAC vendor lookup | the OUI prefix, so the first three octets | maclookup.app |
| The host or URL you type | ping, RTT, throughput, port probe, TLS reachability | the request itself, plus your IP | whoever you targeted |
The last row is the one people forget. When you ping or scan a target, that target sees a connection from your address. That is inherent to the measurement, not something the app adds.
Three hosts belong to sign-in rather than to any tool, and they are listed separately because they are reached only if you sign in, or only if this deployment was built with a Google client id:
| Host | Reached when | What it receives |
|---|---|---|
accounts.google.com |
NEXT_PUBLIC_GOOGLE_CLIENT_ID is set, and not on localhost |
the One Tap prompt loads; Google sees the page view |
apis.google.com |
Firebase Auth has been loaded and reaches for its gapi helper, which is a sign-in path |
the same |
| Your project’s Firebase endpoints | you are signed in and sync is enabled | your auth token and your project documents |
The second row moved recently. The Firebase SDK is no longer imported at module scope anywhere: lib/firebase.ts holds only type-only imports and three dynamic import() calls, so initializeApp runs the first time something asks for auth or Firestore rather than on page load. A visitor who has never signed in is detected from a localStorage hint rather than from the SDK, so nothing on this row is reached. Accounts and saved projects walks the decision, including the one browser case where the auth chunk is fetched anyway to prove a negative.
The third row is deliberately not a literal hostname. Firebase derives its endpoints from the authDomain and projectId in the build’s environment, so the exact names depend on whose deployment you are using. On the official deployment they are Google’s. On your own fork they are yours, and with no Firebase environment variables at all there are no such requests, because initializeApp is never called. Accounts and saved projects has the detail.
The first two are also visible in the Content-Security-Policy rather than only in the code: script-src names https://apis.google.com and https://accounts.google.com, and frame-src names https://*.firebaseapp.com and https://accounts.google.com. A policy is a testable artifact, which is why tests/unit/csp.test.ts derives the expected egress list from the source tree and fails if the two disagree.
RDAP is worth one note: it is the RFC 9082 and RFC 9083 replacement for WHOIS, and rdap.org is a bootstrap redirector, so your query is forwarded to the authoritative registry for that object. Two parties see it, not one.
The two relays, and why they exist
Section titled “The two relays, and why they exist”Two hosts in that table are relays rather than authorities, and the code labels them that way at every layer.
api.hackertarget.com is used because of the CORS restriction described in what a browser cannot do: a page cannot read another site’s security headers, so the only alternatives are a relay or nothing. It is reached from exactly one module, lib/http-relay.ts, whose comment records that the endpoint was verified by curl to send Access-Control-Allow-Origin: * and to return one header block per redirect hop, that it “can add, drop or rewrite anything”, and that “every caller labels its output unverified”. Three tools call it, each behind its own explicitly-chosen relay panel: HTTP headers, security headers, and the redirect checker.
What that means for you, stated plainly:
- The relay operator sees every URL you check.
- The relay could rewrite what it returns, and you would not be able to tell.
- Results are badged “unverified, via relay” in the UI and carry
api.hackertarget.com relay (unverified third party)in the export. - The security score is described as indicative, not as a verified assessment of the server.
- You can avoid the relay entirely by pasting
curl -sS -o /dev/null -D - -L <url>output into the same tool.
api.maclookup.app is the single remote OUI provider. The comment in lib/oui-vendors.ts records that an earlier version also routed through a third-party proxy, which meant one more party seeing the address for no added coverage. Lookups try the bundled database first, and there is an offlineOnly mode in which nothing ever leaves the device.
What runs on every page
Section titled “What runs on every page”Two things are not tool-specific.
| Thing | Scope | Notes |
|---|---|---|
| Vercel Analytics | every page of the web build | mounted unconditionally in app/layout.tsx |
| Google Identity Services script | only when NEXT_PUBLIC_GOOGLE_CLIENT_ID is set, and never on localhost |
loaded from accounts.google.com for the One Tap sign-in prompt |
| Google Fonts | build time only | next/font self-hosts Inter and JetBrains Mono into the bundle, so no request reaches Google at runtime |
I checked the third row rather than assuming it: the static export contains 13 woff2 files under out/_next/static/media/, and a recursive grep of out/ for fonts.gstatic.com and fonts.googleapis.com returns nothing. The docs go further and load no webfont at all, matching Inter and JetBrains Mono by name with a system fallback, because the docs also have to render inside the offline desktop app.
Vercel Analytics is the one thing on this page that is not a diagnostic and not opt-in. It ships in the web build. The desktop build serves the same bundle, so the component is present there too; whether its requests succeed from a loopback origin with no network is not something I verified.
What the app does NOT do
Section titled “What the app does NOT do”We do not run advertising trackers, marketing pixels, or session-replay tools. We do not have a server that could log your input, because output: "export" means there is no server. We do not send tool input anywhere except the hosts named above, and every one of them is named in the UI before the request.
Specifically:
- Nothing you type into an offline tool leaves the browser. Subnet math, VLSM plans, VLAN and ACL configs, MTU calculations, hashes, password generation, JSON formatting, regex testing, timestamp conversion, colour conversion: all local.
- Password and key generation is local, and unbiased.
lib/password-gen.tsdraws fromcrypto.getRandomValuesand rejects draws that land in the biased tail rather than folding them with%, which would over-represent the first2^32 mod ncharacters of the charset.tests/unit/password-gen.test.tsasserts that directly in “discards draws in the biased tail”, andtests/unit/random-gen.test.tschecks the statistical consequence in “shows no low-bucket skew, which is what modulo folding looks like”. Output is never transmitted. - The desktop build adds no telemetry. It makes the same requests the web build makes, plus whichever diagnostic you run.
- No GeoIP. There is no IP geolocation provider anywhere in the codebase.
- Nothing is stored on your device beyond five
localStoragekeys.netdash-projectsholds your saved projects;netdash-deleted-projectsholds the ids of projects you deleted until the cloud confirms they are gone;netdash-auth-sessionholds a single"1"or"0"so a page load can tell whether to load the auth SDK;netdash-recent-toolsandnetdash-sidebar-groupshold command-palette history and which sidebar groups you left open. The app sets no cookies of its own, and creates noIndexedDBdatabase; the only one that appears is the store Firebase Auth creates after you sign in. - No encryption, anywhere, of anything. This is stated as a limitation rather than omitted. A repository-wide search for
subtle.encrypt,subtle.decrypt,deriveKey,deriveBits,PBKDF2,importKeyandgenerateKeyreturns nothing. The only Web Crypto the app uses iscrypto.subtle.digestinlib/hash.tsandcrypto.getRandomValuesinlib/password-gen.ts. Saved projects are plain JSON inlocalStorageand plain documents in Firestore. If you have read a claim anywhere that this app encrypts project storage with AES-GCM and PBKDF2, that claim is false and no code has ever backed it.
Not in scope (yet)
Section titled “Not in scope (yet)”- Analytics as an opt-in. Vercel Analytics is unconditional. Making it a preference would be better, and would need a consent surface the app does not have yet.
- A
runtime.thirdPartylist checked against the code. The registry test proves a host list exists and is non-empty; it does not diff those names against the URLs the component actually requests.tests/unit/csp.test.tscloses half of this from the other direction, since it extracts request-target literals fromlib/,components/,app/andcontexts/and fails if the CSP does not permit them, so a new egress host cannot appear unnoticed. What is still missing is the per-tool attribution: nothing asserts that the host a given tool declares is the host that tool calls. - Self-service account deletion. Covered on accounts and saved projects; it is an email request today.