A complete network supervision station for Firefox. Every request the browser makes — captured without duplicates, explained in plain language, and modifiable on demand.
Created by NeoZ · formerly INTERCEPTOR, renamed in 5.0
Try it in 60 seconds · Download · Why not the built-in panel? · Screenshots · How it works · Changelog
33 seconds, no narration needed. Full-resolution video · every frame is the running tool, driven the way you would drive it.
Firefox's own network panel shows you that a request happened. SWIFT shows you everything about it — the response body pulled off the wire, the JavaScript stack that triggered it, the TLS suite that carried it, the exact reason a cookie will be rejected — and lets you block it, rewrite it, or send it again.
Eight capture layers, zero duplicated rows, no telemetry, no dependencies, and an analyser that refuses to report anything it cannot prove.
Note
New in 5.5 — numbers are shown as they were sent: JSON.parse rounds an integer beyond
2^53 — the ID 1234567890123456789 becomes …800 — and SWIFT no longer does: not in the
body, the comparison, the toolbox, nor the GraphQL, JSON-RPC, JWT and RFC 9457 readers.
Comparisons have no size limit and produce a patch git apply accepts. A proxy that
answered 502 or 504 itself is named, with the reason (Proxy-Status). Nothing on screen is
cut without saying so.
New in 5.4 — SWIFT reads what servers announce: an API's deprecation and end date,
the quota left and when to retry, the Link headers against the IANA registry, the HTTP/3
offer, every cache on the way and the priority asked for. Each example of their
specifications is in the tests.
New in 5.3 — the Summary measures what gzip would have saved and lists the addresses downloaded several times. A request opened from anywhere is brought into view. And turning body capture off now really stops fetch and XHR bodies too.
Since 5.0 — every GraphQL, JSON-RPC and SOAP call named in the table, a one-sentence verdict on every request, browser reports opened, an OpenAPI 3.1 export. Every change →
node tools/captures.mjs, which renders the real console in a headless
browser fed by traffic that went through the real kernel — same store, same analyser,
same statistics as in Firefox.
No account, no build step, no package to install.
1. Download or clone this repository
2. Open about:debugging#/runtime/this-firefox in Firefox
3. Click "Load Temporary Add-on…" and pick manifest.json
Capture starts immediately. Browse anything, then press Ctrl+K and type req to open
the request table. There is nothing to configure — and nothing leaves your machine, because
there is nowhere for it to go.
The extension disappears when Firefox restarts: that is what temporary loading means. For something lasting, see Installation.
Because the built-in panel answers that a request happened. Most of the time the question is why.
| The question you actually have | The built-in panel | SWIFT |
|---|---|---|
| What did the server send back? | The body, if it is still in the cache | The body read off the wire as it arrives, kept even when the cache drops it |
| Which line of my code caused this call? | — | The JavaScript stack behind every fetch, XHR, sendBeacon and WebSocket |
| Why is my CORS request blocked? | An error on the wrong row | The OPTIONS preflight paired with the request it authorised, naming the header that refused |
| Why is this cookie ignored? | The cookie, as sent | The exact rule it breaks — prefix, SameSite, Secure, domain |
| What did that WebSocket frame mean? | 42["order",{…}] |
socket.io EVENT "order" — plus GraphQL, JSON-RPC, MQTT, WAMP, STOMP, SignalR, SockJS, Phoenix, Action Cable and Pusher, and never a label without proof |
| Did my gRPC-Web call succeed? | HTTP 200 | The grpc-status in the trailers, which is what actually decides |
| What protects this page? | The headers, one by one | HSTS, CSP, framing, nosniff, Referrer-Policy, Permissions-Policy, cross-origin isolation read together, with what applies when nothing is said |
| What is in this SSO login? | A base64 blob | The OAuth 2.0 / OpenID Connect request checked against RFC 9700, or the SAML message decoded — issuer, audience, validity, and what is actually signed |
| Is this request really signed? | Two opaque headers | The HTTP message signature (RFC 9421) rebuilt byte for byte — what it covers and what it leaves out — and verified with the key you paste |
| What did this passkey sign-in prove? | A base64 blob | The WebAuthn ceremony opened — domain fingerprint recomputed, presence and verification flags, synced key or not — and the sign-in signature verified with the public key of the captured registration |
| What did this DNS over HTTPS query ask? | Binary application/dns-message |
The question and every answer decoded, with the EDNS options — client subnet, padding, extended DNS errors |
| Is a secret leaking in this traffic? | — | Every finished request audited on its own, with the value masked in the report |
| Can I change a request before it leaves? | — | Pause it, edit it, release it — or block, redirect and rewrite by rule |
| Can I share this capture safely? | A HAR carrying every token | A HAR with secrets masked, next to the faithful one |
| What is served by a Service Worker? | Nothing — webRequest never sees it |
Caught by PerformanceObserver, on its own row |
It is not a replacement for the DevTools panel. It is what you open when the panel has stopped being enough.
- What it is
- Screenshots
- Installation
- How it works: the capture layers
- One request = one row: the correlator
- Surfaces: popup, sidebar, console
- The views, one by one
- The detail panel: twelve tabs
- Search: full syntax
- The security analyser
- The toolbox: 23 tools in 6 families
- Acting on traffic: rules and interception
- Replaying a request
- Import and export
- Settings
- Keyboard shortcuts
- Privacy: what leaves the machine
- Code architecture
- Why the source is in French
- Tests
- Building the package
- Contributing
- Licence
SWIFT is a Firefox extension that observes everything the browser sends and receives, then gives you the means to understand it and act on it.
The browser's built-in network tools show you requests. SWIFT goes further on four specific points:
| What SWIFT does | |
|---|---|
| You never hunt for a feature | Ctrl+K opens a command palette over every view, every tool and every action — and over the 858 reference lines. Type three letters — chm finds Sites and paths, cook finds Cookies — or type cache-status, 429, PROPFIND and get the explanation itself. The registry is built from the real tables, so anything added later appears in it without anyone remembering to register it. |
| It reads what the browser cannot | A application/grpc-web+proto body is a run of length-prefixed frames, and its real verdict lives in the trailers — a gRPC-Web call can answer HTTP 200 and still have failed. The detail panel decodes the frames, the protobuf inside them, and the grpc-status that actually decides. |
| It names every API call | On the wire, every GraphQL request looks the same: POST /graphql, status 200. SWIFT reads the document and puts the operation that runs in its own column — query GetUser, mutation Save — searchable with gql:. The detail shows the variables, a batch call by call, an Apollo persisted query with its SHA-256 recomputed, and the response's real verdict: complete, partial, execution error or request error, each error with its path. A mutation sent with GET, which GraphQL over HTTP forbids, is pointed out along with what the server did with it. The document reader is checked against graphql-core, the Python port of the reference implementation. JSON-RPC 2.0 and SOAP calls get the same treatment: the method or operation next to the path (rpc:), results and errors with the meaning of their code, the SOAP fault, and whether the HTTP status agrees with the body — checked against the examples of their specifications. |
| It reads what the browser reports about itself | CSP violations, network errors (NEL), deprecated APIs, interventions: browsers send them in POSTs that nobody reads. SWIFT opens them field by field, and reads the Reporting-Endpoints, Report-To and NEL headers that ask for them — including the endpoints the browser will ignore, and why. |
| It turns traffic into an API description | The captured API calls, exported as OpenAPI 3.1: operations, path parameters, request and response schemas, status codes, authentication schemes. It describes only what was observed, says so in the document, and copies no captured value into it. Validated with openapi-spec-validator and jsonschema. |
| It says what happened, in one sentence | Every request opens on a verdict: Succeeded: 200 OK, in 168 ms, Refused by the server: 403 Forbidden, Blocked by the “Trackers” rule, Response 200, but 1 GraphQL error in the body — with a button to the tab that holds the details. Nothing in it is guessed: each verdict reuses a reading that is proven elsewhere. |
| It checks what a body claims | A body announced as JSON that is in fact an HTML error page, an image announced as PNG whose bytes are a JPEG, an RFC 9457 problem whose status contradicts the HTTP status: said plainly. For text served without compression, SWIFT compresses the bytes it received and tells you what gzip would have saved — measured, not estimated. A site's security.txt is checked against RFC 9116: contacts, expiry, canonical address, signature. |
| It reads what the server announces | Deprecation and Sunset give the date an API is abandoned and the date it stops (RFC 9745, RFC 8594) — gathered in the Summary, found with tag:deprecie, and stated under the verdict. Rate limits say what is left and when to retry: RFC 9110 Retry-After, and the three generations of the IETF RateLimit draft still in use, each one named. Link headers are read against the IANA registry of 134 link relations, and a preloaded resource is checked against the capture: was it requested at all? Alt-Svc (the HTTP/3 offer and how long it lasts), Cache-Status (each cache on the way, a hit or why it forwarded), CDN-Cache-Control and Priority complete the picture, and Proxy-Status says which proxy produced a 502 or 504 itself, and why, from the IANA registry of 33 proxy error types. Every example of these specifications runs in the tests. |
| It shows what was sent, exactly | JSON.parse turns every number into a double: 1234567890123456789, an ID written as a number, comes out as 1234567890123456800, and a key written twice keeps only its last value. SWIFT reads JSON without converting it, so the formatted body, the comparison, the toolbox and the API readers — GraphQL variables, JSON-RPC ids and results, JWT claims, RFC 9457 problems — show the digits that were sent, and the Response tab names the integers a JavaScript client will read rounded. Checked against JSON.parse on twenty thousand generated texts: the same ones accepted, the same ones refused. |
| It checks, rather than repeats | When a server announces Content-Digest: sha-256=…, SWIFT recomputes the digest on the exact bytes it captured and says whether it matches, showing both values when it does not. The digest covers the compressed stream, and Firefox decodes a compressed response before any extension sees it: so a match proves itself, a mismatch is asserted only for a response that was not compressed, and otherwise the tab says why there is no verdict rather than report a false mismatch. No browser does this. RFC 9530 and the older RFC 3230 form, plus Content-MD5. |
| It says where the time went | Server-Timing next to the duration actually measured: the server claims 100 of the 214 ms, and the other 114 are network, queueing, or time it does not count. |
| It finds the cause, not the symptom | When a CORS request fails, the browser shows the error on that request — while the cause sits in the OPTIONS preflight a few rows above. SWIFT pairs the two and says which header blocked it: a missing Access-Control-Allow-Methods, an origin that does not match, or the classic * with credentials, which no browser accepts. |
| It misses nothing | Eight capture layers run in parallel: webRequest, response bodies via StreamFilter, TLS via securityInfo, DNS resolution, navigation, cookies, page probes (fetch, XHR, WebSocket, SSE, Beacon, WebRTC, PerformanceObserver), and an optional passive proxy. What one layer misses, another sees. |
| It never counts twice | A correlator pairs observations coming from different layers. One real request produces one row, even when five layers saw it. Two identical polling GETs stay two separate rows. |
| It explains | 63 status codes, 40 methods, 229 headers — the whole of the IANA permanent registry, checked by a test, plus the de-facto ones the registry has never taken in (X-Forwarded-For, CF-Ray, RateLimit, Sec-GPC) — 70 media types, 83 ports, 31 TLS cipher suites, TLS alerts, QUIC and HTTP/3 errors, DNS record types. All described in the tool, offline. |
| It never guesses | The security analyser only reports what is provable from what was captured. Every finding carries its evidence. A missing hardening header is not a vulnerability, so it is not reported. |
What it is not: not a proxy, not a vulnerability scanner, not an attack tool. Everything happens inside your Firefox, on your machine.
By the numbers: 238 JavaScript modules, ~48,700 lines, zero external dependencies, 2767 automated assertions, English and French interface.
Every image below is a screenshot of the real tool. They are produced by
node tools/captures.mjs, which renders the actual console in a headless Chromium fed by traffic that was run through the real kernel — same store, same analyser, same statistics as in Firefox. These are not mockups.
All traffic, one row per request. Configurable columns, sorting, quick facets, virtualised scrolling (the table stays fluid at tens of thousands of rows).
Ctrl+K from anywhere. Type a few letters and the matching views, tools and actions rank
themselves; the letters you typed are highlighted so you can see why a result matched. The
list is built from the real view and tool tables, so it never falls out of step with what
the extension can actually do.
It answers questions too, not just "where is that screen". The same box searches all
858 reference lines — every header, status code, method, media type, port, TLS cipher
suite, TLS alert, HTTP/2, HTTP/3, QUIC error, DNS record type, DNS response code, link
relation and proxy error type.
Type cache-status, 429 or PROPFIND and press Enter: the reference opens on that
exact entry, already explained. No tab to find, no table to pick first.
Commands still come first when both match — cookies offers the Cookies view before the
Cookie header — so the palette never becomes harder to use as the tables grow.
What the analyser was able to prove, ranked by severity, each finding carrying its evidence and a link back to the originating request.
Twelve tabs covering the whole record, including a "Raw" tab that prints the complete object: no captured data can escape display.
On the network, one more POST /graphql with status 200. In the table, the operation that
ran is written next to the path; in the detail, the response says what the status hides: one
error, at product.reviews, so the result is partial.
Every header is explained. The CSP policy is broken down directive by directive, with a plain-language statement of what it actually allows. Cache freshness is recomputed from the RFC 9111 formulas, using the headers actually received and the timestamps actually measured.
An API that says, in its headers, that this version is deprecated and when it will stop
(RFC 9745, RFC 8594), how much quota is left (the IETF RateLimit draft), which page comes
next (Link, read against the IANA registry), whether HTTP/3 is on offer (Alt-Svc) and
what each cache did with the response (Cache-Status), and which proxy, if any, produced the
response itself (Proxy-Status). Each block appears only when the header is there.
The figures for the observed scope: statuses, resource types, real protocols, active capture layers, content types, analysis tags, and domains by volume, by count and in error; the requests over time; the slowest, heaviest and most flagged requests, the same addresses downloaded several times, and the addresses whose server announces their retirement. On demand, it measures what gzip would have saved on the text responses served without compression.
What exists on the visited sites, host by host, reconstructed from observed traffic.
138 transformations across 23 tools, grouped into six families by intent: decode, hash, measure, inspect — everything is computed locally, nothing leaves the machine.
858 reference lines in 16 reference tables, offline. All searches every one of them at once, so you do
not have to know which table answers your question — type timeout and get the two status
codes, the header, the HTTP/2 error and the network error together. It searches the
explanations as well as the names: ocsp finds the TLS alert that mentions a stapled OCSP
response, which is nowhere in its name.
Block, redirect, force HTTPS, rewrite headers, mock a response, inject latency, replace a pattern inside a body.
What each layer is actually capturing, and the kernel's own counters.
Every option, plus four ready-made profiles.
The complete manual, offline, inside the extension.
- Open
about:debugging#/runtime/this-firefoxin Firefox. - Click Load Temporary Add-on….
- Pick the
manifest.jsonfile at the root of the repository.
Capture starts immediately, with nothing to configure. The extension disappears when Firefox restarts — that is how temporary loading works.
Download it from the latest release, or build it yourself:
.\build.ps1The package is written to dist/swift-<version>.xpi. Firefox only installs an
unsigned .xpi on Developer Edition, Nightly or ESR, and only after setting
xpinstall.signatures.required to false in about:config. On a standard Firefox, use the
temporary load above.
Nothing to configure. On first start the console opens on the help page. After that:
- Ctrl+Shift+Y — the compact window
- Alt+Shift+S — the sidebar panel
- Alt+Shift+I — the full console in a tab
- Ctrl+Shift+U — pause / resume capture, even outside the console
| Permission | What it is for |
|---|---|
<all_urls>, webRequest, webRequestBlocking |
Seeing requests, reading response bodies and TLS details — Firefox allows both only from a blocking listener — and, if you enable rules or interception, modifying requests |
webNavigation |
Knowing which page originated which request |
cookies |
Logging every cookie set, changed or removed |
dns |
Resolving names to show the address actually contacted |
storage, unlimitedStorage |
Keeping your settings, and the session if you enable persistence |
downloads |
Writing export files to disk |
clipboardWrite |
The "Copy" buttons |
contextMenus |
The context-menu entries |
proxy, notifications |
Optional: requested only if you turn those features on |
The kernel (background/) starts eight layers in the order of a request's life cycle. Each
one sees something the others cannot.
┌─ proxy ─────────── earliest hook (optional, passive)
│
├─ webRequest ────── 9 events: onBeforeRequest → onCompleted / onErrorOccurred
│ ├─ StreamFilter ─ the response body, as it arrives on the wire
│ ├─ securityInfo ─ certificate, TLS version, cipher suite
│ └─ dns.resolve ── canonical name and resolved addresses
│
├─ navigation ────── page context: which document, which frame
│
├─ cookies ───────── cookie mutations, including those made from JavaScript
│
└─ page probes ───── injected into the page (content/hooks.js):
fetch, XMLHttpRequest, WebSocket, EventSource (SSE), sendBeacon,
WebRTC, WebTransport, workers and Service Workers,
PerformanceObserver, page perception metrics, JS call stacks
Why several layers? Because none of them is enough on its own:
webRequestdoes not give you the response body →StreamFilterdoes.webRequestnever sees a request served from cache or by a Service Worker →PerformanceObserverdoes.webRequestcannot tell you which line of JavaScript triggered the call → the page probe captures the stack.- Individual WebSocket frames never pass through
webRequest→ the probe reads them.
Each layer can be enabled and disabled independently in the settings, and the System state view shows live what each one is actually capturing.
This is the heart of the project, and the hardest problem in it. Three layers can each
report the same request - webRequest, the page probes and PerformanceObserver - and two
more (tls, proxy) tag the record afterwards. The result must be a single row.
The correlator (background/core/dedup.js) enforces three strict rules:
-
Two observations from the same layer never merge. Two identical
GETs issued by a polling loop stay two distinct rows. That is what you want to see. -
A secondary observation joins a record at most once. Strict 1:1 pairing, FIFO.
webRequestis the authoritative layer; the others attach to it, each exactly once. -
An orphan observation becomes its own row. If no parent is found within the correlation window, the observation is promoted to a standalone row. Nothing is ever lost — a Service Worker request, which appears in no
webRequestevent at all, stays visible.
The pairing signature is METHOD + normalised URL + tab/frame. URL normalisation
(lowercasing, default port removed, . and .. segments resolved) means
HTTPS://A.FR:443/x/../y and https://a.fr/y produce the same key.
The Merges counter in the status bar shows how many observations were paired. The System state view breaks down layers, promoted orphans and pending queues.
One console page serves as sidebar panel, full-screen tab, detached window and options page; the popup is a separate, lighter page. The layout adapts to the available width, and the type scale follows the window — tighter in a narrow sidebar, larger on a wide display, never below 88 % or above 130 % of the size you chose.
| Surface | How to open | What for |
|---|---|---|
| Popup | Click the toolbar icon (by default), Ctrl+Shift+Y |
A quick glance: counters, pause, jump to the console |
| Sidebar panel | Alt+Shift+S |
Watching while you browse — the page stays visible |
| Full console | Alt+Shift+I |
The complete workstation |
| Detached window | The dock button at the top of the console | A window of its own: right, left, top, bottom, centre, full screen, or wherever you leave it |
| Options page | Add-ons manager | The same console |
What clicking the icon opens is your choice — popup, tab, sidebar or detached window — in the settings or from the dock button, which remembers it.
The sidebar groups 17 views into six families. Simple mode (in settings) hides the advanced views and keeps only the essentials.
| View | What it shows |
|---|---|
| Requests | All traffic, request by request. The table is virtualised with an exact-height invariant, so scrollHeight never shifts while you scroll and the wheel stays smooth at any zoom level. Configurable columns, sorting on any column, quick facets (API, pages, resources, streams, errors, alerts, third-party, slow…), multiple selection, per-row context menu, free-text annotation and colour marking. The table is virtualised: only visible rows are drawn. |
| Security | The analyser's findings, grouped by severity, each with its evidence and a link to the request. Rendered in batches as you scroll, so nothing is capped and nothing freezes. The report can be exported as Markdown. |
| Summary | The overall figures: requests, domains, volumes received and sent, median duration, errors, third-party share, encrypted share, cache, frames, cookies. Then the breakdowns: statuses, resource types, domains by volume, by count and in error, real protocols, content types, capture layers; the slowest, heaviest and most-flagged requests, the same addresses downloaded several times, and — on demand — the compression possible across the whole capture, measured by compressing with gzip every text response that was served without it. |
| Sites and paths | The tree of what exists on each visited host, reconstructed from traffic. Useful to see an application's real surface. |
| Live streams | WebSocket and Server-Sent Events, message by message, with direction (in/out), timestamp and payload. It keeps your place while traffic keeps arriving, and draws a long session in batches as you scroll, so nothing is left out and nothing freezes. Frames riding a known subprotocol are also read: 42["order",{...}] is shown as socket.io EVENT "order", alongside the raw frame. Eleven families are decoded, each by its published specification: Engine.IO / socket.io, STOMP, SignalR, GraphQL over WebSocket (both graphql-transport-ws and Apollo's older graphql-ws, with the operation name), JSON-RPC 2.0 (web3, LSP), WAMP (JSON, batched, MessagePack, CBOR), SockJS (with the STOMP it usually carries), Phoenix Channels, Action Cable, Pusher and binary MQTT 3.1.1 / 5, packet by packet. A frame gets a label only when something proves it: its form cannot belong to anything else ("jsonrpc":"2.0"), or the connection says so — the subprotocol the server negotiated, or a URL such as /socket.io/?EIO=4. A text like 2024 or 3 is not called an Engine.IO ping, and binary bytes are never read as MQTT without the mqtt subprotocol. |
| Comparison | Two requests side by side, line by line: headers, bodies, timings. Select two rows and press C. |
| View | What it shows |
|---|---|
| Cookies | Every cookie set, changed or removed, with the cause and all its attributes. Like the other logs, rendered in batches: no entry is hidden behind a display cap. |
| Navigation | Page and frame changes: start, commit, DOM ready, completion, errors, history changes. |
| Workers and WebRTC | Workers, Service Workers, WebRTC connections and page performance metrics. |
| View | What it shows |
|---|---|
| Toolbox | See section 11. |
| View | What it shows |
|---|---|
| Rules | Automatic interception rules. See section 12. |
| Interception | The queue of suspended requests, to edit then release one by one. |
| View | What it shows |
|---|---|
| System state | What each layer is actually capturing, the browser capabilities detected, and the counters of the store, the correlator and the analyser. This is the view to check when something seems to be missing. |
| Internal log | SWIFT's own errors, and the log of its own commands. Exportable for a bug report. |
| Settings | See section 15. |
| View | What it shows |
|---|---|
| Tutorial | Twelve lessons to get to grips with the tool. A lesson is only ticked once you have actually performed the action — never on your behalf. |
| Help | The complete manual, offline. |
Click a row and the panel opens at the bottom, resizable. The ↑ ↓ arrows move to the previous or next request in displayed order, filters included.
| Tab | Contents |
|---|---|
| Summary | The verdict in one sentence — with, under it, when the server asks to retry and any deprecation it announces — then identity (method, URL, status with its meaning, media type), network (IP, real protocol, cache, on-the-wire sizes, performance metrics), context (tab, window, frame, document, origin, third-party, Firefox tracking classification), where the request comes from as Firefox declares it (Sec-Fetch-Site, -Mode, -Dest, -User, in plain words), and the free-text annotation. |
| Headers | URL parameters, request headers, response headers, headers as seen by page JavaScript. Every known header is explained on hover. Then two computed analyses: HTTP freshness (RFC 9111: freshness lifetime, current age broken down, time remaining, validators, Vary, directives) and the CSP policy (each directive with its meaning, then a plain statement of what the policy allows). Then the response protections — HSTS (and whether Firefox already enforces it for the host), CSP, framing, nosniff, the effective Referrer-Policy, Permissions-Policy, COOP / COEP / CORP and cross-origin isolation, and what the server says about itself — as facts, never as alerts. For a page, a CSP derived from what it actually loaded, ready to try in Report-Only, with what the network cannot show stated plainly. An HTTP message signature (Signature-Input / Signature, RFC 9421) is rebuilt: the components it covers, its parameters, and the exact signature base, byte for byte, from what Firefox reported — then verified with a key you paste (RSA-PSS, RSA PKCS #1, ECDSA P-256 and P-384, Ed25519, HMAC). What cannot be rebuilt — a trailer, a header Firefox did not report — is named instead of producing a false "invalid". Last, report collection: the Reporting-Endpoints, Report-To and NEL headers — where reports go, which endpoints the browser will ignore (a value that is not a string, an origin that is not secure), and the NEL sampling fractions. A Clear-Site-Data header is read: what the browser will erase. Then what the server announces: an address's deprecation and end of service (RFC 9745, RFC 8594), rate limits and when to retry, the Link headers with each relation explained from the IANA registry and whether a preloaded resource was requested, alternative services (Alt-Svc), the caches crossed (Cache-Status, CDN-Cache-Control), the intermediaries crossed (Proxy-Status, RFC 9209: each proxy from the origin to you, and the one that generated a 502 or 504 itself, with the meaning of its error from the IANA registry, the next hop, the ALPN protocol, the status it received and the DNS aliases of RFC 9532) and the priority asked for (Priority). |
| Request | The body sent: nature, source, declared type, encoding, compression. Automatic JSON formatting, form fields broken out, and multipart/form-data bodies decoded part by part (field name, filename, type, content). An OAuth 2.0 / OpenID Connect authorization or token request is read — flow, PKCE, state, nonce, redirect URI — and checked against RFC 6749, RFC 7636 and RFC 9700, without ever reproducing a secret. A SAML message is decoded in both bindings: issuer, subject, audience, validity at capture time, and which element is signed — the response, the assertion, or nothing. A passkey registration or sign-in (WebAuthn) is opened: the signed origin and challenge, the domain fingerprint (rpIdHash, recomputed to name the domain), the presence and verification flags, whether the key is synced, the signature counter, the authenticator model (AAGUID) and its public key. A sign-in signature is verified automatically with the public key of the registration when that registration was captured. A DNS over HTTPS query (?dns= or application/dns-message) is decoded. A GraphQL request is read in all four forms — URL parameters, JSON body, batch, file-upload multipart form: the operation that runs (chosen the way GraphQL's GetOperation chooses it), the variables, and an Apollo persisted query whose SHA-256 is recomputed against the text sent with it. A browser report (application/reports+json, application/csp-report) is opened report by report: CSP violation, network error with the meaning of its NEL type, deprecation, intervention, Permissions-Policy. JSON-RPC 2.0 calls (method, id or notification, parameters, a batch call by call) and SOAP 1.1 / 1.2 envelopes (operation and its namespace, SOAPAction or the action parameter, header blocks) are read too. |
| Response | The body received, with the same treatment, plus image preview and hex rendering for binary. An OAuth token response is summarised. For a script or stylesheet, the Subresource Integrity hashes (SHA-256, -384, -512) and a ready integrity tag — computed at capture time on the exact bytes the page received, and refused only when the body was truncated. WebAuthn options sent by a server are read (challenge length, accepted algorithms, user verification, resident key, attestation). A DNS over HTTPS answer is decoded record by record — A, AAAA, CNAME, MX, TXT, SOA, SRV, CAA, HTTPS / SVCB, DS, DNSKEY, RRSIG — with the EDNS options. A GraphQL response is judged: complete result, partial result, execution error or request error (GraphQL §7.1), each error with its path and code, an HTTP 200 that hides errors, the status rules of application/graphql-response+json, and the schema an introspection query handed out. JSON-RPC results and errors with the meaning of each standard code, a request left without a response, a notification that got one; the SOAP fault (code, subcode, reason, detail) and the SOAP 1.1 rule that a fault is served with HTTP 500. An RFC 9457 problem (application/problem+json): type, title, detail, extension members, and a status member that contradicts the status served. Body checks: content that contradicts its declared type, and for text served without compression, the gzip size measured on the bytes received. A security.txt file is checked against RFC 9116. A script or stylesheet that announces a source map (ECMA-426: SourceMap header or sourceMappingURL comment) shows its address. JSON is shown as sent: an integer beyond 2^53 keeps its digits, and the tab lists, with their JSONPath, the integers a JavaScript client will read rounded and the keys written twice in the same object (RFC 8259 §4). |
| Cookies | Cookies set and changed by this request, with all attributes detailed. |
| Security | TLS version, cipher suite, key exchange, forward secrecy, ECH, HSTS — and every certificate of the chain read in full from its DER bytes, like Firefox's certificate viewer: the names it covers (DNS, IP, email, URI), key algorithm and size, key usages, whether it is a certificate authority, the validation level it declares (DV, OV, IV or EV), where to check revocation (OCSP, CRL), the issuer's certificate, key identifiers, and the embedded Certificate Transparency proofs with their log and timestamp. One click copies the PEM or opens it in the toolbox. |
| Alerts | The analyser's findings for this request, each with its evidence. |
| Streams | The WebSocket frames, SSE messages, WebRTC data-channel messages or WebTransport datagrams of this connection. A session that is still receiving grows as you watch it — frames are appended, never redrawn, and only while you are at the bottom of the panel, so reading further up is never interrupted. A binary frame keeps its bytes: the format is named when protobuf, MessagePack or CBOR recognises it, the opening bytes are shown in hex, and a click sends the frame to the toolbox. |
| Timeline | The network phases the browser actually measured — blocked, DNS, TCP connection, TLS, waiting for the first byte, receiving — as disjoint bars, so their sum is right. Nothing is filled in: the browser does not expose when sending ends, so no "sending" figure is invented; and when it hides the detail of a resource from another origin (no Timing-Allow-Origin), the tab says so instead of showing 0 ms and 0 bytes. Then every step, timestamped, from first observation to last. |
| JS stack | The JavaScript call stack that triggered the request, when the page probe was able to capture it. |
| Replay | See section 13. |
| Raw | The complete object as it exists in memory. This is the safety net: no captured data can stay invisible. An automated test (tests/detail-coverage.test.mjs) verifies that each of a record's 60 fields is displayed somewhere. |
The search box accepts free text, regular expressions between /, and per-field filters.
Terms combine with AND. A leading - excludes.
method:POST status:5xx host:api. size:>100000 -image /regex/
method: type: host: path: url: gql: rpc: mime: scheme: proto: ip: tag: risk:
src: state: init: tls: error: classe: note: color:
Accept >, <, >=, <=, = and ranges.
status: (also accepts 2xx through 5xx) size: duration: tab: frame: id:
findings: redirects: ws: sse: cookies: wire:
flag: (pinned) third: (third-party) cache: (served from cache) body: (body captured)
stack: (JS stack present) private: (private browsing) replayed: (replayed)
imported: (came from an imported HAR)
The filters you already use in Firefox's own network panel work here unchanged:
has-response-header: and has-request-header: (a header with exactly that name),
set-cookie-name: set-cookie-domain: set-cookie-value: (cookies set by the response),
larger-than: (size in bytes), is:running is:cached is:from-cache, regexp: (on the URL).
Firefox's names for criteria that already exist are accepted as synonyms: status-code:
domain: remote-ip: mime-type: protocol: transferred:.
| Query | What it finds |
|---|---|
status:5xx |
Every server error |
host:api. method:POST |
POSTs to a host containing "api." |
size:>1000000 |
Everything over one megabyte |
duration:>3000 -image |
Slow requests, images excluded |
risk:critical |
Requests carrying a critical finding |
third:true cookies:>0 |
Third parties that set cookies |
/\/api\/v[0-9]+\// |
Versioned API paths, by regular expression |
tag:cleartext |
Everything travelling in the clear |
-has-response-header:content-security-policy type:main_frame |
Pages served without a Content-Security-Policy header |
set-cookie-domain:.example.com |
Responses that set a cookie whose domain contains .example.com |
gql:mutation |
Every GraphQL mutation, whatever URL it was sent to |
gql:GetUser |
The GraphQL operation named GetUser |
tag:graphql-introspection |
GraphQL servers that handed their schema to an introspection query |
tag:rapport-navigateur |
Reports the browser sent on its own (CSP, NEL, deprecations) |
rpc:eth_call |
JSON-RPC calls to eth_call |
rpc:SOAP |
Every SOAP call |
tag:security-txt |
security.txt files the browser loaded |
tag:source-map |
Scripts and stylesheets that announce a source map |
tag:deprecie |
Addresses whose server announces their deprecation or end of service (Deprecation, Sunset) |
tag:quota |
Responses that state a rate limit (RateLimit, its draft forms, X-RateLimit-*) |
tag:retry-after |
Responses that say when to retry |
The bodies checkbox extends the search to bodies, headers, frames and stacks.
The ★ button saves the current filter and recalls recent searches.
The ? button next to the field opens the full syntax help.
One governing rule: a finding is only reported if it is provable from what was captured, and if it corresponds to a genuinely exploitable weakness. Every finding carries its evidence, in plain language.
| Rule | Severity | Evidence |
|---|---|---|
| Credentials written in the URL | critical | The URL contains user:password@ — the password is in history, in logs and in the Referer |
| Token passed in the query string | high | An unambiguously named parameter (access_token, api_key…) carries a value long enough to be a token |
Unsigned JWT (alg: none) |
critical | The token's own header says so: its payload can be altered without a key |
| Data sent in the clear (HTTP) | critical | http scheme and something worth stealing: Authorization, a cookie, or a request body |
| Mixed content on an HTTPS page | high | The page is https, the resource is http: modifiable in transit |
| Untrusted certificate / domain mismatch | critical | Firefox rejects the certification chain |
| Expired certificate, obsolete TLS, broken cipher suite | high | Version or suite read from securityInfo |
| CORS: origin mirrored with credentials | critical | The server echoes the client's Origin and sets Allow-Credentials: true |
CORS: null origin with credentials |
high | Same mechanism, with the null origin |
| Cookie set in the clear | high | Set by an http response without Secure: it will travel back in the clear |
__Host- / __Secure- prefix not honoured |
medium | The prefix's requirements are not met: the browser rejects the cookie |
SameSite=None without Secure |
medium | The combination is refused by the browser |
| Secrets in bodies and headers | varies | A value matching a known provider's exact format, which cannot be confused with anything else |
| CSP nonce reused | medium | The same nonce-… appears in the CSP of two distinct responses: a nonce only protects if it is unpredictable, and an injected script can reuse this one. Cached responses and 304s are not counted |
Some observations are exactly true without being a vulnerability by themselves. They are shown in the Alerts tab with their evidence, tagged in the table, and never counted as alerts:
- A parameter comes back in the response — the value of a query or form parameter appears
verbatim in the body, and whether its special characters came back unescaped (tags
refleteandreflete-brut). It is where anyone testing an injection starts; nothing says in which context the value lands, so nothing more is claimed. - A redirect leads exactly to a parameter's value — the shape of an open redirect (tag
redirection-parametree); it does not prove the server would accept any value. - A GraphQL mutation ran over
GET— answered 2xx, which GraphQL over HTTP (§4.3) forbids: a plain link or image can then trigger it with the site's cookies (taggraphql-mutation-get). - The server answered a GraphQL introspection query — its schema, with the number of
types, is handed to whoever asks (tag
graphql-introspection). Read only when the request asked for the schema.
- "Missing security headers" — absent hardening is not a vulnerability, it is a missed opportunity. Nothing is exploitable purely because a header is not there.
- "Personal data" matched by regular expression (email, card, IBAN) — too many false leads, and finding an address in a page is not a vulnerability.
That is why a clean capture shows zero findings instead of a wall of noise.
Secret masking is on by default: a detected value is displayed truncated. You can add your own secret and tracker patterns in the settings.
138 transformations, 23 tools grouped into six families by what you are trying to do — decode and convert, encryption and digests, network and HTTP, read and measure, search and compare, produce — all computed locally. A row's context menu, and the "Toolbox" buttons in the detail panel, send a value straight into it.
| Family | Contents |
|---|---|
| Transform | The full catalogue of 138 transformations, grouped: bases, text, web, casing, Unicode normalisation, lines… — and JSON to JSON Schema 2020-12, from one document or several examples in JSON Lines: a property is required only when present in every example, a format is claimed only when every value matches it, and no value is copied into the schema. The JSON transformations — format, compact, sort keys, one path per line, JSON Lines, YAML, CSV, form, headers, cookies — keep every number and string as written, every duplicate key and every __proto__ key; two Set-Cookie lines stay two values |
| Keys and trials | XOR (including single-byte key search), Vigenère, Caesar across all 26 shifts |
| Encryption | AES-GCM / CBC / CTR, PBKDF2 derivation, RSA and ECDSA signing and verification |
| JWT | Header and payload decoding, labels for standard claims, signature verification with a key, JWK thumbprint (RFC 7638) |
| Digests | MD5, SHA-1, SHA-2, SHA-3, Keccak, SHAKE, BLAKE2b/2s, RIPEMD-160, SM3, MD4, NTLM, CRC (20 standard variants), Adler-32, FNV-1a, MurmurHash3, xxHash32, xxHash64, SipHash-2-4, HMAC |
| Measures | Length, bytes, Shannon entropy, character distribution |
| Hexadecimal | Hex dump with ASCII column |
| Binary | Protocol Buffers, MessagePack, CBOR, ASN.1/DER and X.509 certificates, character sets and mojibake repair, WebSocket and HTTP/2 frames decoded byte by byte, including full HPACK header decompression (RFC 7541: static table, dynamic table, Huffman). CBOR read as RFC 8949 requires: 64-bit integers and bignums keep all their digits, an embedded document (tag 24) is decoded, byte strings are written in its diagnostic notation (h'0102'), and every ill-formed sequence of its Appendix F.1 is refused; a MessagePack timestamp keeps its nanoseconds |
| Code | Call-code generation in 39 formats (see section 14), readability and deobfuscation |
| OTP codes | HOTP and TOTP (RFC 4226 / 6238), neighbouring windows, otpauth:// links |
| Identify | Recognising an unknown value: format, likely encoding, candidate digest. Identifiers are decoded, not just recognised: a UUID gives its version, variant and — for v1, v6 and v7 — the timestamp it embeds; ULID, Snowflake (Twitter, Discord, Instagram), MongoDB ObjectId and KSUID give their creation date, machine, sequence and counter |
| Timestamps | Fourteen origins read side by side: Unix in seconds / milliseconds / microseconds / nanoseconds, Windows FILETIME, Chrome/WebKit, HFS, Apple/Cocoa, .NET ticks, Excel serial, Julian day, NTP (RFC 5905), GPS, plus the packed MS-DOS date used in ZIP archives. ISO week and durations |
| Numbers | Conversion between bases 2 to 36, boundary values, Luhn |
| Structures | JSON, XML, YAML: navigable tree, JSONPath paths, CSS and XPath selectors. PHP serialize() is read as a tree — arrays, objects, enums, references, and the NUL-encoded protected/private visibility — and never executed |
| Headers | A pasted header block is split line by line, each value broken down, each point worth a look flagged. Content-Disposition filenames are decoded through RFC 8187 extended values, RFC 2231 continuations and RFC 2047 encoded-words, so the real filename is shown rather than UTF-8''%e2%82%ac%20rates. Modern headers written as RFC 9651 structured fields (Priority, Accept-CH, Cache-Status, Content-Digest, Signature-Input) are parsed into their real types across all eight base types: 42 is an integer, "42" is a string, :YQ==: is a byte sequence, @1659578233 is a date, and %"h%c3%a9" a display string |
| Search | Ready-made patterns: tokens, keys, addresses, identifiers |
| Regular expression | Test bench with capture groups and replacement |
| Compare | Line-by-line and word-by-word diff with no size limit — the shortest list of changes (Myers' algorithm); on huge texts with thousands of changes, beyond a search budget, it anchors on the lines that appear once on each side (the "patience" principle git also offers) and says the list may not be the shortest — and a unified diff that patch and git apply accept, byte-identical to GNU diff -u on its reference cases; Levenshtein distance, similarity |
| URL | Every part of the URL, RFC 3986 canonical form, known service on the port, homograph detection (a Cyrillic "а" inside a Latin word is flagged) |
| IP address | IPv4 and IPv6: mask, network, broadcast, usable range, category, subnetting, summarising a prefix list, range to prefixes, enumeration, reverse names in-addr.arpa / ip6.arpa (read both ways) |
| Reference | The complete tables, offline, with an All search across all 16 reference tables at once: 63 statuses, 40 methods, 229 headers (the entire IANA permanent registry), 70 media types, 83 ports, 31 TLS cipher suites, WebSocket close codes, HTTP/2 errors, HTTP/3 and QPACK errors, QUIC transport errors, TLS alerts, DNS record types, DNS response codes, Firefox network errors, the 134 link relations of the IANA registry, and its 33 proxy error types (Proxy-Status) |
| Generate | UUID v3/v4/v5/v7, ULID, nanoid, passwords with strength calculation, MAC addresses, random hex and base64 |
| Import a request | Paste a curl command and turn it back into a replayable request |
Off by default. Until you enable something, SWIFT observes without ever altering traffic.
A rule has conditions and an action.
Conditions: host (contains), URL (regular expression, with capture groups), method, resource type. They combine with AND.
Actions:
| Action | Effect |
|---|---|
block |
The request is cancelled |
redirect |
The request goes to another URL — $1…$9 expand the capture groups |
upgrade |
An http request is reissued as https |
modifyHeaders |
Adds, replaces or removes request and response headers |
mock |
The page receives a simulated response — the server's real body is still recorded |
delay |
The request leaves with a delay, to simulate a slow network (capped at 30 s) |
replaceBody |
The page receives the server's body with a pattern replaced |
Seven ready-made templates ship with the tool: block common telemetry, block a domain's images, redirect one domain to another, impose a User-Agent, force HTTPS on a domain, simulate a JSON response, remove a response header. Every rule states in plain language what it does: "If host contains 'example.com' AND method is POST, then the request is blocked."
A rule whose URL expression is unparseable is inert and flagged as such: a forgotten parenthesis must never turn "this URL" into "every URL".
Suspends a request before it leaves, long enough to edit it by hand and then release it. The queue is visible in the Interception view. If every console is closed, everything is released automatically: no navigation can stay blocked with nobody left to decide.
The Replay tab of the detail panel takes a captured request, lets you change anything
(method, URL, headers, body) and sends it again. The response appears alongside, and the
replayed row is marked in the table (replayed:true).
Useful for: checking that a parameter changes the result, testing a boundary value, reproducing an error, comparing two variants with the Comparison view.
| Format | File | What for |
|---|---|---|
| HAR 1.2 | .har |
The standard interchange format: readable by Firefox, Chrome, Charles, Fiddler, Wireshark… |
| HAR 1.2, secrets masked | .har |
The same file, made shareable. Authorization, Cookie, Set-Cookie and the other secret-bearing headers lose their value; so do query parameters whose name announces a secret, and anything the analyser's own patterns recognise, wherever it sits — including inside a response body. The file says how many values it masked, so a sanitised export is never mistaken for traffic that had nothing to hide. The faithful export stays next to it: you need that one to replay. |
| Full JSON | .json |
Every record in full, with the statistics |
| CSV | .csv |
For a spreadsheet |
| Postman collection | .postman_collection.json |
Replaying in Postman |
| OpenAPI 3.1 | .openapi.json |
The API calls described for Swagger UI, Redoc, Postman or a client generator: operations, path parameters (only segments shaped like identifiers), request and response schemas, statuses, Bearer and Basic authentication. One origin per document — the toast says which, and how many calls to other origins were left out. It holds what crossed the network and nothing else: query parameters are never marked required, and no captured value is copied into it. |
| Findings report | .md |
The security summary in Markdown |
| URL list | .txt |
One URL per line |
| Settings | .json |
Configuration backup |
| Diagnostics | .json |
The internal log, for a bug report |
A captured request can be reproduced in 39 formats:
- Command line — cURL (bash, PowerShell, cmd.exe), wget, HTTPie,
PowerShell
Invoke-WebRequestandInvoke-RestMethod - JavaScript and Python —
fetch, Node.js, Pythonrequests, Pythonhttp.client - Ruby —
net/http, HTTParty - PHP — cURL, Guzzle
- Go, Rust —
net/http,reqwest - JVM — Java
java.net.http, OkHttp, Kotlin - .NET — C#
HttpClient, RestSharp - Apple — Swift
URLSession, Objective-CNSURLSession - Others — Dart, Elixir, R, Perl, Clojure…
- Raw and documentation — raw HTTP request, raw HTTP response, Markdown sheet, full JSON record
A multiple selection exports a complete, ready-to-run script.
- HAR — a HAR file produced by another tool loads into the table; imported rows are
marked
imported:true. - Session — a SWIFT capture exported as JSON reloads in full.
- curl — a pasted
curlcommand becomes a replayable request. - Settings — an exported configuration can be re-imported.
Project rule: every option present has a real effect in the code, and every option in the interface exists in the configuration. No decorative settings. A test enforces the first half: each of the 75 settings must be read somewhere other than its own declaration, its default value or its translation.
A profile changes only the settings listed here; everything else keeps your value.
| Profile | What it does |
|---|---|
| Maximum | Every capture layer on, every body kept, no cap on bodies, frames, messages or rows. Two layers stay your decision: the proxy, which asks for a permission, and worker frames, which change what a worker reads from self.location |
| Balanced | Everything observed, memory capped: sent bodies up to 256 KiB, received bodies up to 1 MiB, no binary data, no bodies for images, media and fonts; 500 frames of up to 16 KiB per socket, 500 messages per SSE stream; 20,000 requests in memory |
| Light | For very long sessions: no received bodies, no JavaScript stacks, no bodies for images, media, fonts, stylesheets and scripts; 100 frames of up to 4 KiB per socket, 100 messages per SSE stream; 5,000 requests |
| Quiet observation | Metadata only: no sent, received or binary bodies, no WebSocket frames, SSE messages or WebRTC data, no stacks; 10,000 requests |
- Interface (17) — language, simple mode, theme (dark, light or the system's), higher contrast, text size and adaptation to the window, clearing a tab on each navigation, table density, time format, following the stream by default, default scope, wrapping and JSON formatting of bodies, what the icon opens and where the detached window goes, the icon counter, a desktop notification on a critical alert
- Capture layers (17) — one switch per layer: webRequest, page-context probes, WebSocket frames, Server-Sent Events, PerformanceObserver, JavaScript call stacks, WebRTC, workers and Service Workers, WebSocket frames inside workers, cookies set from JavaScript, WebTransport, page perception metrics, navigation, cookie changes, TLS and certificates, DNS resolution, and the optional proxy layer
- Request and response bodies (10) — sent, received and binary bodies, a byte limit for each, a limit per frame or message, frames kept per socket, SSE messages kept per stream, resource types with no body capture. They apply to what the page probes read as well
- Duplicate-free correlation (3) — on or off, the correlation window, the sweep period
- Storage (3) — requests kept in memory, on-disk persistence, requests reloaded at startup
- Ingestion filters (4) — ignore the extension's own requests, ignored resource types, URLs to ignore, or the only URLs to capture
- Automatic analysis (9) — the analyser, and each family on its own: exposed secrets, transport, exploitable CORS, cookies, known trackers (a label, never an alert); masking of sensitive values; your own tracking domains and secret patterns
- Internal log (5) — the diagnostic log, internal errors, commands, internal events, entries kept
- Active interception (2) — the rules engine, request replay
- Live interception (5) — hold outgoing requests, incoming responses, or both; which requests to hold; the safety timeout that releases them
| Key | Effect |
|---|---|
Ctrl+K |
Open the command palette — every view, tool and action by name, and every reference entry by its protocol name |
/ |
Focus the search box |
↑ ↓ |
Open the previous / next request |
Esc |
Close the detail, the palette, a menu or the shortcut list |
P |
Pause or resume capture |
F |
Follow the stream or freeze it |
C |
Compare the two selected rows |
S |
Save the current filter |
1 to 9 |
Switch to a view |
? |
Show the shortcut list |
Ctrl+click |
Add a row to the selection |
Shift+click |
Select a range of rows |
Right click |
Row context menu |
Ctrl+Shift+Y |
Same as clicking the icon: the compact window, by default |
Alt+Shift+S |
Open the sidebar panel |
Alt+Shift+I |
Open the console in a tab |
Ctrl+Shift+U |
Pause / resume, even outside the console |
The last four work anywhere in Firefox. You can change them in about:addons, from the
gear menu, under extension shortcuts. The console's own list (?) and the Help read the
same table, ui/console/raccourcis.js.
Nothing.
- The extension issues no network request of its own, except the ones you explicitly trigger: a replay, a manual probe.
- No telemetry, no account, no server.
- Every reference table is bundled: the toolbox and the help work offline.
- All computation (digests, encryption, decoding) happens in the browser.
- The capture lives in memory. It is only written to disk if you enable persistence, and then only into the extension's own local storage.
- Exports are written where you ask for them, by Firefox's download manager.
The content security policy declared in the manifest forbids any external script:
script-src 'self'; object-src 'none'; child-src 'none'; frame-src 'none'.
No external dependencies, no build step. These are ES modules loaded directly by Firefox.
manifest.json Manifest V2, Firefox 115+
background/ The kernel — persistent background page
├── background.js Startup, badge, menus, shortcuts
├── core/
│ ├── config.js Configuration: defaults, persistence, broadcast
│ ├── store.js In-memory record store (60 fields per row)
│ ├── dedup.js Anti-duplicate correlator
│ ├── analyzer.js Analysis orchestration
│ ├── analyzer-regles.js The security rules and their evidence
│ ├── analyzer-faits.js Facts shown with their evidence, never counted as alerts
│ ├── webauthn-liens.js Links a passkey sign-in to its registration and challenge
│ ├── secrets.js Secret and tracker patterns
│ ├── persist.js Session kept across restarts
│ └── debug.js Internal log
├── capture/ The eight layers
│ ├── webrequest.js 9 webRequest events
│ ├── streamfilter.js Response bodies on the wire
│ ├── bodies.js Body decoding and decompression
│ ├── empreintes.js Digests computed on the bytes actually received
│ ├── security.js TLS and certificates
│ ├── dnsinfo.js DNS resolution
│ ├── navigation.js Page context
│ ├── cookies.js Cookie mutations
│ ├── proxy.js Proxy layer (optional)
│ └── probe.js Manual probe
├── ingest/ Non-webRequest inputs
│ ├── page.js Observations from the page probes
│ ├── promote.js Promotion of orphan observations
│ ├── har.js HAR import
│ └── curl.js curl command parsing
├── rules/
│ ├── engine.js Rule engine
│ └── intercept.js Manual interception
├── export/ HAR, JSON, CSV, Postman, OpenAPI 3.1, report, 39 code generators
├── api/ Command service for the UI, live broadcast
└── lib/ Shared kernel utilities
content/
├── bridge.js Page ↔ kernel bridge (content script)
└── hooks.js Probes injected into the page
ui/ The interface — one page for all four surfaces
├── console.html/.js Shell: header, navigation, view routing
├── app.js Shared state, kernel access, theme, scale
├── popup.html/.js The popup
├── theme.css, console.css Visual foundation (everything keys off --scale)
├── console/ One view per file, plus the detail panel
└── lib/ Codecs, digests, network, reference tables, i18n
tests/ 2767 assertions, no browser required
tools/sockets.mjs Real WebSockets against a real server: page, worker, WebRTC
tools/transparence.mjs What a page can tell about the probes — it should be nothing
tools/minutage.mjs Network timing phases in a real browser: only real measurements
tools/interaction.mjs Types into every filter and reads through redraws, in a real browser
tools/defilement.mjs Scrolls 20 000 rows in a real browser, looking for holes
tools/affichage.mjs Opens every view at four widths, looking for overflow
tools/captures.mjs Generates the documentation screenshots
tools/banniere.mjs Generates the social preview card (docs/images)
tools/vitrine.mjs Composes the showcase images at the top of this file
tools/demo.mjs Films the demonstration (docs/demo.mp4 and demo.gif)
tools/scene.mjs Real traffic through the real kernel, shared by the above
tools/chrome.mjs Headless Chromium over the DevTools protocol
tools/apercu-icone.mjs Renders the icon at the sizes Firefox actually uses
tools/apercu-theme.mjs Renders the console in a given theme, a row open, to judge it by eye
tools/verifier-openapi.mjs Builds OpenAPI documents and schemas for tools/verifier-openapi.py,
which checks them with openapi-spec-validator and jsonschema
build.ps1 Verification and .xpi packaging
- Every captured field is displayed. A test enforces it mechanically.
- No decorative settings. Every option has a real effect.
- No unprovable finding. Each one carries its evidence.
- Nothing is ever lost. An observation without a parent becomes its own row.
- Short, cohesive files. 200 to 400 lines typically.
- Every colour pair is measured. Both themes are verified against WCAG 2.1 contrast thresholds by a test; the build stops if any text falls below the reading threshold. A dark interface is easy to make pretty and unreadable.
- Nothing is capped for display. Long lists render in batches rather than being truncated; a long text shows its start, its exact length and a "Show all" button; a ranking says how many lines it has. A test fails on any new silent cut.
- Numbers keep their digits. JSON is read without converting its numbers, so an identifier beyond 2^53 is never shown rounded.
- A name from outside is only a name. Every table looked up with a value a server, a page
or a search provides has no prototype: a header or a claim called
constructoris shown as such, never as the JavaScript function of that name. A test renders every tab with them. - Comments explain why, not what.
The source comments and internal identifiers are in French, and that is deliberate rather than an oversight.
The interface is fully bilingual, but the translation layer works by using the French text itself as the lookup key:
t('Requetes') // → "Requests" in English mode, "Requetes" otherwiseui/lib/dict-en*.js maps French source strings to English. A string missing from the
dictionary simply displays its French original — never an empty label, never a raw key. That
design has a real benefit (the UI can never show a broken placeholder) and one consequence:
translating the source strings would break English mode entirely.
Error messages go through te(), which also recognises messages that carry a value —
caractere invalide dans le base32 : 9 becomes invalid character in the base32: 9 —
through templates with holes (ui/lib/dict-en-erreurs.js). The test suite refuses a
displayed text written straight into the page, a sentence built by concatenation, and an
error message without a translation or a template.
So the source stays French. Everything a reader or user meets — this README, the repository description, the releases — is in English, and the extension itself runs in English or French at the flip of a setting.
npm test2767 assertions, with no browser and no dependencies. The kernel and interface modules are
written for Firefox; tests/harnais.mjs supplies the minimum WebExtension API and DOM they
need to import and run under Node. The logic under test is exactly the logic that runs in
the browser, with no rewriting.
| Suite | Assertions | What it covers |
|---|---|---|
core.test.mjs |
240 | URL normalisation, correlation signatures, the store, the rule engine (both ways: what matches and what must not), the security analyser rule by rule, HAR export, curl import, all 39 code generators, and the settings applied to what the page probes send — a body the settings refuse is not kept |
avance.test.mjs |
469 | WebSocket and HTTP/2 frames, CSP, RFC 9111 freshness, multipart, canonical URLs and homographs, protocol tables, binary structures, rare digests, generators |
ui-load.test.mjs |
582 | Actual loading of the 192 interface modules, complete module graph (no dead import, no file outside the graph), consistency with the HTML pages and the manifest, full translation coverage — every displayed string must have a dictionary entry, including labels that reach the translator through a table (allRows labels, search help, CSP directive meanings), no text written straight into the page or built by concatenation, which no dictionary entry can match, and every text of the HTML pages — labels, tooltips, screen-reader names, placeholders — translated and set again by the script — and measured contrast: every colour pair in both themes is checked against the WCAG 2.1 thresholds. It also holds the documentation to the code: both help pages and both tutorials must have the same sections, item for item, every setting must be read by the code, the profiles must do what they say, and the README must name every settings group, profile, search criterion, analyser fact and kernel file, and state the right number of reference tables, link relations and proxy error types |
detail-coverage.test.mjs |
130 | Each of a record's 60 fields is displayed, each tab has a render function, each searchable field exists |
outils.test.mjs |
108 | The toolbox, against published vectors |
rendu.test.mjs |
114 | The interface actually rendered: sixteen views against three captures, the eleven detail tabs, the twenty-two toolbox panels against thirty-two hostile inputs, and 264 deliberately malformed HAR files — then a fragment-by-fragment comparison of both languages, so nothing can stay in French on an English screen. It also counts the commands each view sends the kernel: a view that re-renders itself in a loop is caught in a second instead of freezing the tab. Finally the translator itself reports every text it could not translate while everything is rendered in English: a French sentence that arrives through a variable can no longer hide. Then the table's Follow mode: a request opened from elsewhere is brought into view, reading suspends Follow and closing resumes it, the setting applies when it changes and only then; and what the screen shows — no bold markers left raw, punctuation that follows the language |
lectures.test.mjs |
166 | What 4.4 reads, checked against its sources: the HAR timing rules (TLS inside connect, counted once), every WebSocket subprotocol and every text that must receive no label, MQTT packet by packet, the certificate reader cross-checked against the OpenSSL X.509 parser built into Node, and the Firefox search criteria |
securite.test.mjs |
141 | The security tools against independent references — gzip and DEFLATE made by node:zlib, digests by node:crypto: the analyser's facts and the reused-nonce alert, digests judged only on what the captured bytes can prove, hostile SAML input and DEFLATE bombs, every response protection, the derived CSP, OAuth 2.0 against RFC 9700, and SAML in both bindings, down to which element is signed |
decodeurs.test.mjs |
209 | What 4.6 reads, against independent references: every example of RFC 9421 (Appendix B and section 2) — signature bases identical byte for byte, RSA-PSS, ECDSA, HMAC and Ed25519 signatures verified with the published keys, a tampered base refused — and every case where a base cannot be rebuilt; the strict RFC 9651 serialisation; WebAuthn registrations and sign-ins made by Yubico's fido2 in ES256, EdDSA and RS256, signatures verified and a tampered one refused; DNS messages made by dnspython and the RFC 8484 example, with hostile inputs (pointer loops, oversized names, truncation) |
protocoles.test.mjs |
267 | What 5.0 to 5.3 read, against independent references: GraphQL documents — the operations, the one that runs and introspection — compared with graphql-core, the Python port of the reference implementation, on 27 documents chosen for their traps; the Apollo persisted-query hash against the example Apollo publishes; the four transport forms, batches and the facts about responses; browser reports with the field names of the W3C and WICG specifications and the 30 NEL error types; report collection and the endpoints the browser ignores; JSON Schema inference and the OpenAPI 3.1 export — whose output is also checked with openapi-spec-validator and jsonschema by tools/verifier-openapi.mjs; JSON-RPC 2.0 against every exchange of its specification (§7) copied as is; SOAP 1.1 and 1.2 against the examples of the SOAP 1.1 Note and the SOAP 1.2 Primer; the RFC 9457 example; security.txt against the RFC 9116 example; and the measured compression, whose gzip is decompressed back to the identical text by node:zlib; the verdict in every case and its order of precedence, Fetch Metadata, Clear-Site-Data and source maps against their specifications |
signaux.test.mjs |
185 | What 5.4 and 5.5 read in response headers, against the texts that define them: the three HTTP date forms, and IMF-fixdate checked on five hundred instants against ECMA-262's toUTCString; every example of RFC 9745 (Deprecation), RFC 8594 (Sunset), RFC 9110 (Retry-After), RFC 8288 (Link), RFC 7838 (Alt-Svc), RFC 9211 (Cache-Status), RFC 9213 (CDN-Cache-Control) and RFC 9218 (Priority), RFC 9209 (Proxy-Status) and RFC 9532 (next-hop-aliases), and of drafts -11, -07 and -05 of the IETF RateLimit draft; the 134 link relations, and the 33 proxy error types with their recommended status and parameters, against snapshots of the IANA registries; the kernel tags, the summary, the verdict lines and the Headers tab in both languages; and hostile input — every reader takes 3,000 generated values and a 200 KB one without throwing, and 5,000 links or 10,000 exhausted quotas draw their tab in under a second |
comparaison.test.mjs |
156 | What 5.5 repaired, against independent references: the comparison engine gives the shortest list of changes on two thousand random cases (checked against the longest common subsequence computed by dynamic programming) and handles 200,000 lines; the unified diff is byte-identical to GNU diffutils 3.12 on eleven cases, finds the same 20,000 hunks as GNU on 200,000 lines, and re-applies to the second text on a thousand more (and on six hundred with GNU patch, run locally); the Comparison view — rendered with two requests in both languages — with repeated headers, CRLF and unknown measures; lossless JSON against JSON.parse on twenty thousand texts, and JSON.stringify on three thousand values; the toolbox transformations and the protocol readers with integers beyond 2^53, duplicate keys and __proto__; CBOR and MessagePack against vectors generated by the Python cbor2 and msgpack libraries, the 81 examples of RFC 8949 Appendix A and its 94 ill-formed sequences (Appendix F.1); the lazy JSON tree and long texts; the eleven detail tabs and several tools rendered, in both languages, for a request whose headers, token and bodies are full of names like constructor, __proto__ and toString — nothing inherited from JavaScript may show; and a guard that fails on any new silent cut in a view |
Expected values come from published sources: RFC vectors (4226, 6238, 6455, 4231, 7541, 9113,
3986, 7578, 9111, 8187, 2231, 2047, 6266, 7638, 8941, 9421, 9651, 8484, 9745, 8594, 9110, 8288,
7838, 9211, 9213, 9218, 9209, 9532, 8949, and ZeroMQ RFC 32), the IANA registries of link relations and proxy error
types, ECMA-262 for HTTP dates, GNU diffutils for the unified diff, cbor2 and msgpack for CBOR and MessagePack,
independent libraries (Yubico fido2 for WebAuthn, dnspython for DNS, graphql-core for
GraphQL, openapi-spec-validator and jsonschema for the OpenAPI export, node:crypto and
node:zlib), standard check values (all 20 CRC variants are verified against their
published check value for 123456789), FIPS 202 for SHA-3, RFC 7693 for BLAKE2, RFC 9562
for UUIDs.
.\build.ps1 -Verify # verify without building
.\build.ps1 # verify, test, then write dist/swift-<version>.xpiThe script checks that the 161 required files are present and that every surface declared in the manifest exists on disk, then runs the nine test suites. If a test fails, the build stops.
Works with Windows PowerShell 5.1 as well as PowerShell 7.
To regenerate the documentation screenshots:
node tools/captures.mjsThe nine test suites run under Node with no browser, which is what lets them run everywhere. Two things cannot be checked that way, because they only exist once a layout engine is involved — so they live as tools, and each prints a verdict and an exit code:
node tools/defilement.mjs # 20 000 rows, scrolled for real
node tools/affichage.mjs # every view at 350, 700, 1100 and 1600 px
node tools/sockets.mjs # every kind of socket, against a real server
node tools/transparence.mjs # what a page can tell about the probes
node tools/minutage.mjs # network timing: only what the browser measured
node tools/interaction.mjs # typing into every filter, reading through redrawsdefilement.mjs fills the store with 20 000 requests, then scrolls the table down and
back up a thousand steps. At each step it checks that the drawn rows follow one another
with no gap and no repeated index, that they cover the visible area from top to bottom,
that scrollHeight does not move between frames — it is what makes a scrollbar jump — and
that the batch-rendered lists keep posting until nothing is left. It then opens the detail
panel, which is the heaviest surface in the console, on a 20 000-frame WebSocket session
and a 5 MB response body, and measures both.
affichage.mjs opens every view at four widths, including the 350 px of a Firefox
sidebar, and looks for a page that overflows horizontally, an element wider than its frame
with nothing able to scroll it into view, two siblings that overlap when they should stack,
and text cut off with nothing to say so.
transparence.mjs is the one that guards the promise at the top of
content/hooks.js: the probes are purely passive. A page that notices fetch has been
replaced is no longer being observed — it is being changed, and plenty of sites check
exactly that and behave differently when they find it. The tool has a page measure itself,
installs the probes, has it measure itself again, and compares 42 observations: the name,
length and prototype of every replaced function, instanceof and prototype chains,
Function.prototype.toString — the [native code] check — property descriptors, the type
of error thrown by malformed calls, and subclassing. Anything that differs is a finding.
sockets.mjs starts a real WebSocket server — RFC 6455 handshake and framing, fifty
lines, no dependency — then opens real connections from a page, from a Worker, from a
SharedWorker, and a WebRTC data channel between two peer connections. It checks that every
connection, every text frame, every binary frame with its bytes, the negotiated
sub-protocol and the close code are captured — and that the page is not disturbed: its
worker still receives its own messages, and a relative importScripts inside that worker
still resolves.
minutage.mjs loads, in a real browser, a same-origin resource served after a known
delay and two resources from another origin — one without Timing-Allow-Origin, one
with it — and checks what the real probe reports. The wait must reflect the server's delay,
no "sending" time may be invented, the phases must not add up to more than the request
lasted, and a resource whose detail the browser hid must be reported as hidden — not as
0 ms of DNS, 0 bytes, and a "reception" equal to the absolute clock, which is what the
previous probe reported (1219 ms for a 25-byte file).
interaction.mjs checks two gestures that only a real browser and a real keyboard can
catch. It types into every filter field of the console, one key at a time, and checks
that the field kept the focus and everything typed — seven of them used to keep only the
first letter, or lose the focus after a pause, because the view rebuilt the field it was
typing into. Then it scrolls 6,000 px into a 3,000-frame WebSocket session and through a
fully expanded site tree, makes the view redraw the way live traffic does, and checks the
reader is still exactly where they were — they used to be sent back to the top several
times a second.
Bug reports and suggestions are welcome.
For a useful bug report: open the Internal log view, export the diagnostics, and attach the file. It contains internal errors and the command log — not your traffic.
Before opening a pull request:
npm test # all 2767 assertions must pass
.\build.ps1 -Verify # the build must be greenRepository conventions: dependency-free ES modules, files of 200 to 400 lines, French comments that explain the why (see section 19), and one non-negotiable rule — no feature that claims something it cannot prove.
CONTRIBUTING.md has the practical detail: what the build checks beyond the tests, how to add a decoder, and why a string assembled at runtime can never be translated. CODE_OF_CONDUCT.md covers the rest.
MIT — © 2026 NeoZ
SWIFT · created by NeoZ
See everything. Invent nothing.





















