` is reported as from `bank.example`. Read the From row in Message and every header before you trust the domain.
### RFC 5321 SMTP trace information
[RFC 5321](https://www.rfc-editor.org/rfc/rfc5321), Draft Standard, updated by [RFC 7504](https://www.rfc-editor.org/rfc/rfc7504).
| Section | Requirement | What the tool does |
| --- | --- | --- |
| [4.4](https://www.rfc-editor.org/rfc/rfc5321#section-4.4) | Received lines are prepended and never reordered | Reverses them to get the route oldest first |
| [4.4](https://www.rfc-editor.org/rfc/rfc5321#section-4.4) | Stamp: `FROM`, `BY`, optional `WITH`, then `;` and date-time | Takes the first `from`, `by` and `with` words and the text after the last `;` |
| [4.4](https://www.rfc-editor.org/rfc/rfc5321#section-4.4) | The FROM clause SHOULD carry the address from the TCP connection | Takes the first address in the clause, with fallbacks described under Method |
| [4.4](https://www.rfc-editor.org/rfc/rfc5321#section-4.4) | obs- date forms MUST NOT be used in SMTP | Accepts them; a stamp in `EST` is read without a warning |
| [4.4](https://www.rfc-editor.org/rfc/rfc5321#section-4.4) | Return-Path preserves the MAIL FROM reverse-path | Uses the Return-Path domain as the SPF identity for alignment |
| [4.1.3](https://www.rfc-editor.org/rfc/rfc5321#section-4.1.3) | IPv6 address literal `[IPv6:...]` | Recognised in the `from` clause |
Received headers below your own receiver were written by servers you do not control. Section 4.4 says a mail program "MUST NOT change or delete a Received: line", but nothing stops a sender from adding false ones before the first real hop. The tool draws them like any other hop.
### RFC 3848 and RFC 6531 transmission types
[RFC 3848](https://www.rfc-editor.org/rfc/rfc3848), Draft Standard, registers `ESMTPA`, `ESMTPS`, `ESMTPSA`, `LMTP`, `LMTPA`, `LMTPS` and `LMTPSA` for the `with` clause ([section 1](https://www.rfc-editor.org/rfc/rfc3848#section-1)). [RFC 6531](https://www.rfc-editor.org/rfc/rfc6531), Proposed Standard, adds the `UTF8SMTP` family, including `UTF8SMTPS` "ESMTP with SMTPUTF8 and STARTTLS" ([section 4.3](https://www.rfc-editor.org/rfc/rfc6531#section-4.3)).
| Type | Means encrypted | Tool counts it as TLS |
| --- | --- | --- |
| `ESMTPS`, `ESMTPSA` | Yes | Yes |
| `LMTPS`, `LMTPSA` | Yes | Only if `TLS` appears elsewhere in the header |
| `UTF8SMTPS`, `UTF8SMTPSA` | Yes | Only if `TLS` appears elsewhere in the header |
| Any type, with `TLS` in a comment | Not defined by these RFCs | Yes |
The No TLS finding only applies to hops with a public address, so the usual LMTP hop to a mailbox store on an internal address is not flagged. [RFC 3848 section 3](https://www.rfc-editor.org/rfc/rfc3848#section-3) warns that these keywords "should not be used for mail filtering or relaying decisions except in very controlled environments"; the tool uses them only for a CHECK.
### RFC 8601 Authentication-Results: parsing
[RFC 8601](https://www.rfc-editor.org/rfc/rfc8601), Proposed Standard.
| Section | Requirement | What the tool does |
| --- | --- | --- |
| [2.2](https://www.rfc-editor.org/rfc/rfc8601#section-2.2) | `authserv-id [version] (no-result / 1*resinfo)` | Splits on every `;` and shows the first part, version included, as the server |
| [2.2](https://www.rfc-editor.org/rfc/rfc8601#section-2.2) | `no-result` is the literal `none` | Not recognised: the header is reported as missing |
| [2.2](https://www.rfc-editor.org/rfc/rfc8601#section-2.2) | Comments (CFWS) may appear between tokens | A `;` inside a comment or quoted `reason` cuts the entry short and its properties are lost |
| [2.3](https://www.rfc-editor.org/rfc/rfc8601#section-2.3) | `ptype.property=pvalue` | Read for every dotted property; `reason` is not read |
| [2.6](https://www.rfc-editor.org/rfc/rfc8601#section-2.6) | A method may carry a version, `dkim/1` | Not recognised: the entry is dropped |
| [2.7.1](https://www.rfc-editor.org/rfc/rfc8601#section-2.7.1), [2.7.2](https://www.rfc-editor.org/rfc/rfc8601#section-2.7.2) | DKIM and SPF result sets | Pass, none and failure, as listed under Method |
| [4.1](https://www.rfc-editor.org/rfc/rfc8601#section-4.1) | Ignore unregistered results and unsupported methods | Shows only `spf`, `dkim` and `dmarc`; other methods are read but not displayed |
The `;` problem is not theoretical: the expanded example in [RFC 7208 section 9.2](https://www.rfc-editor.org/rfc/rfc7208#section-9.2) has `reason="client-ip=192.0.2.1; smtp.helo=foo.example.com"`, which the tool splits in two.
### RFC 8601 Authentication-Results: trust
| Section | Requirement | What the tool does |
| --- | --- | --- |
| [2.5](https://www.rfc-editor.org/rfc/rfc8601#section-2.5) | Consumers SHOULD use the authserv-id to decide whether to use the header | Shows it; you decide |
| [4.1](https://www.rfc-editor.org/rfc/rfc8601#section-4.1) | MUAs and filters "SHOULD NOT interpret this header field unless specifically configured to do so" | You start the reading by pasting; there is no trusted-server list |
| [5](https://www.rfc-editor.org/rfc/rfc8601#section-5) | The receiving MTA MUST delete forged copies claiming its own authserv-id | Out of scope: the tool is not an MTA and assumes your receiver did this |
| [7.1](https://www.rfc-editor.org/rfc/rfc8601#section-7.1) | Forged header fields: a trusted list "should initially be empty" | Always adds the INFO finding that results are reported, not verified |
RFC 8601 says processing should not be on by default; the tool reads the header because that is its whole job, and every result is labelled as the receiver's claim. If the topmost header was added by a server outside your own mail system, or by the sender, treat every result on the page as unverified.
### RFC 7208 SPF
[RFC 7208](https://www.rfc-editor.org/rfc/rfc7208), Proposed Standard, updated by [RFC 7372](https://www.rfc-editor.org/rfc/rfc7372), [RFC 8553](https://www.rfc-editor.org/rfc/rfc8553) and [RFC 8616](https://www.rfc-editor.org/rfc/rfc8616).
| Section | Requirement | What the tool does |
| --- | --- | --- |
| [2.4](https://www.rfc-editor.org/rfc/rfc7208#section-2.4) | With a null reverse-path, MAIL FROM is `postmaster@` the HELO identity | Not applied: a Return-Path of `<>` has no domain, so the Return-Path row shows none |
| [2.6](https://www.rfc-editor.org/rfc/rfc7208#section-2.6) | Seven results: none, neutral, pass, fail, softfail, temperror, permerror | Displays the recorded result as written |
| [4](https://www.rfc-editor.org/rfc/rfc7208#section-4) | check_host() evaluation | Not performed; no DNS query is made |
| [8.2](https://www.rfc-editor.org/rfc/rfc7208#section-8.2) | Neutral MUST be treated exactly like none | Both are informational |
| [8.5](https://www.rfc-editor.org/rfc/rfc7208#section-8.5) | Softfail is between fail and none | Reported as CHECK |
| [9.2](https://www.rfc-editor.org/rfc/rfc7208#section-9.2) | SPF results in Authentication-Results with `smtp.mailfrom` | Shows `smtp.mailfrom` as the checked identity; alignment uses Return-Path instead |
The `smtp.mailfrom` the receiver recorded and the Return-Path domain usually agree. When they do not, the SPF tile and the alignment table show different domains.
### RFC 6376 DKIM
[RFC 6376](https://www.rfc-editor.org/rfc/rfc6376), Internet Standard, updated by [RFC 8301](https://www.rfc-editor.org/rfc/rfc8301), [RFC 8463](https://www.rfc-editor.org/rfc/rfc8463), [RFC 8553](https://www.rfc-editor.org/rfc/rfc8553) and [RFC 8616](https://www.rfc-editor.org/rfc/rfc8616).
| Section | Requirement | What the tool does |
| --- | --- | --- |
| [3.2](https://www.rfc-editor.org/rfc/rfc6376#section-3.2) | DKIM-Signature is a tag=value list | Parsed for `d=`, `s=` and `a=` |
| [3.5](https://www.rfc-editor.org/rfc/rfc6376#section-3.5) | `d=` is the signing domain (SDID) | Compared with the From domain for alignment |
| [6](https://www.rfc-editor.org/rfc/rfc6376#section-6) | "Verifiers MUST produce a result that is semantically equivalent to applying the steps listed in Sections 6.1, 6.1.1, and 6.1.2" | Not a verifier: no key lookup, no body hash, no signature check |
Because it does not verify, the tool compares every `d=` it finds, including signatures the receiver marked as failed. With one aligned signature that failed and an unaligned one that passed, it still says DMARC passed through DKIM. RFC 9989 counts only a validated `d=` ([section 3.2.3](https://www.rfc-editor.org/rfc/rfc9989#section-3.2.3)). The receiver's own DMARC result is still the one shown.
### RFC 9989 DMARC
[RFC 9989](https://www.rfc-editor.org/rfc/rfc9989), Proposed Standard, May 2026. It obsoletes RFC 7489 together with [RFC 9990](https://www.rfc-editor.org/rfc/rfc9990) (aggregate reporting) and [RFC 9991](https://www.rfc-editor.org/rfc/rfc9991) (failure reporting); the tool uses neither report format.
| Section | Requirement | What the tool does |
| --- | --- | --- |
| [3.2.4](https://www.rfc-editor.org/rfc/rfc9989#section-3.2.4) | The SPF Domain is the SPF-validated MAIL FROM | Uses the Return-Path domain |
| [3.2.10.1](https://www.rfc-editor.org/rfc/rfc9989#section-3.2.10.1) | Relaxed alignment: same Organizational Domain | Not evaluated |
| [3.2.10.2](https://www.rfc-editor.org/rfc/rfc9989#section-3.2.10.2) | Strict alignment: identical domains | Evaluated, without checking that SPF or DKIM passed |
| [4.4](https://www.rfc-editor.org/rfc/rfc9989#section-4.4) | Domain comparisons are case-insensitive | Lower-cases before comparing |
| [4.10](https://www.rfc-editor.org/rfc/rfc9989#section-4.10) | DNS tree walk for policy and Organizational Domain | Not performed |
| [5.3.1](https://www.rfc-editor.org/rfc/rfc9989#section-5.3.1) | Zero or several From domains: validation is not possible | Uses the first domain and carries on |
| [5.3.5](https://www.rfc-editor.org/rfc/rfc9989#section-5.3.5) | Pass when an Authenticated Identifier aligns | Takes the receiver's recorded result |
| [9.2](https://www.rfc-editor.org/rfc/rfc9989#section-9.2) | Result names pass, fail, none, temperror, permerror | Pass is PASS; none is INFO; temperror is CHECK; fail and permerror are FAIL |
The `dmarc=none` result is informational: it indicates no applicable policy, rather than a failed authentication check.
### RFC 8617 ARC
[RFC 8617](https://www.rfc-editor.org/rfc/rfc8617), Experimental. Not on the standards track.
| Section | Requirement | What the tool does |
| --- | --- | --- |
| [4.1.1](https://www.rfc-editor.org/rfc/rfc8617#section-4.1.1) | AAR is `instance ";" authres-payload` | Read only when no Authentication-Results header exists; the instance tag `i=1` is shown as the server name and the real authserv-id is skipped |
| [4.2.1](https://www.rfc-editor.org/rfc/rfc8617#section-4.2.1) | Instance tags order ARC sets | Not used; the topmost AAR is taken |
| [5.2](https://www.rfc-editor.org/rfc/rfc8617#section-5.2) | Validator actions on ARC-Seal and ARC-Message-Signature | Not performed |
An ARC-Authentication-Results header records what an intermediary saw, and without seal validation anyone could have written it.
### RFC 2047 encoded words
[RFC 2047](https://www.rfc-editor.org/rfc/rfc2047), Draft Standard. Encoded words ([section 2](https://www.rfc-editor.org/rfc/rfc2047#section-2)) are not decoded; From, Subject and other values are shown as written. The From domain is unaffected, because encoded words may not appear in an addr-spec ([section 5](https://www.rfc-editor.org/rfc/rfc2047#section-5)).
## Finding reference
The RFC section column links the rule each finding comes from. "none" marks a Tamperbox recommendation that no specification requires.
### Authentication findings
| Finding | Severity | What it means | What to do | RFC section |
| --- | --- | --- | --- | --- |
| Reported results, not independent verification | INFO | The receiver's word, not a re-check | Confirm the reporting server is yours | [RFC 8601 §7.1](https://www.rfc-editor.org/rfc/rfc8601#section-7.1) |
| No Authentication-Results header | INFO | Nothing was recorded, the header says `none`, or the headers came from the sender's side | Copy from the received message | [RFC 8601 §2.2](https://www.rfc-editor.org/rfc/rfc8601#section-2.2) |
| DMARC passed | PASS | The receiver found an aligned SPF or DKIM pass | | [RFC 9989 §5.3.5](https://www.rfc-editor.org/rfc/rfc9989#section-5.3.5) |
| DMARC fail (or other) | FAIL | The From domain was not proven; `none` means it has no DMARC policy | Treat as unverified; if yours, fix SPF or DKIM alignment | [RFC 9989 §9.2](https://www.rfc-editor.org/rfc/rfc9989#section-9.2) |
| SPF passed but is not aligned | INFO | Normal for a mail service; DMARC relies on DKIM | Nothing | [RFC 9989 §4.4.2](https://www.rfc-editor.org/rfc/rfc9989#section-4.4.2) |
| SPF fail, permerror | FAIL | The receiver reports failure or a permanent evaluation error | Add the server to the record, if the message is yours | [RFC 7208 §2.6](https://www.rfc-editor.org/rfc/rfc7208#section-2.6) |
| DKIM none | INFO | The message was not signed | Sign outgoing mail | [RFC 8601 §2.7.1](https://www.rfc-editor.org/rfc/rfc8601#section-2.7.1) |
| DKIM fail (or other) | FAIL | The signature did not verify or could not be checked | Check the key; look for a forwarder that altered the message | [RFC 8601 §2.7.1](https://www.rfc-editor.org/rfc/rfc8601#section-2.7.1) |
### Identity and route findings
| Finding | Severity | What it means | What to do | RFC section |
| --- | --- | --- | --- | --- |
| Replies go to X | CHECK | Answers leave the From domain | Verify the sender before replying | none (Tamperbox heuristic) |
| N s at server | CHECK | The longest wait on the route | Nothing, unless it repeats | none (Tamperbox threshold) |
| Clock skew at hop N | INFO | Server clocks disagree; header order still holds | Fix the clock if the server is yours | [RFC 5321 §4.4](https://www.rfc-editor.org/rfc/rfc5321#section-4.4) |
| No TLS into server | CHECK | The hop was not recorded as encrypted | Enable STARTTLS; publish MTA-STS | [RFC 3848 §1](https://www.rfc-editor.org/rfc/rfc3848#section-1) |
## Guide
### Is this message genuine?
1. Confirm that the server named in Authentication-Results is your own receiving server; everything below it was written by the sender.
2. Read the DMARC result. A pass means the From domain was proven; a fail means it was not.
3. Check Reply-To and Return-Path. A reply address on another domain is the common shape of phishing that asks you to answer.
4. Read the From header itself in Message and every header. The display name may claim a different address than the actual mailbox; compare them.
5. If in doubt, contact the supposed sender through a channel you already have.
### My own mail fails DMARC
1. Open the DKIM key that signed it (Take it further) and confirm it is published and strong enough.
2. Open SPF for the bounce domain and confirm the sending service is listed.
3. Make the service sign with your domain, not its own, so DKIM aligns even when the bounce domain does not.
### A long delay
A wait over 30 seconds at one server is usually a queue, a content filter or a greylisting retry. If it repeats, ask the operator of that server. The route names the server that held the message.
### Clock skew
A hop stamped before the one under it means a server's clock is wrong. The order of the headers is what counts. Fix the clock with NTP if the server is yours.
### Break it cases
Each case is built from the headers you pasted, keeping every other line.
| Case | What it does to the headers |
| --- | --- |
| Reply-To elsewhere | Sets Reply-To to a look-alike domain |
| All checks fail | Replaces Authentication-Results with spf=fail, dkim=none, dmarc=fail and a look-alike Return-Path |
| Clock skew | Restamps the newest Received header 70 seconds before the one under it |
| Not headers | Loads a sentence that is not a header block |
## Worked example
The page opens on an order confirmation sent through a mail service, built from documentation addresses and the reserved `.example` zone. Four hops: the sender's laptop to `out.harbor-supply.example`, to the mail service `mail-sor-f17.inboxcloud.example`, to `mx.receiver.example`, to the mailbox store, 41 seconds end to end, 38 of them at the mail service.
**Receiver reports: DMARC passed through DKIM.** SPF passed for `send.bulkpost.example`, the mail service's bounce domain, so the only aligned identity is the DKIM signature `d=harbor-supply.example; s=google`. The findings are the 38-second wait (CHECK), two INFO notes, and the DMARC pass. Look up hop addresses answers from local sample data, so nothing is sent. Replace the sample with your own headers and every section, card and link is rewritten for them.
## For agents
The Tamperbox MCP server runs the same analysis. Search its catalog for the header operation, inspect the input schema, and call it with the header block. Keep the warning block with any conclusion:
- Results are what the receiving server recorded; nothing is re-verified.
- Headers below the trusted receiver can be forged.
- Alignment is an exact domain match; delays depend on each server's clock.
See [Connect an MCP client](https://docs.tamperbox.dev/mcp/connect/) and [Code mode](https://docs.tamperbox.dev/mcp/code-mode/) for the calling pattern, and [Security and privacy](https://docs.tamperbox.dev/reference/security/) for what travels when addresses are looked up.
## Limits
- No signature or ARC verification; no DNS lookup for the authentication section.
- Exact alignment only.
- The topmost Authentication-Results header only.
- From supports a single mailbox with display names and comments; ambiguous lists remain unknown and encoded words are not decoded.
- Hop lookups: up to 50 public addresses, only on request; country is registry data, not location.
- Nothing is stored.
## References
| Specification | Status | Sections used |
| --- | --- | --- |
| [RFC 5322, Internet Message Format](https://www.rfc-editor.org/rfc/rfc5322) | Draft Standard, updated by RFC 6854 | [2.1](https://www.rfc-editor.org/rfc/rfc5322#section-2.1), [2.2](https://www.rfc-editor.org/rfc/rfc5322#section-2.2), [2.2.3](https://www.rfc-editor.org/rfc/rfc5322#section-2.2.3), [3.3](https://www.rfc-editor.org/rfc/rfc5322#section-3.3), [3.4](https://www.rfc-editor.org/rfc/rfc5322#section-3.4), [3.6.2](https://www.rfc-editor.org/rfc/rfc5322#section-3.6.2), [3.6.7](https://www.rfc-editor.org/rfc/rfc5322#section-3.6.7) |
| [RFC 5321, Simple Mail Transfer Protocol](https://www.rfc-editor.org/rfc/rfc5321) | Draft Standard, updated by RFC 7504 | [4.1.3](https://www.rfc-editor.org/rfc/rfc5321#section-4.1.3), [4.4](https://www.rfc-editor.org/rfc/rfc5321#section-4.4) |
| [RFC 3848, ESMTP and LMTP Transmission Types Registration](https://www.rfc-editor.org/rfc/rfc3848) | Draft Standard | [1](https://www.rfc-editor.org/rfc/rfc3848#section-1), [3](https://www.rfc-editor.org/rfc/rfc3848#section-3) |
| [RFC 6531, SMTP Extension for Internationalized Email](https://www.rfc-editor.org/rfc/rfc6531) | Proposed Standard | [4.3](https://www.rfc-editor.org/rfc/rfc6531#section-4.3) |
| [RFC 8601, Message Header Field for Indicating Message Authentication Status](https://www.rfc-editor.org/rfc/rfc8601) | Proposed Standard | [2.2](https://www.rfc-editor.org/rfc/rfc8601#section-2.2), [2.3](https://www.rfc-editor.org/rfc/rfc8601#section-2.3), [2.5](https://www.rfc-editor.org/rfc/rfc8601#section-2.5), [2.6](https://www.rfc-editor.org/rfc/rfc8601#section-2.6), [2.7.1](https://www.rfc-editor.org/rfc/rfc8601#section-2.7.1), [2.7.2](https://www.rfc-editor.org/rfc/rfc8601#section-2.7.2), [4.1](https://www.rfc-editor.org/rfc/rfc8601#section-4.1), [5](https://www.rfc-editor.org/rfc/rfc8601#section-5), [7.1](https://www.rfc-editor.org/rfc/rfc8601#section-7.1) |
| [RFC 7208, Sender Policy Framework](https://www.rfc-editor.org/rfc/rfc7208) | Proposed Standard, updated by RFC 7372, 8553, 8616 | [2.4](https://www.rfc-editor.org/rfc/rfc7208#section-2.4), [2.6](https://www.rfc-editor.org/rfc/rfc7208#section-2.6), [4](https://www.rfc-editor.org/rfc/rfc7208#section-4), [8.2](https://www.rfc-editor.org/rfc/rfc7208#section-8.2), [8.5](https://www.rfc-editor.org/rfc/rfc7208#section-8.5), [9.2](https://www.rfc-editor.org/rfc/rfc7208#section-9.2) |
| [RFC 6376, DomainKeys Identified Mail Signatures](https://www.rfc-editor.org/rfc/rfc6376) | Internet Standard, updated by RFC 8301, 8463, 8553, 8616 | [3.2](https://www.rfc-editor.org/rfc/rfc6376#section-3.2), [3.5](https://www.rfc-editor.org/rfc/rfc6376#section-3.5), [6](https://www.rfc-editor.org/rfc/rfc6376#section-6) |
| [RFC 9989, DMARC](https://www.rfc-editor.org/rfc/rfc9989) | Proposed Standard; obsoletes RFC 7489 with RFC 9990 and 9991 | [3.2.3](https://www.rfc-editor.org/rfc/rfc9989#section-3.2.3), [3.2.4](https://www.rfc-editor.org/rfc/rfc9989#section-3.2.4), [3.2.10.1](https://www.rfc-editor.org/rfc/rfc9989#section-3.2.10.1), [3.2.10.2](https://www.rfc-editor.org/rfc/rfc9989#section-3.2.10.2), [4.4](https://www.rfc-editor.org/rfc/rfc9989#section-4.4), [4.4.2](https://www.rfc-editor.org/rfc/rfc9989#section-4.4.2), [4.10](https://www.rfc-editor.org/rfc/rfc9989#section-4.10), [5.3.1](https://www.rfc-editor.org/rfc/rfc9989#section-5.3.1), [5.3.5](https://www.rfc-editor.org/rfc/rfc9989#section-5.3.5), [9.2](https://www.rfc-editor.org/rfc/rfc9989#section-9.2) |
| [RFC 9990, DMARC Aggregate Reporting](https://www.rfc-editor.org/rfc/rfc9990) | Proposed Standard | none; cited as a successor of RFC 7489 |
| [RFC 9991, DMARC Failure Reporting](https://www.rfc-editor.org/rfc/rfc9991) | Proposed Standard | none; cited as a successor of RFC 7489 |
| [RFC 8617, Authenticated Received Chain](https://www.rfc-editor.org/rfc/rfc8617) | Experimental | [4.1.1](https://www.rfc-editor.org/rfc/rfc8617#section-4.1.1), [4.2.1](https://www.rfc-editor.org/rfc/rfc8617#section-4.2.1), [5.2](https://www.rfc-editor.org/rfc/rfc8617#section-5.2) |
| [RFC 2047, MIME Part Three: Message Header Extensions](https://www.rfc-editor.org/rfc/rfc2047) | Draft Standard, updated by RFC 2184, 2231 | [2](https://www.rfc-editor.org/rfc/rfc2047#section-2), [5](https://www.rfc-editor.org/rfc/rfc2047#section-5) |
RFC 9990 and RFC 9991 are cited only as the documents that, with RFC 9989, replace RFC 7489; the tool reads no DMARC reports. For the key behind a message's signature, see the [DKIM Record Checker](https://docs.tamperbox.dev/tools/dkim-checker/).
---
# Email Provider Lookup
How Tamperbox attributes public MX hostnames to mailbox services and gateways, handles mixed routes, and preserves uncertainty about the downstream mailbox host.
Canonical: https://docs.tamperbox.dev/tools/email-provider/
Updated: 2026-10-24
Applies to: tamperbox.dev, October 2026
Prerequisites: A domain whose inbound mail route you want to attribute
Outputs: A provider verdict, route-by-route hostname evidence, public MX records and limitations
Limitations: Attribution is inferred from published MX hostnames and cannot prove mailbox ownership or reveal a host concealed behind a gateway
The tool page is [tamperbox.dev/email-provider](https://tamperbox.dev/email-provider). It opens on a live lookup of `tamperbox.dev`; replace that domain to investigate another inbound route. This guide documents exactly how the catalog matches hostnames, how routes are grouped and why some answers must remain unknown.
Where processing happens: pasted text is cleaned and validated in the browser. The domain is sent to the Tamperbox server, which asks a public resolver for MX records. Provider grouping runs on the server response. Nothing is stored, no email is sent and no SMTP connection is opened.
## What the tool answers
The lookup answers which recognised service appears first in a domain's public inbound mail route. It distinguishes mailbox services from gateways because those statements have different evidence. A Microsoft 365 hostname can support “likely mailbox service”; a Mimecast hostname supports only “gateway detected, downstream mailbox unknown.”
It also answers whether the route is mixed, partly recognised, entirely unknown, missing, explicitly disabled by null MX or inconclusive because DNS evidence cannot be interpreted. Each provider row names the exact hostname and preference behind the attribution.
The tool does not infer from SPF, website hosting, nameservers, branding or a mail vendor's customer list. Those signals can belong to outgoing mail or unrelated infrastructure and would turn a bounded MX observation into a guess.
## Inputs
### Domain names
Enter a public domain such as `example.com`. Browser validation lowercases capitals, removes one final dot, normalises full-width dots and converts international labels to their ASCII form. The parser refuses IP addresses, ports, paths, underscores, missing top-level domains, empty or oversized labels and labels with invalid hyphen placement.
A maximum of 1,024 source characters is accepted. The canonical DNS name may be at most 253 characters, with labels of 63 characters or fewer. The server repeats the same validation before it makes a request.
### Pasted text and files
Pasting `https://example.com/about`, `person@example.com`, `mailto:person@example.com`, or a quoted variant keeps only `example.com`, and the page announces the transformation. Text that yields no valid domain remains unchanged so the specific parser refusal is visible. Ordinary typing is never silently rewritten.
The drop zone accepts a text file, a page-wide file drop and paste outside another field. File bytes are read in the browser and are not uploaded; only a valid domain derived from the text can be sent for DNS lookup.
### Reserved synthetic input
Entering `harbor-supply.example` uses a fixed local mail-routing fixture instead of sending DNS requests. It contains one synthetic MX route and an unknown provider. This exception is for the documented reserved example only; other domains use the live server path. The checked line labels the synthetic run, and its records cannot demonstrate real delivery or provider ownership.
### Lookup timing and cancellation
The request runs 700 milliseconds after typing stops, immediately on Enter, on field change, after a shared handoff, or through Refresh lookup. Clearing or editing cancels the active request and invalidates its generation. A late response cannot restore stale routes, cards or Copy result.
## Reading the result
### Verdict and findings
The verdict answers the provider question: likely mailbox service, gateway, mixed route, unknown route, no provider or inconclusive. Chips show mail-routing state and each recognised service. Findings explain the route classification and the provider inference separately, worst first, so valid MX publication cannot hide an uncertain attribution.
The privacy pair states what is local and what leaves the page. Copy result is available only for a completed result. Empty, invalid, loading and failed states hide stale evidence and copying.
### Inbound routes and DNS evidence
Inbound routes are sorted by MX preference and hostname. Each row has provider, role, matched hostname and priority; the lowest preference is marked preferred. An unknown row stays “Not recognized,” not “self-hosted.”
DNS evidence shows the underlying MX set and TTL. It remains the source of truth if a catalog label is questioned. History follows all evidence and supports Revert and Reset input; Take it further follows History and stays hidden because no honest same-category derived-value receiver exists today.
## Method
### Obtain usable MX evidence
The shared routing engine follows [RFC 5321 section 5](https://www.rfc-editor.org/rfc/rfc5321#section-5): MX is queried first, CNAME traversal is bounded to eight hops, and every answer must have a preference from 0 to 65,535 plus a valid target. The normative sequence says a sender “first performs a DNS lookup for an MX record.”
NXDOMAIN, null MX, empty MX with address fallback, no route, resolver failure and invalid MX are classified before provider matching. The provider catalog never overrides routing. A Google-looking hostname in a report whose routing status is not `mx` cannot produce a provider identification.
### Match complete hostnames
Each MX target is lowercased and its final dot removed. Rules are anchored regular expressions for complete names. `route1.mx.cloudflare.net` matches Cloudflare Email Routing; `route1.mx.cloudflare.net.attacker.example` does not. Evidence is deduplicated by hostname and preference.
The current catalog recognises Cloudflare Email Routing, Gmail consumer inbound, Google Workspace, Microsoft 365, Zoho Mail, Proton Mail, Fastmail, Mimecast, Proofpoint and Barracuda. Catalog membership is a Tamperbox inference, not an RFC registry.
### Group services and roles
Matches are grouped by provider id. Every group carries a display name, a role of mailbox or gateway, and its supporting routes. A route at the lowest numeric preference is marked preferred. Several routes for one provider remain one provider group because they are redundant endpoints of the same published service.
Gateway rules take precedence in interpretation, not DNS ordering. A gateway is the public receiving edge; it may filter, reject, archive or forward mail, but the hostname does not reveal where the mailbox is ultimately stored.
### Decide the provider status
Exactly one recognised mailbox provider with no unknown routes is `identified`, and the mailbox host is recorded with confidence `inferred`. Any recognised gateway without another group is `gateway`. More than one provider group, or any recognised group plus unknown MX routes, is `mixed`. No recognised group is `unknown`.
Non-MX routing states map to `no_mx` except resolver failure and invalid MX, which map to `inconclusive`. This preserves the difference between “no provider can exist in this evidence” and “the check could not tell.”
## Standards and conformance
### RFC 5321 routing evidence
| Section | Requirement | How the tool applies it |
| --- | --- | --- |
| [5](https://www.rfc-editor.org/rfc/rfc5321#section-5) | Locate SMTP destinations through MX | Reads MX before any provider inference |
| [5.1](https://www.rfc-editor.org/rfc/rfc5321#section-5.1) | Lower preference values are preferred | Sorts and marks the preferred public routes |
| [5.1](https://www.rfc-editor.org/rfc/rfc5321#section-5.1) | Equal-preference hosts are alternatives | Keeps every row; does not select one mailbox owner |
RFC 5321 defines routing, not provider identity. The tool conforms to the route ordering but adds a clearly labelled catalog inference. It stops before resolving targets, connecting to SMTP, following delivery through gateways or establishing an account relationship.
### RFC 7505 negative evidence
| Section | Requirement | How the tool applies it |
| --- | --- | --- |
| [3](https://www.rfc-editor.org/rfc/rfc7505#section-3) | A domain accepting no mail publishes a single `0 .` MX | Reports no provider because the domain explicitly refuses mail |
| [3](https://www.rfc-editor.org/rfc/rfc7505#section-3) | Null MX must not be mixed with ordinary routing | Shared routing marks a mixed or nonzero form invalid and provider inference inconclusive |
The provider catalog does not run on a null MX. This prevents the absence of service from being confused with an unknown service.
### Catalog conformance boundary
Provider rules are implementation data maintained with the code and its tests. Anchored patterns prevent substring impersonation; tests cover supported names, deceptive suffixes, mixed routes, duplicates, case and final dots. No vendor document standardises all customer MX patterns, so the catalog describes observed public naming contracts and can lag a provider change.
## Finding reference
### Positive and qualified attribution
| Finding | Severity | Evidence | Meaning |
| --- | --- | --- | --- |
| Likely email provider | Pass | One mailbox group, no unknown routes | Published MX consistently points at that service; ownership remains inferred |
| Gateway detected | Check | One gateway group, no other routes | The named service receives mail first; downstream mailbox stays unknown |
| Multiple or partly recognized routes | Check | Several groups, or recognised plus unknown | No single mailbox host can be determined |
| Email provider unknown | Info | No hostname matches | Catalog has no answer; self-hosting is only one possibility |
### Missing and inconclusive attribution
| Finding | Severity | Evidence | Meaning |
| --- | --- | --- | --- |
| No email provider: null MX | Fail | Exact `0 .` route | The domain explicitly accepts no mail |
| No email provider: NXDOMAIN | Fail | Public DNS says the domain does not exist | There is no public inbound route |
| No MX provider identified | Check | Only address fallback or no route | Address records do not identify a mailbox platform |
| Provider unknown: lookup failed | Check | Resolver or alias failure | Retry; this is not evidence of absence |
| Provider unknown: MX uninterpretable | Check | Invalid or contradictory MX | Repair the DNS set before attribution |
### Catalog signatures
| Provider | Role | Accepted hostname shape |
| --- | --- | --- |
| Cloudflare Email Routing | Gateway | `route` plus digits under `mx.cloudflare.net` |
| Google Gmail | Mailbox | Gmail inbound names under `gmail-smtp-in.l.google.com` |
| Google Workspace | Mailbox | `aspmx` variants, `googlemail.com` variants or `smtp.google.com` |
| Microsoft 365 | Mailbox | Tenant under `mail.protection.outlook.com` or `mx.microsoft` |
| Zoho, Proton, Fastmail | Mailbox | Their bounded regional inbound patterns |
| Mimecast, Proofpoint, Barracuda | Gateway | Anchored gateway-specific suffixes with at least one tenant label |
## Guide
### Reproduce the evidence
Run `dig +short MX example.com` or `nslookup -type=mx example.com`. Compare the exact targets and preferences with the route table. A provider label should be explainable from those hostnames alone. If the commands show different answers, wait for the previous TTL and compare resolvers before editing DNS.
For a suspected deceptive name, compare the complete suffix. `tenant.mail.protection.outlook.com` can match Microsoft 365; `tenant.mail.protection.outlook.com.example.net` cannot.
### Resolve a gateway result
Obtain a message that was legitimately delivered to the domain and inspect its Received chain in the [Email Header Analyzer](https://tamperbox.dev/email-header-analyzer). Start at the trusted receiving side and trace later hops after the named gateway. Do not trust arbitrary forwarded headers or claim the next hostname is the contractual mailbox provider without administrative evidence.
Gateway administration, mail-flow connectors and billing records are stronger evidence than SPF. SPF normally describes outbound senders and does not answer who stores inbound mail.
### Review mixed routes
| Situation | Investigation | Safe action |
| --- | --- | --- |
| Old and new mailbox services coexist | Check migration plan, TTL and provider admin panels | Remove the retired set only after delivery is confirmed on the new service |
| Gateway plus mailbox MX coexist | Confirm whether direct MX is intentional fallback or bypass | Preserve filtering order; avoid exposing a bypass accidentally |
| Recognised routes plus one unknown backup | Identify the operator and test authorised failover | Leave the provider verdict mixed until the route is understood |
| Equal-preference providers | Treat them as peers | Do not assume the first displayed name receives all mail |
### Use Break it and Heal it
Pasting an email address or URL demonstrates cleanup; capitals and a final dot demonstrate canonicalisation. IP addresses, underscore owners and single labels demonstrate pre-request refusals. Heal it offers the domain inside a refused email address or URL. History records replacements, Revert walks backward and Reset input restores the value that started the sequence.
## Worked example
The live default is `tamperbox.dev`. On 21 October 2026 its lookup filled three MX rows and matched Cloudflare Email Routing. The correct conclusion was gateway detected with downstream mailbox unknown. In the same comparison, `github.com` and `microsoft.com` each presented one Microsoft 365 route and a simpler likely-provider answer.
The default was selected because it teaches the most important distinction in this tool: an inbound service is not always the mailbox host. The observed records are not frozen into the page. Every load uses the production path, so service names, priorities, TTLs and conclusions can change.
## For agents
Agents can run the live `email_provider` operation through Tamperbox MCP or analyse pasted MX records through the bounded capability operation. Always return the exact supporting hostnames, roles and preferences. Say “likely” for mailbox attribution and “gateway” for an edge service; never erase the limitation by shortening either to “uses provider X.”
Treat `mixed`, `unknown` and `inconclusive` as useful outcomes. Do not enrich them with unrelated web hosting, SPF or nameserver guesses unless the user explicitly asks for a broader investigation and the evidence is labelled separately. Never infer that an account exists.
## Limits
The observation comes from one public recursive resolver at one time. Private and split-horizon records are invisible, cached answers can be stale, and DNSSEC validation is outside this page. The tool does not resolve MX targets, trace SMTP, inspect tenant configuration or follow gateway delivery.
The catalog is finite. Custom vanity names, resellers and newly changed provider naming can remain unknown. A matched hostname can still be delegated through a partner or configured incorrectly. Results are not stored, but a live lookup necessarily sends the domain to the Tamperbox server and public resolver.
## References
- [RFC 5321](https://www.rfc-editor.org/rfc/rfc5321), Simple Mail Transfer Protocol, especially [section 5](https://www.rfc-editor.org/rfc/rfc5321#section-5) and [section 5.1](https://www.rfc-editor.org/rfc/rfc5321#section-5.1). Current Internet Standard; it defines routing rather than provider ownership.
- [RFC 7505](https://www.rfc-editor.org/rfc/rfc7505), A Null MX Resource Record for Domains That Accept No Mail, especially [section 3](https://www.rfc-editor.org/rfc/rfc7505#section-3). Current Proposed Standard.
---
# Exact-Length String Generator
How the Exact-Length String Generator builds strings of an exact size in characters, UTF-16 units or UTF-8 bytes, what the limit test set contains, how counterstrings work, and how to test a length limit with them.
Canonical: https://docs.tamperbox.dev/tools/string-length-generator/
Updated: 2026-10-25
Applies to: tamperbox.dev, October 2026
Prerequisites: The length limit to test, and if you know it, what the system counts
Outputs: The string with Copy and Download, its length in every count, or the limit test set with each value's size and reason
Limitations: Strings up to 1,000,000 units and limit sets up to 500,000 in the browser; nothing is sent anywhere
The tool page is [tamperbox.dev/string-length-generator](https://tamperbox.dev/string-length-generator). It opens on a string of exactly 255 ASCII characters.
Where processing happens: strings are built in your browser, and downloads are made on your device. No server request is made.
## What the tool answers
What exactly should I paste to test a length limit, and is the limit counted in characters, UTF-16 units or bytes?
## The three counts
| Count | Who counts this way | Emoji 😀 |
|---|---|---|
| Characters (code points) | Python `len()`, most database CHAR limits | 1 |
| UTF-16 units | JavaScript, Java and C# length, HTML `maxlength` | 2 |
| UTF-8 bytes | Byte limits on columns, file names, HTTP headers | 4 |
To find out what a field counts, generate the limit in each unit with the emoji fill: the one the field accepts exactly is the one it uses.
## Method
### One string
The string is built from whole characters of the chosen fill, never half a surrogate pair. When the next character would go past the length, a plain `a` (one unit in every count) closes the gap, so any length can be hit exactly in any unit.
### Limit test set
For a limit N: the empty value, one character, N - 1, N, N + 1, N - 2 characters with a space on each side, N spaces (up to 100,000) and 2N. Every value is exactly the size its name says, in the unit you chose. N must be accepted unchanged; N + 1 must be refused, not silently cut.
### Counterstring
A counterstring such as `*3*5*7*9*12*` writes each asterisk's position before it. Built from the end backwards, it is always exactly N characters. Paste it into a field, read the last number left, and you know how many characters the field kept. It is ASCII, so all counts agree.
## Break it presets
- **Emoji at the limit:** 255 characters of emoji: 510 UTF-16 units and 1,020 bytes.
- **Bytes, not characters:** 255 UTF-8 bytes of CJK, which is 85 characters.
- **JavaScript length:** 10 UTF-16 units of emoji, which is 5 characters.
- **Split accents:** letters with combining accents that look half as long as their code point count.
- **Limit test set** and **Counterstring** for 255 and 1,000.
## Limits
A string is limited to 1,000,000 units and a limit set to 500,000 in the browser. Only the first 20,000 UTF-16 units are displayed; Copy and Download take the whole string with no line break added.
## Finding reference
| Result at the limit | Severity | Source | What it means | Fix |
| --- | --- | --- | --- | --- |
| Maximum accepted unchanged | pass | | The limit is where it is documented | None |
| Maximum + 1 refused with a message | pass | | The limit is enforced | None |
| Maximum + 1 silently cut | fail | | Data loss without notice | Refuse over-long values, or show the cut |
| Maximum refused | fail | | Off-by-one in the check | Use "length > max", not "length >= max" |
| Fewer emoji than characters accepted | check | [HTML maxlength](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-maxlength) | The limit counts UTF-16 units or bytes | Count the same unit on client, server and database |
| Accepted in the form, error on save | fail | [RFC 3629](https://www.rfc-editor.org/rfc/rfc3629) | The database counts bytes, the form counts characters | Size the column in characters, or check bytes on input |
| Counterstring cut at an unexpected number | check | | Something truncates before the documented limit | Find the layer whose limit matches the number |
## Guide
### Find what a field counts
1. Set the documented limit and generate one string with the emoji fill in each unit.
2. Submit each. The one accepted exactly at the limit names the unit the field counts.
### Test a limit end to end
Make the limit set, submit every value, and check the stored value as well as the form message. Maximum must be stored unchanged; maximum + 1 must be refused.
### Find where a value is cut
Generate a long counterstring, submit it, and read the last number that survived in the stored value, a log or an export. That number is how many characters the slowest layer kept.
## Use it from an agent
The `string_generate` operation takes the length as its input text and `unit`, `fill` and `mode` (`one`, `set` or `counter`) options. Through the API, strings are limited to 1,000,000 units, limit sets to 100,000, and output to the API's response size limit. See the [MCP documentation](https://docs.tamperbox.dev/mcp/).
## References
- [RFC 3629: UTF-8](https://www.rfc-editor.org/rfc/rfc3629)
- [RFC 2781: UTF-16](https://www.rfc-editor.org/rfc/rfc2781)
- [HTML Standard: the maxlength attribute](https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#attr-fe-maxlength)
- [Unicode Standard Annex #29: Unicode Text Segmentation](https://www.unicode.org/reports/tr29/)
---
# Getting started
Pick a Tamperbox surface, run a small example, and keep the result's warnings and limits intact.
Canonical: https://docs.tamperbox.dev/getting-started/
Updated: 2026-10-19
Applies to: MCP 0.2.0
Prerequisites: Node.js 22 or later for local MCP use
Outputs: A working browser tool result or MCP client connection
Limitations: Live network evidence can change between requests and regions.
## Pick a surface
| Need | Use |
| --- | --- |
| Explore a tool visually | [tamperbox.dev](https://tamperbox.dev), with the method behind each finding under [Tool pages](https://docs.tamperbox.dev/tools/) |
| Call one documented endpoint | Tamperbox REST API |
| Let an agent discover and combine operations | Tamperbox MCP |
## Run the MCP server locally
The released package starts a stdio server and uses the production Tamperbox API by default.
```sh
npx -y tamperbox-mcp@0.2.0
```
Most clients need the command split into an executable and arguments. Start with `npx`, then pass `-y` and `tamperbox-mcp@0.2.0` as arguments.
## Try a safe first task
Ask the connected client:
> Show me the input and output schemas for `json_format`. Do not run it yet.
The agent should use `search`, return a focused schema fragment, and report `apiCalls: 0`. Next, give it synthetic JSON and ask it to format the value. That second task should use `execute`.
## Verify the boundary
`search` reads the bundled OpenAPI snapshot and makes no API request. `execute` can call allowlisted Tamperbox API routes. Inputs embedded in executed code are visible to the MCP host, the API for each request, and the model provider carrying the tool call.
Continue with [Connect a client](https://docs.tamperbox.dev/mcp/connect/) or [Code mode](https://docs.tamperbox.dev/mcp/code-mode/).
---
# HAR File Sanitizer
How the HAR File Sanitizer replaces credentials, cookies, keys and personal values in a HAR file with stable placeholders in the browser, what it detects and what it does not, and how to prepare a HAR file for a support ticket.
Canonical: https://docs.tamperbox.dev/tools/har-sanitizer/
Updated: 2026-10-25
Applies to: tamperbox.dev, October 2026
Prerequisites: A HAR file exported from a browser's developer tools (Network panel, Save all as HAR or Export HAR), up to 50 MiB
Outputs: A verdict, every replacement by request and place, the sanitized HAR to copy or download, and the request list with failures as a Markdown table
Limitations: Detection is by field name and known patterns; names, postal addresses and phone numbers in free text are not detected; binary bodies are not inspected; nothing is sent anywhere
The tool page is [tamperbox.dev/har-sanitizer](https://tamperbox.dev/har-sanitizer). It opens on a sample capture from a shop on the reserved `.example` domain: a sign-in that returns a token, an order list called with that token and a session cookie, and a checkout that fails with a card number in its body.
Where processing happens: the HAR file is read and cleaned in your browser and never uploaded, including files you open or drop. No server request is made.
## What the tool answers
Can I attach this HAR file to a ticket? A HAR file records every request and response the browser made, with headers, cookies and bodies. Session cookies and bearer tokens in it let anyone holding the file act as the user until they expire. The tool replaces what it can find, tells you exactly what and where, and lists the requests so you can point support at the ones that failed.
## Inputs
### Pasted text
Paste the HAR JSON into the box. Text over one million characters is kept out of the box so the page stays responsive; the box says what is loaded.
### Files
Drop a `.har` or `.json` file anywhere on the page, or use Open file. Files up to 50 MiB are read in the browser.
### Options
- **Every cookie value:** on by default. Cookie names stay, so you can see which cookies were sent.
- **Email addresses** and **Card numbers:** on by default.
- **IP addresses:** off by default, because they are usually needed to diagnose network problems.
- **JSON Web Tokens:** keep claims and remove the signature (default), or replace the whole token.
- **More field names to replace:** names your application uses for private values, such as `tenant` or `account_id`.
## Reading the result
### Verdict and chips
How many distinct values were replaced, in how many requests, with a chip per kind, a chip for failed requests and a chip for binary bodies that were not inspected.
### What was replaced
One row per place: the request number, the placeholder and where it was (`request header authorization`, `response body > access_token`, `request query`). Original values are never shown.
### Sanitized HAR
The cleaned file, with Copy and Download. The download is named after your file with `.sanitized.har`.
### Requests
Every request with method, status, sanitized URL and time. Failed means status 0 (blocked, cancelled or a network error) or 400 and above. Copy failures as Markdown gives a table ready for a ticket.
## Method
### 1. Reading
The text is parsed as JSON and must hold `log.entries` as a list. A leading byte order mark is ignored.
### 2. By name
Names are compared after lowercasing and removing everything but letters and digits. A name is a credential when it contains authorization, token, secret, password, passphrase, apikey, session, csrf, xsrf, signature, credential, privatekey, codeverifier, samlresponse, samlrequest, otp or cookie, or is exactly key, sig, sid, auth, pass, pwd, pin, ssn, cvv or cvc. `code` counts only in a URL query or form, where it is an OAuth authorization code. Names that end in type, expires, expiry, endpoint, url, uri, hint, length or format are treated as descriptive metadata, except names containing password, passwd or secret, which are always redacted.
This applies to headers, query parameters, form fields, `postData.params` and keys at any depth of a JSON body.
### 3. Headers and cookies
Authorization and Proxy-Authorization keep their scheme. Cookie and Set-Cookie keep each name and attribute and lose the value. The `cookies` lists of requests and responses are treated the same way. Location, Referer, Origin and Content-Location are cleaned as URLs.
### 4. URLs
User and password in a URL are replaced, query parameters and OAuth or SPA fragment parameters are cleaned by name, including repeated parameters and malformed percent escapes, and the whole URL is searched for the patterns below.
### 5. Patterns in any text
Supported header, query, body, comment and custom metadata text is searched for: private key blocks; AWS access key IDs; Google API keys; GitHub tokens; Stripe secret and restricted keys; Slack tokens; JSON Web Tokens; credentials after Bearer, Basic or Token; email addresses; 13 to 19 digit numbers that pass the Luhn check; and, when chosen, IPv4 addresses outside the documentation and loopback ranges.
### 6. Placeholders
Each distinct value gets one placeholder for the whole file: `[TOKEN_n]`, `[PASSWORD_n]`, `[COOKIE_n]`, `[CARD_n]`, a named key such as `[AWS_KEY_n]`, `email-n@redacted.invalid`, and for IP addresses a documentation address from the three documentation ranges (192.0.2.0/24, 198.51.100.0/24 and 203.0.113.0/24), followed by `[IP_n]` after 762 distinct addresses. A JSON Web Token keeps its header, has its claims cleaned the same way (so an email claim is replaced), and gets `redacted-signature` as its signature. Input that looks like a placeholder is still untrusted. Running the tool again may renumber placeholders; the mapping is stable within each run.
### 7. Bodies
JSON bodies are parsed, cleaned and written with the indentation they had. URL-encoded and multipart form bodies are cleaned field by field. Sensitive JSON names also apply to values inside arrays and nested objects. Malformed JSON or multipart bodies are replaced as a whole, rather than retained without inspection. Data deeper than 100 levels is refused. Other text is searched for patterns. Base64 bodies with a text, JSON, XML, JavaScript or form type are decoded, cleaned and encoded again; other Base64 bodies are binary and are kept and counted.
## Finding reference
| Finding | Severity | Source | What it means | Fix |
| --- | --- | --- | --- | --- |
| N values replaced | pass | [W3C HAR](https://w3c.github.io/web-performance/specs/HAR/Overview.html) | Credentials and personal values were found and replaced | Read the file before sharing |
| Nothing to replace found | info | | No credential names or known patterns matched | Read the file anyway; free text is not checked |
| N failed requests | check | [RFC 9110 15](https://www.rfc-editor.org/rfc/rfc9110#section-15) | Status 0 or 400 and above | Copy failures as Markdown for the ticket |
| Binary bodies not inspected | info | | Images, fonts and archives were kept as they are | Remove the entries if a binary could hold private data |
| Not a HAR file | fail | [W3C HAR](https://w3c.github.io/web-performance/specs/HAR/Overview.html) | No `log.entries` list | Export again from the Network panel; Heal it can wrap a bare `entries` list |
## Guide
### Prepare a HAR file for a support ticket
1. Record: open the developer tools, Network panel, tick Preserve log, reproduce the problem, then Save all as HAR (Chrome, Edge) or Export HAR (Firefox, Safari).
2. Drop the file here. Read What was replaced, and add your own field names under More field names if something private was missed.
3. Download the sanitized file and attach it. Paste Copy failures as Markdown into the ticket.
4. Sign out of the session you recorded, so the original cookies stop working even if the original file leaks.
### Share tokens without making them usable
Keep the default JWT setting. Support can read expiry, issuer, audience and scopes, but the token has no valid signature and cannot be replayed.
### Break it cases
| Case | What it adds | What it shows |
| --- | --- | --- |
| Token in a URL | A request with `access_token` in the query | A credential replaced in the URL |
| API key header | A request with an X-API-Key header | A credential replaced in a header |
| Card in a body | A JSON body with a card number | A card number replaced |
| Private key in a response | A text response holding a private key block | A private key replaced |
| Blocked request | A request with status 0 | A failed request in the list |
| Not a HAR file | A bare `entries` object | The error and the Heal it repair |
## Use it from an agent
The `har_sanitize` operation takes the HAR JSON and returns the sanitized HAR, with a warning that counts the replacements by kind. Options match the page: `cookies`, `emails`, `cards`, `ips` and `jwt`. Through the API the input goes to our server and is limited to the API's request size; use the page for files you do not want to send anywhere. See the [MCP documentation](https://docs.tamperbox.dev/mcp/).
## Limits
- Names, postal addresses, phone numbers and other personal details in free text are not detected.
- Binary bodies are not inspected.
- Changed bodies are written out again, so Content-Length headers may not match.
- Files are limited to 50 MiB in the browser.
## References
- [W3C: HTTP Archive (HAR) format](https://w3c.github.io/web-performance/specs/HAR/Overview.html)
- [RFC 7519: JSON Web Token](https://www.rfc-editor.org/rfc/rfc7519)
- [RFC 6265: HTTP State Management Mechanism](https://www.rfc-editor.org/rfc/rfc6265)
- [OWASP: Logging Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html)
---
# Hidden Character Checker
How the Hidden Character Checker finds zero width, direction, look-alike, control and decomposed characters in text, counts its length four ways, and cleans it, with every rule behind each finding and how to confirm a result outside Tamperbox.
Canonical: https://docs.tamperbox.dev/tools/hidden-characters/
Updated: 2026-10-24
Applies to: tamperbox.dev, October 2026
Prerequisites: Text to check, pasted, typed or opened from a file of up to 200,000 characters
Outputs: A verdict naming what was found, one finding per kind of character with its code point, name and positions, the text with hidden characters shown as tags, a clean copy, the four lengths and the four Unicode forms
Limitations: A finding is a character present in the text, not proof of an attack; look-alike letters are only found inside Latin words or in words made of a small set of well-known look-alikes; nothing is sent anywhere
The tool page is [tamperbox.dev/hidden-characters](https://tamperbox.dev/hidden-characters). It opens on the word `admin` with a zero width space (U+200B) inside it: the name a person typed, a username that looks identical to the real one and fails to log in. This page is the long version: what each finding means, how the counts differ, and how to check a result without this tool.
Where processing happens: your text is read and cleaned in your browser. Nothing is uploaded, including text from a file you open or drop, and no server request is made for any input.
## What the tool answers
Two strings can look the same and not be the same. The tool answers four questions about a piece of text:
1. **Is anything in it that you cannot see?** Zero width, direction, filler, tag and control characters.
2. **Is anything in it that only looks like something else?** A no-break space for a space, a Cyrillic letter for a Latin one, a fullwidth form for an ASCII one.
3. **How long is it?** The same text has several lengths, and the one that matters depends on who is counting.
4. **Is it written in the form the receiving system expects?** An accent can be one character or two.
It does not decide whether a character is harmful. A zero width non-joiner is correct in Persian and a joiner is correct inside an emoji.
## Inputs
### Text
Type or paste into the box. Paste anywhere on the page outside a field also replaces the text. Input is limited to 200,000 characters.
### Files
Drop a `.txt`, `.csv`, `.json`, `.md`, `.log`, `.xml` or `.html` file anywhere on the page, or use Open file. Files over 512 KiB are refused with a message. The file is read in the browser as UTF-8 and never uploaded.
## Reading the result
### Verdict and chips
The verdict names how many hidden or misleading characters were found and describes the first by name and code point. The first chip is the size in characters and bytes. The other chips count each kind found and jump to the finding that explains it.
### What was found
One finding per kind of character, with the code point, its Unicode name, what it does, and where it is by line and column. The status follows the risk: Fail for characters that change what is displayed or that make a blank look filled (direction overrides, fillers, tag characters, NUL, look-alike letters inside a word, broken surrogates), Check for characters that behave differently from what they look like (zero width characters, look-alike spaces, control characters, text not in composed form), and Info for notes.
### Your text with hidden characters shown
The same text with each hidden character written as a tag such as ⟦U+200B⟧. Shown only when something was found.
### Clean text
A copy with the repairs you tick applied. Only repairs that would change this text are listed. Use as input puts the clean text back in the box.
### Length, by every count
| Count | What counts it |
|---|---|
| Characters | Unicode code points. Python `len()`, and most database CHAR limits |
| UTF-16 units | JavaScript, Java and C# string length. An emoji counts as 2 |
| UTF-8 bytes | Bytes on the wire, a VARCHAR byte limit, file names, HTTP headers |
| Visible characters | What a person counts. An accent, or a family emoji built from seven code points, counts once |
### Unicode forms
The text in NFC, NFD, NFKC and NFKD with the length of each, and whether it is the same as the input.
### Heal it and Break it
Heal it offers to clean the text with the current choices, or to restore the input before a break. Break it adds a hidden character to your own text where it can (a zero width space inside the first word, a Cyrillic letter in place of a Latin one, a byte order mark at the start) and loads a short sample when your text cannot carry the case. Every Break it can be undone from Heal it.
## Method
### 1. Reading
The text is split into code points, so an emoji is one item and a lone surrogate is one item. Each code point is classified. Neighbouring characters are used only to excuse the joiners that emoji and some scripts need.
### 2. Invisible and misleading characters
| Kind | Code points | Why it matters |
|---|---|---|
| Zero width | U+200B, U+2060, U+2061 to U+2064, U+180E, U+FEFF inside text, U+200D outside emoji, U+200C outside non-Latin letters | Takes no space, breaks equality, search and length checks |
| Byte order mark | U+FEFF at the start | Editors hide it; parsers and CSV importers see an extra character |
| Direction | U+202A to U+202E, U+2066 to U+2069 (Fail); U+200E, U+200F, U+061C (Check) | Changes the display order, so what is shown is not what is stored |
| Filler | U+115F, U+1160, U+3164, U+FFA0, U+2800, U+034F | Blank to the eye, not whitespace, so "not empty" checks accept it |
| Tag | U+E0000 to U+E007F | Invisible and able to carry hidden text |
| Control | C0 and C1 controls except tab, line feed and carriage return | NUL ends a string in C; others are acted on by terminals |
| Look-alike space | U+00A0, U+1680, U+2000 to U+200A, U+202F, U+205F, U+3000, U+2028, U+2029 | Treated differently by trim(), split and equality |
| Lone surrogate | U+D800 to U+DFFF alone | Not valid Unicode; encoders replace it or fail |
| Replacement | U+FFFD | Data was already lost before it reached the tool |
### 3. Look-alike letters
A word is a run of letters and digits. If it holds Latin letters and also Cyrillic, Greek, Armenian or Cherokee letters from a list of well-known look-alikes, each of those letters is reported with the Latin letter it resembles. A word with no Latin letter made only of look-alikes is reported as a low note, because that is how ordinary Russian or Greek words are written. The list is a short set of the common cases, not the full Unicode confusables data in [UTS #39](https://www.unicode.org/reports/tr39/).
### 4. Decomposed text and the other forms
The text is compared with its NFC form ([UAX #15](https://www.unicode.org/reports/tr15/)). If they differ, a finding says how the code point count changes. A text where NFKC differs from NFC contains compatibility characters such as fullwidth letters, ligatures or superscripts.
### 5. Lengths
Code points are counted with the language's own iteration, UTF-16 units with the string length, UTF-8 bytes by the size of each code point (a lone surrogate is written as U+FFFD, three bytes), and visible characters with the browser's grapheme segmentation ([UAX #29](https://www.unicode.org/reports/tr29/)). Line endings are counted as CRLF, LF and CR separately, and more than one kind is flagged as mixed.
### 6. Cleaning
Each repair is optional. Invisible characters removes zero width, direction, filler, tag, control, soft hyphen and byte order mark characters, and keeps the joiners that emoji need. Spaces replaces look-alike spaces with a normal space and U+2028 and U+2029 with a line feed. Look-alike letters replaces only letters inside a Latin word. Composed form rewrites the text in NFC. Edges trims leading and trailing whitespace. The result states what each repair changed.
## Limits and interpretation
- A finding is evidence of a character, not of intent.
- Look-alike detection covers a small set of the commonest cases and only inside Latin words, or whole words of look-alikes.
- Text from files is read as UTF-8. Text already damaged into U+FFFD cannot be repaired here.
- A character your font cannot draw is not the same as a hidden character: a box or question mark is a missing glyph, which this tool does not detect.
- Input is limited to 200,000 characters and files to 512 KiB.
## Finding reference
| Finding | Severity | Source | What it means | Fix |
| --- | --- | --- | --- | --- |
| Zero width character | check | [UAX #9](https://www.unicode.org/reports/tr9/) | Invisible, breaks equality, search and length checks | Remove it (Clean text) |
| Byte order mark at the start | check | [Unicode FAQ: BOM](https://www.unicode.org/faq/utf_bom.html#bom1) | An editor or export added it; importers see an extra character | Remove it, or save as UTF-8 without BOM |
| Direction override or isolate | fail | [UAX #9](https://www.unicode.org/reports/tr9/) | What is shown is not what is stored | Remove it unless the text needs it |
| Direction mark | check | [UAX #9](https://www.unicode.org/reports/tr9/) | Invisible, changes the layout of punctuation | Remove it from identifiers |
| Blank filler | fail | [UTS #39](https://www.unicode.org/reports/tr39/) | Looks empty but passes a not-empty check | Remove it and treat the value as empty |
| Tag character | fail | [UTS #39](https://www.unicode.org/reports/tr39/) | Can carry hidden text | Remove it |
| Control character | check (NUL: fail) | [RFC 20](https://www.rfc-editor.org/rfc/rfc20) | Acted on by terminals and parsers | Remove it |
| Look-alike space | check | [UAX #44](https://www.unicode.org/reports/tr44/) | trim(), split and equality treat it differently | Replace with a normal space |
| Look-alike letter in a Latin word | fail | [UTS #39](https://www.unicode.org/reports/tr39/) | Reads as a Latin letter and compares unequal | Replace with the Latin letter |
| Word made only of look-alikes | info | [UTS #39](https://www.unicode.org/reports/tr39/) | Normal in Cyrillic or Greek text; suspicious in a Latin context | Check where the text came from |
| Not in composed form (NFC) | check | [UAX #15](https://www.unicode.org/reports/tr15/) | Equal-looking strings compare unequal | Normalize to NFC before storing or comparing |
| Lone surrogate | fail | [Unicode FAQ: UTF-16](https://www.unicode.org/faq/utf_bom.html#utf16-7) | Not valid Unicode | Fix the code that split the pair |
| Replacement character | check | [Unicode FAQ](https://www.unicode.org/faq/utf_bom.html) | Data was lost upstream | Get the text again, read as UTF-8 |
| Stacked combining marks | info | [UAX #29](https://www.unicode.org/reports/tr29/) | Draws outside its line | Limit marks per character if layout matters |
| Leading or trailing whitespace | info | | Easy to miss | Trim where the value is an identifier |
## Guide
### Find why two equal-looking values do not match
1. Paste each value in turn and compare the findings and the four lengths.
2. If one has a finding the other lacks, that character is the difference. If only the Unicode forms differ, normalize both to NFC.
3. Fix it where the value enters the system, not only where it is compared.
### Clean pasted identifiers before storing them
Remove invisible characters, replace look-alike spaces and normalize to NFC for user names, email addresses, file names and keys. Keep joiners in prose, emoji and scripts that need them.
### Test that your system rejects or normalizes hidden characters
Use Break it on a real value from your form: each case adds one hidden character to your text. Submit it and check that the system refuses it, cleans it, or at least stores it unchanged and shows the same length you see here.
### Break it cases
| Case | What it loads | What it shows |
| --- | --- | --- |
| Zero width space | U+200B inside your first word | Zero width finding |
| Cyrillic look-alike | One of your Latin letters replaced by a Cyrillic one | Look-alike letter finding |
| Disguised file name | U+202E near the end of your text | Direction override finding |
| No-break space | Your first space as U+00A0 | Look-alike space finding |
| Byte order mark | U+FEFF at the start | Byte order mark finding |
| NUL byte | U+0000 inside your first word | Control character finding |
| Split accent | Your text in NFD, or a sample | Not in composed form |
| Trailing spaces | Two spaces at the end | Trailing whitespace |
| Blank name | U+3164 alone | Blank filler |
| Hidden tag text | Two tag characters after your text | Tag character |
| Emoji family | One family emoji | No findings, and four different lengths |
## Use it from an agent
The same check is the `text_inspect` operation of the Tamperbox API and MCP server. It takes text, returns the counts, the findings with code point, name, line and column, and, with `clean` set to true, the cleaned text. Call it with the same input and the same limits as the page. See the [MCP documentation](https://docs.tamperbox.dev/mcp/).
## Confirm a result outside Tamperbox
- Python: `[hex(ord(c)) for c in text]` lists every code point; `len(text)` and `len(text.encode())` give characters and bytes; `unicodedata.normalize("NFC", text) == text` tests the form.
- JavaScript: `[...text].map(c => c.codePointAt(0).toString(16))` lists code points; `text.length` is the UTF-16 count; `new TextEncoder().encode(text).length` is the byte count; `text.normalize("NFC") === text` tests the form.
- A hex dump of the file (`xxd file | head`) shows a byte order mark as `ef bb bf` at the start.
## References
- [Unicode Standard Annex #15: Unicode Normalization Forms](https://www.unicode.org/reports/tr15/)
- [Unicode Standard Annex #9: Unicode Bidirectional Algorithm](https://www.unicode.org/reports/tr9/)
- [Unicode Technical Standard #39: Unicode Security Mechanisms](https://www.unicode.org/reports/tr39/)
- [Unicode Standard Annex #29: Unicode Text Segmentation](https://www.unicode.org/reports/tr29/)
- [CVE-2021-42574: bidirectional text in source code](https://nvd.nist.gov/vuln/detail/CVE-2021-42574)
---
# HTTP/3 & Compression Checker
What the transport checker requests, how it tests HTTP/3, measures each compression encoding and judges HSTS against the preload list's rules, what each finding means, and how to fix and verify each one.
Canonical: https://docs.tamperbox.dev/tools/http3-checker/
Updated: 2026-10-24
Applies to: tamperbox.dev, October 2026
Prerequisites: A public HTTPS address on port 443 (an http address is upgraded by its own redirect), or response headers from curl -I to paste
Outputs: A verdict with one clause per lane, three chips, the HTTP/3, compression and HSTS preload lanes with their evidence, findings worst first, and the response headers of the Brotli request
Limitations: One probe location at one moment; a failed QUIC connection describes this probe, not every visitor; encoded sizes exclude headers and framing; a pasted header shows an offer, not a working connection
The tool page is [tamperbox.dev/http3-checker](https://tamperbox.dev/http3-checker). It opens by checking `tamperbox.dev` live, answers in one sentence with one clause per lane, and shows the evidence under it. This page is the long version: what our server requests, every rule the page applies, what each finding means, and how to fix and verify each one.
Where processing happens: an address is sent to the Tamperbox server, which requests the page once per compression it can ask for, attempts one HTTP/3-only connection where the probe supports it, requests the http:// form of the address and the HTTPS main domain for HSTS, and asks hstspreload.org about the main domain. Pasted response headers are read in your browser and never uploaded. History and tool-to-tool transfers remain in the browser; see Browser transfers below.
## What the tool answers
Three questions, one lane each, judged separately:
1. **Does the site offer HTTP/3, and does it work?** The Alt-Svc offer, and where the direct probe is available, whether a QUIC-only connection to the final HTTPS address succeeds.
2. **Does it compress its pages, and how well?** What each of `br`, `zstd` and `gzip` gets back, the encoded size of each against the uncompressed body, and whether the response declares `Vary: Accept-Encoding`.
3. **Is it ready for HSTS preload, or already preloaded?** The HSTS header on the main domain against the preload list's four rules, and the domain's status on hstspreload.org.
The verdict takes the worst of the three lanes. The three chips, one per lane, scroll to the lanes. Findings under the verdict list every rule that fired, worst first.
## Inputs
### A website address
`example.com`, `www.example.com/page` or `https://example.com/`. The check runs when you stop typing, or on Enter. An address without a scheme starts at `https://`. The parser refuses, with a message in the field, an address with spaces, a scheme other than `http` or `https`, an IP address, a host without a top-level domain, a port other than 80 or 443, and credentials in the address; the last two offer a repair. The fragment is dropped. Our server applies the same rules to the address and to every redirect target, and requires the host to resolve only to public addresses.
### Pasted response headers
Paste the output of:
```sh
curl -sI https://example.com/
```
A paste that starts with a status line or a `Name: value` line is read in the browser. When several responses are pasted, the last one is used. The page reads `Alt-Svc`, `Content-Encoding`, `Vary` and `Strict-Transport-Security` from it. Nothing is requested, so sizes, the other encodings, the QUIC test, the http:// redirect and the preload list stay unknown, and the lanes say so.
### Files
A `.txt` file dropped on the field, or chosen with Open file, is read in the browser like a paste.
### What the page does to pasted text
Pasted text goes through the shared cleaner before anything runs. An address the parser accepts is kept whole; otherwise a quoted or bracketed link loses its quotes and trailing punctuation, and an email address or `mailto:` link becomes its domain. Text that fits nothing stays exactly as pasted. Typing is never rewritten; leaving the field or pressing Enter tidies what was typed the same way. Break it and Heal it loads are never cleaned.
## Reading the result
### Verdict and chips
The sentence has one clause per lane: "HTTP/3 works", "HTTP/3 offered", "HTTP/3 connection failed" or "No HTTP/3 offer"; "Brotli on", "gzip on", "pages sent uncompressed" or, for a paste, the Content-Encoding found; "preloaded HSTS", "HSTS preload pending", "ready for HSTS preload", "not eligible for HSTS preload" or "no HSTS". The detail names the final address and its status, or says the result came from pasted headers. The chips repeat the three lane states: the HTTP/3 state, "n of 3 encodings" or "Not compressed", and "Preload ready", "Preload not ready" or "Preloaded".
### Protocol, compression, preload
Three lanes side by side. The HTTP/3 lane shows the state in one word (Works, Advertised, Failing, Not offered), the Alt-Svc line as sent, and for how long browsers remember the offer. The compression lane lists what each Accept-Encoding got back with its encoded size, and the identity body size. The HSTS preload lane shows the preload status, the header as sent, the four submission checks with their state, a note on the list's answer, and, when the header is not eligible, the line to send with a Copy button.
### Findings
Every rule that fired, worst first; two or more passes fold into one line.
### Response headers
Behind a disclosure, the full headers of the Brotli request (or the pasted headers), with the four that matter labelled: Alt-Svc, Content-Encoding, Vary and Strict-Transport-Security.
### History
Every Break it and Heal it change, newest first, with what it replaced. Revert undoes the newest change; Reset input returns to what you entered.
### Take it further
For a live result with a main domain, the [Redirect Checker](https://docs.tamperbox.dev/tools/redirect-checker/) link receives its `http://` root address to trace the redirect required by preload rules. A separate button opens the [Unix Timestamp Converter](https://docs.tamperbox.dev/tools/unix-timestamp/) when the result has a finite, positive HSTS max-age. It adds that duration to the current time when the result is rendered. This illustrates the duration; it is not the expiry of a particular browser’s stored HSTS policy.
The timestamp button uses a one-time message to a new same-origin tab, with no input in the URL or browser storage. Keep the source tab open until the transfer completes. A blocked popup or a transfer taking over 30 seconds produces a message; copy the value if needed. The Redirect Checker link uses the browser transfer described below.
### Browser transfers
Explicit tool links temporarily place the transferred input in browser `sessionStorage`. The URL fragment carries a random token, not the input. The receiving page accepts that token once, for its destination path and for less than one minute, then removes it from the address. A same-origin browser channel can complete a transfer into another tab. Ordinary documentation links do not transfer input. When browser storage is unavailable, navigation still works but the input may need to be pasted again.
## Method
The rules, in the order they are applied. The analysis is one function, `assessTransport` in the shared HTTP checks library, that the page, the server and the MCP operation run on the same evidence.
### What our server requests
With the direct probe available, our server runs one GET per encoding, `br`, `zstd`, `gzip` and `identity`, two at a time, each with a 4 second connect timeout and an 8 second request limit, within 22 seconds for the whole check. Automatic redirects and content decoding are off; the probe follows up to five redirects itself, revalidating each target, and refuses a loop. Each body is counted as received, without decoding, up to 2 MiB. Then one HEAD request is sent over HTTP/3 only to the final HTTPS address, with no fallback. Separately, the server requests `https://` on the registrable domain (the main domain: `example.com` for `www.example.com`) for its HSTS header, requests the `http://` form of the final address to see its redirect, and asks `https://hstspreload.org/api/v2/status` about the registrable domain.
Without the direct probe, the server fetches the page once per encoding through its ordinary client, which decodes bodies, so only decoded sizes are known and HTTP/3 is not tested. Headers shown in full are those of the `br` request.
### HTTP/3
- The Alt-Svc header is parsed per [RFC 7838 Section 3](https://www.rfc-editor.org/rfc/rfc7838#section-3): a list of `protocol="authority"` entries, each with an optional `ma` parameter. An entry for `h3` or a draft such as `h3-29` is an HTTP/3 offer. Per [Section 3.1](https://www.rfc-editor.org/rfc/rfc7838#section-3.1), an entry without `ma` is remembered for 86400 seconds, and the value `clear` means no alternatives.
- With a direct test, **HTTP/3 works** (PASS) when the response really arrived over HTTP version 3, with the time taken; **HTTP/3 connection failed** (FAIL) when the attempt errored or the answer came back on another version. The test runs whether or not Alt-Svc offers h3, so a server that answers over QUIC shows as working even without an offer.
- Without a direct test, **HTTP/3 advertised** (PASS) when Alt-Svc offers h3, with the authority and the duration; **No HTTP/3 advertisement** (CHECK) otherwise, which does not prove the server lacks HTTP/3.
- **Alt-Svc remembered for n minutes** (INFO) when `ma` is under 86400: browsers forget the offer quickly and start each visit on HTTP/2.
- **No HTTP/2** (CHECK) when the first successful transfer used HTTP/1.1.
The HTTP/3 lane is PASS for works or advertised, FAIL for a failed connection, CHECK otherwise.
### Compression
An encoding counts as applied when the response to that Accept-Encoding carries a Content-Encoding other than `identity`, following [RFC 9110 Section 8.4](https://www.rfc-editor.org/rfc/rfc9110#section-8.4) and [Section 12.5.3](https://www.rfc-editor.org/rfc/rfc9110#section-12.5.3). Savings compare each encoded body with the identity body of a separate request.
| Condition | Finding | Status |
| --- | --- | --- |
| No request got an encoded response | Not compressed, with the uncompressed size when known | FAIL |
| Something applied, but the `br` request did not get `br` back | No Brotli, naming what applied | CHECK |
| `br` applied, sizes known | Brotli saves n% | PASS |
| `br` applied, sizes unknown | Brotli compression observed | PASS |
| `zstd` applied | Zstandard offered | PASS |
| A compressed response without `Vary: Accept-Encoding` or `Vary: *` | No Vary: Accept-Encoding | CHECK |
| Pasted headers with Content-Encoding | Compressed with (encoding) | PASS |
| Pasted headers without Content-Encoding | No Content-Encoding in the pasted headers | INFO |
The Vary rule follows [RFC 9110 Section 12.5.5](https://www.rfc-editor.org/rfc/rfc9110#section-12.5.5): "An origin server SHOULD send a Vary header field when its algorithm for selecting a representation varies based on aspects of the request message other than the method and target URI". The finding says a shared cache could reuse an incompatible encoding; it is not evidence that one did. The compression lane is PASS with Brotli, CHECK with another encoding only, FAIL with none; for a paste it is PASS with an encoding and INFO without.
### HSTS and the preload list
The HSTS header is parsed per [RFC 6797 Section 6.1](https://www.rfc-editor.org/rfc/rfc6797#section-6.1): `max-age` (required, whole seconds), `includeSubDomains` and `preload`, with unknown directives noted. The header used is the main domain's; if that request failed or carried no header, the final page's header is used. The four submission checks follow the preload list's own rules, which are Chromium's policy ([hstspreload.org submission requirements](https://hstspreload.org/#submission-requirements)), not an RFC:
| Check | Rule |
| --- | --- |
| HTTP redirects to HTTPS on the same host | The `http://` form of the final address answers with a Location on `https://` and the same host name; not checked when that request fails |
| max-age at least one year (31536000) | `max-age` of 31536000 or more |
| includeSubDomains | The directive is present |
| preload directive | The directive is present |
The header is eligible when no check is false. The list's answer decides the finding: **(domain) is preloaded** or **Preloaded via (parent)** (PASS) when hstspreload.org reports the domain or a parent entry, including a top-level domain such as `dev`; **Preload submission pending** (INFO); **Ready to preload** or **Ready to preload, not listed** (INFO) when the header is eligible; **No HSTS header** (CHECK); **Not eligible for preload** (CHECK) with the missing checks named. If the list service does not answer, the status is unavailable, never absent. Inherited coverage protects the domain even when its own header fails the individual checks, which are still shown for reference. The lane is PASS when preloaded or eligible, INFO when pending, CHECK otherwise.
### Heal it
For pasted headers only: **Preview an HTTP/3 Alt-Svc offer** adds `alt-svc: h3=":443"; ma=86400` when none is present, and **Make HSTS preload-ready** replaces the HSTS header with `max-age=63072000; includeSubDomains; preload`. Both edit the pasted example; publishing Alt-Svc still needs a working HTTP/3 service, and preload settings need HTTPS on every subdomain.
## Standards and conformance
The checker observes what one set of requests from our server received and judges it with the rules above. It is not a browser: it does not switch to an alternative service after seeing Alt-Svc, does not decode bodies in the direct measurement, and does not keep HSTS state.
### RFC 7838: HTTP Alternative Services
[RFC 7838](https://www.rfc-editor.org/rfc/rfc7838) is a Proposed Standard (April 2016). [Section 3](https://www.rfc-editor.org/rfc/rfc7838#section-3) gives the header syntax and [Section 3.1](https://www.rfc-editor.org/rfc/rfc7838#section-3.1) the `ma` parameter and its 24 hour default: "When this parameter is omitted, the default is 24 hours". The tool parses the header, reports the h3 offer, its authority and its lifetime, and notes a lifetime under a day. It does not connect to the advertised authority; the direct test goes to the final HTTPS origin on port 443 only.
### RFC 9114 and RFC 9000: HTTP/3 over QUIC
[RFC 9114](https://www.rfc-editor.org/rfc/rfc9114) (HTTP/3) and [RFC 9000](https://www.rfc-editor.org/rfc/rfc9000) (QUIC) are Proposed Standards. [RFC 9114 Section 3.1](https://www.rfc-editor.org/rfc/rfc9114#section-3.1) describes discovery through Alt-Svc with the `h3` identifier; the direct test is a single HEAD request that succeeds only when the response was carried on HTTP version 3. The tool does not test 0-RTT, connection migration or any QUIC version detail.
### RFC 9113: HTTP/2
[RFC 9113](https://www.rfc-editor.org/rfc/rfc9113) is a Proposed Standard and obsoletes [RFC 7540](https://www.rfc-editor.org/rfc/rfc7540). HTTP/2 is inferred from the version of the first successful transfer; the tool does not inspect ALPN directly and the TLS version is not reported here.
### RFC 9110: content coding and Vary
[RFC 9110](https://www.rfc-editor.org/rfc/rfc9110) is an Internet Standard (STD 97). [Section 8.4](https://www.rfc-editor.org/rfc/rfc9110#section-8.4) defines Content-Encoding and the coding names registered at IANA, [Section 12.5.3](https://www.rfc-editor.org/rfc/rfc9110#section-12.5.3) Accept-Encoding, and [Section 12.5.5](https://www.rfc-editor.org/rfc/rfc9110#section-12.5.5) Vary. The codings themselves are specified by [RFC 7932](https://www.rfc-editor.org/rfc/rfc7932) (Brotli), [RFC 8878](https://www.rfc-editor.org/rfc/rfc8878) (Zstandard, which obsoletes [RFC 8478](https://www.rfc-editor.org/rfc/rfc8478)) and [RFC 1952](https://www.rfc-editor.org/rfc/rfc1952) (gzip); the tool reads the coding names and sizes and never decodes.
### RFC 6797: HSTS
[RFC 6797](https://www.rfc-editor.org/rfc/rfc6797) is a Proposed Standard (November 2012). [Section 6.1](https://www.rfc-editor.org/rfc/rfc6797#section-6.1) defines the header and its directives; [Section 6.1.1](https://www.rfc-editor.org/rfc/rfc6797#section-6.1.1) requires `max-age`: "The REQUIRED "max-age" directive specifies the number of seconds". The `preload` directive is not part of RFC 6797; it is the preload list's own token. The tool parses the header as the RFC describes and judges it by the list's rules, which are stricter than the RFC (the RFC sets no minimum lifetime).
### Where the tool stops
- Only the final HTTPS origin on port 443 is tested for QUIC; alternative authorities and ports in Alt-Svc are not probed.
- Sizes are encoded response-body bytes from separate requests; dynamic pages can differ between them.
- The preload list answer is hstspreload.org's; browsers ship their own copy with a delay.
- Inherited preload coverage is reported as a pass even when the domain's own header is not eligible.
## Finding reference
### HTTP/3 and HTTP/2
| Finding | Status | Rule | What it means | Fix |
| --- | --- | --- | --- | --- |
| HTTP/3 works | PASS | Direct HEAD over QUIC answered on version 3 | Browsers switch to QUIC after the first response advertises it | Nothing; keep Alt-Svc published |
| HTTP/3 advertised | PASS | Alt-Svc offers h3; no direct test | The offer is seen; whether QUIC connects is unknown | Nothing on the page; test from the command line |
| HTTP/3 connection failed | FAIL | Direct test errored or fell to another version | Browsers try QUIC, wait, and fall back to HTTP/2 | Open UDP 443 at the firewall or security group; enable HTTP/3 at the CDN; build the server with QUIC |
| No HTTP/3 advertisement | CHECK | No h3 in Alt-Svc, no direct result | Not proof of absence | Enable HTTP/3 and publish Alt-Svc |
| Alt-Svc remembered for n minutes | INFO | `ma` under 86400 | Each visit starts on HTTP/2 | Set `ma=86400` or longer |
| No HTTP/2 | CHECK | First transfer on HTTP/1.1 | Requests queue on six connections | Enable HTTP/2 on the server or proxy |
### Compression
| Finding | Status | Rule | What it means | Fix |
| --- | --- | --- | --- | --- |
| Not compressed | FAIL | No encoding applied | HTML travels uncompressed; Brotli or gzip usually cut it by 70 to 85% | Enable compression for text types at the server or CDN |
| No Brotli | CHECK | `br` not returned for the `br` request | Brotli is usually 15 to 25% smaller than gzip for HTML | Enable Brotli; every current browser accepts it |
| Brotli saves n% | PASS | `br` applied, sizes known | The saving against the identity body | Nothing |
| Brotli compression observed | PASS | `br` applied, sizes unknown (fallback measurement) | Compression works; the saving is not measured | Nothing |
| Zstandard offered | PASS | `zstd` applied | Chrome and Firefox accept it | Nothing |
| No Vary: Accept-Encoding | CHECK | Compressed response without the Vary value | A shared cache could serve the wrong encoding | Add `Vary: Accept-Encoding` at the origin; check the proxy does not strip it |
| Compressed with (encoding) | PASS | Pasted Content-Encoding | The response was encoded | Nothing; sizes need a live check |
| No Content-Encoding in the pasted headers | INFO | Pasted, no Content-Encoding | Either not compressed or not requested | Run a live check |
### HSTS and preload
| Finding | Status | Rule | What it means | Fix |
| --- | --- | --- | --- | --- |
| (domain) is preloaded, Preloaded via (parent) | PASS | hstspreload.org reports the domain or a parent | Browsers enforce HTTPS before the first visit | Nothing; keep the header |
| Preload submission pending | INFO | Status pending | Waiting for a browser release | Keep the header in place |
| Ready to preload, not listed | INFO | Header eligible, status none | Submission is possible; it is a commitment that takes months to undo | Confirm every subdomain serves HTTPS, then submit |
| No HSTS header | CHECK | No Strict-Transport-Security | The first request can be made in plain text | Send the header on the main domain |
| Not eligible for preload | CHECK | One or more checks false | The missing checks are named | Send `max-age=63072000; includeSubDomains; preload` on the main domain and redirect http:// to https:// on the same host |
## Guide
### HTTP/3 connection failed
1. Read the error in the lane: a timeout usually means UDP 443 is blocked by a firewall or security group; "did not use HTTP/3" means the server answered but on another version.
2. At a CDN, switch HTTP/3 on in its settings. On your own server, build or configure QUIC (nginx 1.25 with `listen 443 quic;`, Caddy on by default) and open UDP 443.
3. Confirm from the command line, then check again:
```sh
curl -sI --http3-only https://example.com/ | head -1
```
### No HTTP/3 offer
1. Enable HTTP/3 as above.
2. Publish the offer on every HTTPS response: `Alt-Svc: h3=":443"; ma=86400`.
3. Check again; the lane should read Advertised or Works, and the INFO about a short `ma` should not appear.
### Not compressed, or gzip only
1. Enable compression for text types (`text/html`, `text/css`, `application/javascript`, `application/json`, `image/svg+xml`) at the server or CDN; a CDN may have it off while the origin compresses, or the reverse.
2. Prefer Brotli with gzip as the fallback; add Zstandard where the server supports it.
3. Confirm each encoding and the sizes:
```sh
for e in br zstd gzip identity; do printf '%s ' "$e"; curl -so /dev/null -H "Accept-Encoding: $e" -w '%{content_type} %{size_download}\n' https://example.com/; done
```
### No Vary: Accept-Encoding
1. Add `Vary: Accept-Encoding` to compressed responses at the origin.
2. Check the proxy or CDN in front of it does not drop the header.
3. Check again; the CHECK disappears.
### Not eligible for preload
1. Read the four checks in the lane; the missing ones are named in the finding.
2. Make `http://` on the main domain redirect to `https://` on the same host with 301 or 308 (the Redirect Checker link under Take it further traces it).
3. Send `Strict-Transport-Security: max-age=63072000; includeSubDomains; preload` on the main domain, only after confirming that every subdomain serves HTTPS: preloading applies to all of them and removal takes months to reach browsers.
4. Check again, then submit at hstspreload.org when the lane reads Ready. Confirm the header with:
```sh
curl -sI https://example.com/ | grep -i strict-transport-security
```
### Reading pasted headers
Paste `curl -sI` output to check the offer, the encoding and the HSTS header of a response you already have. The lanes say what a live check would add. Heal it previews an Alt-Svc offer and a preload-ready HSTS header on the pasted text.
### Break it cases
| Case | What loads | What it shows |
| --- | --- | --- |
| Drop Alt-Svc | Your result's headers (or the sample's) as text, without Alt-Svc | The CHECK for no offer, and the Heal it preview |
| gzip only, no HTTP/3 | `legacy.harbor-supply.example` | No offer, gzip without Brotli, no HSTS on the host |
| Not compressed | `plain.harbor-supply.example` | The FAIL for uncompressed pages |
| Alt-Svc for 5 minutes | `quic.harbor-supply.example` | The INFO for a short `ma` |
| Ready to preload | `preload.harbor-supply.example` | Brotli, Zstandard, an eligible header, not yet listed |
The reserved names are answered in the browser and never requested; the sample's compression sizes are decoded sizes only.
## Worked example
The page opens on `tamperbox.dev`, checked live by our server when the page loads. Read the result top down: the verdict gives one clause per lane; the chips repeat the lane states; the HTTP/3 lane shows the Alt-Svc line as sent and, when the direct probe ran, whether the QUIC-only HEAD request succeeded and how long it took; the compression lane lists what each Accept-Encoding got back with its encoded size against the identity body; the HSTS preload lane shows the header on the main domain, the four checks and the list's answer, which for a `.dev` name is inherited from the top-level domain's entry. The exact header values, sizes and timings are live evidence and change with the site's configuration and the moment of the check, so they are not reproduced here. Replace the address in the box to check your own site, or press a Break it case to see each state on the reserved sample.
## For agents
The Tamperbox MCP server exposes two MCP tools: use `search` to inspect the catalog and schema, then use `execute` to call `POST /v1/tools/http3_checker` through `tamperbox.request`. Its required `url` must identify a public host; use `https://` when testing HTTP/3. The result carries `status`, `http3` (`advertised`, `tested`, `ok`, `timeMs`, `error`), `alpn`, `encodings` (one row per Accept-Encoding with `encoding`, `bytes` and `timeMs`), `headers`, `hsts` (`header`, `httpRedirect`, `apex`, `apexHeader`) and `preload` (`domain`, `status`), plus warnings and provenance. Apply the rules in [Method](#method) to read it, and keep these warnings with any conclusion:
- A failed QUIC connection describes this probe's path to the server, not every visitor's.
- An Alt-Svc offer is a claim; only a tested connection is evidence that HTTP/3 works.
- Sizes come from separate requests and exclude headers and framing; `bytes: null` means the size is unknown, not zero.
- A preload status of `unavailable` means the list could not be asked, not that the domain is absent.
See [Connect an MCP client](https://docs.tamperbox.dev/mcp/connect/) and [Code mode](https://docs.tamperbox.dev/mcp/code-mode/) for the calling pattern, and [Security and privacy](https://docs.tamperbox.dev/reference/security/) for where the address travels.
## Limits
- One probe location at one moment. A failed HTTP/3 connection, a slow transfer or a missing header can be specific to this path.
- The direct test sends one HEAD over HTTP/3 only to the final HTTPS origin; alternate hosts and ports from Alt-Svc are not probed.
- Encoded sizes exclude HTTP headers, TLS and QUIC framing and retransmissions; transfers above 2 MiB or interrupted transfers have unknown complete sizes; separate requests can return different content.
- Without the direct probe, only advertisement and encoding headers are reported, and byte counts stay unknown.
- The TLS version is not reported here; HTTP/2 is inferred from the transfer that succeeded.
- Preload coverage is reported from hstspreload.org; a domain can inherit coverage from a parent entry even without its own header.
- A missing `Vary: Accept-Encoding` is a cache-compatibility warning, not proof of a wrong response.
- History is a browser interaction record, not server monitoring. Explicit tool links use temporary browser storage as described in [Browser transfers](#browser-transfers).
## References
- [RFC 7838, HTTP Alternative Services](https://www.rfc-editor.org/rfc/rfc7838). Proposed Standard, April 2016. Sections used: [3](https://www.rfc-editor.org/rfc/rfc7838#section-3) and [3.1](https://www.rfc-editor.org/rfc/rfc7838#section-3.1).
- [RFC 9114, HTTP/3](https://www.rfc-editor.org/rfc/rfc9114). Proposed Standard, June 2022. Section used: [3.1](https://www.rfc-editor.org/rfc/rfc9114#section-3.1).
- [RFC 9000, QUIC: A UDP-Based Multiplexed and Secure Transport](https://www.rfc-editor.org/rfc/rfc9000). Proposed Standard, May 2021. Used as the transport the direct test requires.
- [RFC 9113, HTTP/2](https://www.rfc-editor.org/rfc/rfc9113). Proposed Standard, June 2022; obsoletes [RFC 7540](https://www.rfc-editor.org/rfc/rfc7540) and [RFC 8740](https://www.rfc-editor.org/rfc/rfc8740).
- [RFC 9110, HTTP Semantics](https://www.rfc-editor.org/rfc/rfc9110). Internet Standard, STD 97, June 2022. Sections used: [8.4](https://www.rfc-editor.org/rfc/rfc9110#section-8.4), [12.5.3](https://www.rfc-editor.org/rfc/rfc9110#section-12.5.3) and [12.5.5](https://www.rfc-editor.org/rfc/rfc9110#section-12.5.5).
- [RFC 6797, HTTP Strict Transport Security (HSTS)](https://www.rfc-editor.org/rfc/rfc6797). Proposed Standard, November 2012. Sections used: [6.1](https://www.rfc-editor.org/rfc/rfc6797#section-6.1) and [6.1.1](https://www.rfc-editor.org/rfc/rfc6797#section-6.1.1).
- [HSTS preload list submission requirements](https://hstspreload.org/#submission-requirements). Chromium project policy; the `preload` directive and the four checks come from here, not from an RFC.
- [RFC 7932, Brotli Compressed Data Format](https://www.rfc-editor.org/rfc/rfc7932). Informational, July 2016; updated by [RFC 9841](https://www.rfc-editor.org/rfc/rfc9841).
- [RFC 8878, Zstandard Compression and the 'application/zstd' Media Type](https://www.rfc-editor.org/rfc/rfc8878). Informational, February 2021; obsoletes [RFC 8478](https://www.rfc-editor.org/rfc/rfc8478); updated by [RFC 9659](https://www.rfc-editor.org/rfc/rfc9659).
- [RFC 1952, GZIP file format specification version 4.3](https://www.rfc-editor.org/rfc/rfc1952). Informational, May 1996.
- [IANA HTTP Content Coding Registry](https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding). The names `br`, `zstd`, `gzip` and `identity` the tool requests.
---
# JSON to YAML Converter
How Tamperbox converts JSON to YAML live in the browser, which hostile or ambiguous inputs it refuses, and how to verify semantic round trips.
Canonical: https://docs.tamperbox.dev/tools/json-to-yaml/
Updated: 2026-10-24
Applies to: tamperbox.dev, October 2026
Prerequisites: A JSON value pasted, typed, dropped or opened as a local file
Outputs: YAML representing the same JSON objects, arrays and scalar values; Findings for validity, complexity and interoperability boundaries
Limitations: The converter preserves parsed data, not whitespace, quote style or object layout; It does not validate an application schema or predict every YAML loader's schema choices; Input is limited to 1,000,000 characters, 80 levels and 100,000 values
The tool page is [tamperbox.dev/json-to-yaml](https://tamperbox.dev/json-to-yaml). It opens with a synthetic QA test-suite object and converts it immediately. This guide explains the parser boundary, the live-update behavior, every refusal, how the reverse handoff works and how to test that a conversion kept the intended data.
Where processing happens: the source is sent only to a Web Worker in the current browser tab. Conversion does not upload the source or interpret it as code. Explicit reverse-conversion navigation temporarily stores the output in tab-scoped session storage for the handoff; ordinary conversion does not persist it. The worker returns converted text and bounded counts to the page.
## What the tool answers
The page answers four questions in task order. First, is the source unambiguous JSON under the interoperability rules used by the converter? Second, what JSON kind and how many values or containers did the parser read? Third, can that tree be represented with the JSON-compatible YAML schema without changing its values? Fourth, what should a QA engineer test before the YAML becomes a fixture, configuration file or deployment input?
The verdict is about the parsed data model. “Converted valid JSON to YAML” does not mean an application accepts the document. It does not apply JSON Schema, an OpenAPI schema or a product configuration schema. It also does not promise that a YAML loader using YAML 1.1 implicit typing will interpret every plain scalar like this converter's YAML 1.2-oriented JSON schema.
## Inputs
### Typed or pasted JSON
The field accepts any JSON value: object, array, string, number, boolean or null. Editing is live. An input event clears the old output immediately, waits 120 milliseconds to avoid parsing every intermediate keystroke, then sends the current source to the worker. Worker results carry a revision number and are ignored after a newer conversion request starts. The current implementation advances that revision when it dispatches the request, rather than on the initial keystroke; see the timing limitation below.
Pasting in the field or elsewhere on the page replaces the source. The text is not “cleaned” into a different shape because punctuation is meaningful JSON. Clear empties the source, output and cards. Empty input is a neutral state, not an invalid document.
### Dropped or opened files
The shared drop zone accepts `.json`, `application/json` and plain-text files. The browser reads the file as text. A file above 1,000,000 bytes is refused before conversion. The file name may appear in the field hint; the bytes never leave the browser.
### Reverse-conversion handoff
Take it further sends the current YAML output to the YAML to JSON page. The transfer uses a one-use random token, session storage and same-origin BroadcastChannel communication, with a 60-second validity window. The source is never placed in a query string or durable storage. The destination consumes the value once and removes the token from its address.
### Input limits
| Boundary | Limit | Why it exists |
| --- | ---: | --- |
| Source text | 1,000,000 characters | Bounds memory, worker messages and parsing time |
| Nesting | 80 levels | Prevents hostile recursion and impractical fixtures |
| Tree size | 100,000 values and containers | Bounds traversal and YAML generation |
| Integer interoperability | −(253−1) through 253−1 | Prevents silent precision changes in JavaScript and common JSON consumers |
| Worker freshness | Current dispatched revision only | Rejects responses superseded by a newer dispatched request |
## Reading the result
### Verdict and checks
A successful verdict names both formats and states that the represented data is unchanged. The checks name valid JSON and JSON-model preservation; the validity detail and a chip give the bounded node count. A failed verdict clears the output before showing the reason, so Copy result cannot accidentally return the previous valid conversion. Heal it then offers the newest source that converted successfully.
### Findings
The findings separate syntax from interoperability. Valid syntax receives a Pass. The data-model check receives a Pass after the complete tree walk. A parser refusal receives one Fail with a safe message; it never echoes private input. The page folds multiple passing rows into one disclosure so problems remain first.
### Converted output
Output uses two-space indentation and a line-width preference of 100 characters. The emitter uses the JSON-compatible YAML schema and disables references, so repeated objects are written as repeated values rather than synthesized anchors. That makes the output predictable for test fixtures but can make a document larger.
### History and reverse conversion
Break it replacements and Heal it repairs are logged newest first. Revert restores the input before the latest change. Reset returns to the first input in that history. Ordinary typing starts a new history because it is a new source, not an automated repair. The reverse link appears only for a valid current output.
## Method
### Strict JSON parsing
The source first passes the platform JSON parser. Then a JSON-token parser checks the same source inside a wrapper object. This second pass detects duplicate member names and integers that cannot be represented safely. A converter must not silently keep the first or last duplicate because different downstream parsers choose differently.
[RFC 8259 section 4](https://www.rfc-editor.org/rfc/rfc8259#section-4) says object names “SHOULD be unique.” The tool treats that interoperability recommendation as a refusal: the intended value cannot be inferred safely. Section 4 also explains that receiver behavior is unpredictable when names are not unique. The stricter choice is deliberate and appears in the page limits.
### Numeric interoperability
[RFC 8259 section 6](https://www.rfc-editor.org/rfc/rfc8259#section-6) permits implementations to set number range and precision limits. It identifies integers in the binary64 exact range as interoperable. The tree walk therefore accepts finite numbers and rejects an integer outside the safe range with “A number is too large to convert safely. Quote it as text.” Quoting identifiers is not a workaround that changes nothing: it intentionally makes the identifier a string, which is usually the correct contract for account numbers, database IDs and long request IDs.
### Complexity walk
The parsed value is traversed iteratively. Each stack item carries depth. More than 80 levels or 100,000 visited nodes stops before YAML emission. The iterative walk avoids using the JavaScript call stack for attacker-controlled nesting. Primitive values count because a flat array with a million strings can be as expensive as a deep tree.
### YAML emission
The emitter receives only the checked value. It uses the JSON schema described by [YAML 1.2.2 section 10.2](https://yaml.org/spec/1.2.2/#102-json-schema), disables references and selects two-space indentation. YAML mappings represent JSON objects, sequences represent arrays, and JSON strings, finite numbers, booleans and null use compatible scalar forms.
Key order follows the parsed object's enumeration order as an implementation convenience, not a semantic promise. JSON object order does not establish application meaning. A consumer that depends on order needs an array or an explicit order field.
### Live rendering and stale-result control
An input edit clears the visible output and schedules conversion. Dispatch increments the revision, and the worker echoes it. The page ignores a response whose revision no longer matches. This guards requests already superseded by another dispatched request. It does not invalidate the earlier revision during the preceding debounce interval or on the empty-input path. Wait for conversion to settle before copying or following the reverse link; a matching revision alone does not prove that the source has remained unchanged since dispatch.
## Standards and conformance
### RFC 8259
| Sections | Applied here | Deliberate boundary |
| --- | --- | --- |
| [3 Values](https://www.rfc-editor.org/rfc/rfc8259#section-3) | Accepts every JSON value at the root | Does not require an object or array |
| [4 Objects](https://www.rfc-editor.org/rfc/rfc8259#section-4) | Parses objects and refuses repeated names | Stricter than `SHOULD` for deterministic conversion |
| [5 Arrays](https://www.rfc-editor.org/rfc/rfc8259#section-5) | Preserves element order and mixed value kinds | Does not apply an item schema |
| [6 Numbers](https://www.rfc-editor.org/rfc/rfc8259#section-6) | Requires finite, safely representable integers | Does not preserve source exponent spelling |
| [8 Strings](https://www.rfc-editor.org/rfc/rfc8259#section-8) | Uses the platform Unicode JSON parser | Does not normalize Unicode |
### YAML 1.2.2
The output targets the [JSON schema](https://yaml.org/spec/1.2.2/#102-json-schema), not the core schema and not YAML 1.1. It uses block mappings and sequences chosen by the emitter. The tool does not claim byte-level canonical YAML, preserve comments or attach application tags. The YAML specification defines a representation graph, serialization tree and presentation stream as distinct levels; this conversion promises the compatible representation, not the original presentation.
## Finding reference
### Refusals
| Finding | Status | Meaning | Fix |
| --- | --- | --- | --- |
| Invalid JSON | Fail | Syntax is incomplete or a duplicate/unsafe value made intent ambiguous | Correct the source or restore the last valid JSON |
| Input above limit | Fail | The browser worker budget would be exceeded | Split the artifact or remove unrelated data |
| Document too complex | Fail | Depth or node count exceeded its bound | Flatten nesting or split the fixture |
| Unsafe integer | Fail | A common JSON consumer may round the identifier | Quote it as a string and update the schema |
### Successful evidence
| Finding | Status | What it proves | What it does not prove |
| --- | --- | --- | --- |
| Valid JSON | Pass | The complete source parsed under the strict checks | The application schema accepts it |
| JSON data model preserved | Pass | The emitter received the checked tree | Every YAML loader chooses identical schemas |
| Node count | Pass | The bounded walk completed | The document is small enough for every consumer |
## Guide
### Convert a fixture safely
1. Paste or open the JSON. Wait for the valid verdict; conversion normally follows the edit after 120 milliseconds.
2. Use Break it → Duplicate key. Confirm the tool clears the YAML rather than choosing one value. Heal it restores the last valid source.
3. Use Break it → Unsafe integer. Decide whether that field is an identifier. If so, quote it in JSON and require a string in the schema.
4. Read the YAML once for application conventions such as required document markers or key order rules. These are outside the data-model conversion.
5. Open “Convert this YAML back to JSON.” Deep-compare the parsed JSON values.
6. Run the application's own schema validator and tests on the exported YAML.
### Check independently
With Node.js and a YAML package already approved by your project, parse both ends and compare values rather than text:
```js
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { load } from 'js-yaml';
const json = JSON.parse(readFileSync('fixture.json', 'utf8'));
const yaml = load(readFileSync('fixture.yaml', 'utf8'));
assert.deepEqual(yaml, json);
```
This command tests your chosen loader, which is valuable because schemas and duplicate-key policies vary. Run it in the project's locked dependency environment, not by installing an unreviewed global package.
### Break it and manual edge cases
| Case | Input property | Expected result |
| --- | --- | --- |
| Duplicate key | Two `role` members | Refused; neither value is selected |
| Unsafe integer | `9007199254740993` | Refused; quote the identifier intentionally |
| Truncated JSON | An unclosed array | Refused; no stale YAML remains |
| Excessive depth | More than 80 nested arrays | Refused before emission |
| Empty source | Whitespace only | Neutral empty state |
## Worked example
The synthetic opening object describes a checkout suite with two cases. Each case has a name and expected HTTP status, and the suite has an enabled boolean. The YAML mapping keeps the suite name, the sequence keeps case order, each nested mapping keeps its fields, numeric statuses remain numbers and the boolean remains a boolean. No real endpoint, credential or customer data appears.
Changing `expected` from `401` to `403` clears the old YAML immediately, then rewrites that scalar after the debounce. Removing the closing brace clears the output and produces a refusal. Restoring it creates a fresh result. Check each completed result against the current source. Rapid edits while a worker is still running are subject to the timing limitation below.
## For agents
The browser page is the private-input surface. Remote automation may call `json_to_yaml` through the Tamperbox API or MCP with an `application/json` text artifact and an optional indentation from 1 through 8. A remote call transmits the artifact to Tamperbox infrastructure; preserve that distinction when advising a user with secrets.
An agent should retain the warnings and provenance, avoid claiming that conversion validates a schema, and test hostile cases before recommending generated fixtures. It should never “repair” a duplicate by choosing the value that looks safer. Ask which value the producer intended or reject the source.
## Limits
### Timing and handoff boundaries
Revision checks begin at dispatch, not at each edit. An earlier worker response can arrive during the 120-millisecond debounce or after Clear and repaint an earlier result. Do not rely on this version for a strict no-stale-output guarantee; wait for the current conversion and compare the output with the source before using it. Reverse handoff temporarily uses session storage and a random URL fragment, with expiry and one-use consumption.
### Format boundaries
The converter does not preserve JSON whitespace, exponent notation, escape spelling or member order as a contract. It does not infer YAML tags, comments or anchors. It does not validate Kubernetes, GitHub Actions, Docker Compose, OpenAPI or another application schema. It cannot prove how a YAML 1.1 loader interprets plain scalars. Processing is local on the page, but a user who follows a remote API or MCP example changes that privacy boundary.
## References
- [RFC 8259](https://www.rfc-editor.org/rfc/rfc8259), especially [values](https://www.rfc-editor.org/rfc/rfc8259#section-3), [objects](https://www.rfc-editor.org/rfc/rfc8259#section-4), [arrays](https://www.rfc-editor.org/rfc/rfc8259#section-5), [numbers](https://www.rfc-editor.org/rfc/rfc8259#section-6) and [strings](https://www.rfc-editor.org/rfc/rfc8259#section-8). Internet Standard.
- [YAML 1.2.2](https://yaml.org/spec/1.2.2/), especially the [representation model](https://yaml.org/spec/1.2.2/#chapter-3-processes-and-models) and [JSON schema](https://yaml.org/spec/1.2.2/#102-json-schema). Current YAML language specification used for the output boundary.
---
# JWT Decoder & Verifier
What the JWT decoder reads from a token, every rule it applies to the header, the signature and the claims, what each finding means for the application that receives the token, and how to fix and verify each one.
Canonical: https://docs.tamperbox.dev/tools/jwt/
Updated: 2026-10-21
Applies to: tamperbox.dev, October 2026
Prerequisites: A three-part signed JWT (a Bearer prefix is removed), and, for signature verification, the HMAC secret or the public JWK or SPKI PEM that matches the token's algorithm
Outputs: A verdict with PASS, CHECK and FAIL chips for the signature and each registered claim, the header and payload as editable tables or JSON, the rebuilt token with its signature state, History and Take it further links for the time claims
Limitations: Decoding proves readability, not trust; a signature match proves only that this key verifies these bytes; JWE, crit headers and unencoded payloads are not verified; no key is ever fetched from a token
The tool page is [tamperbox.dev/jwt](https://tamperbox.dev/jwt). It opens on a synthetic token signed in your tab, answers in one sentence whether that token would be rejected and why, and shows the header, the claims and the rebuilt token under it. This page is the long version: what the page reads, every rule it applies, what each finding means for the application that receives the token, and how to fix and verify each one.
Where processing happens: everything runs in your browser. The token, the key and the expected values are read locally; signatures are checked with the browser's Web Crypto implementation; the `jku` and `x5u` addresses in a header are never fetched and an embedded `jwk` is never used. Nothing is uploaded and nothing is stored.
## What the tool answers
Four questions, in the order a resource server asks them:
1. **Is this a well-formed JWS?** Three unpadded Base64URL segments, a UTF-8 JSON header and payload, no duplicate keys, no number too large to show exactly.
2. **Does the signature verify with your key?** Only once you supply a key, only for the algorithm you expect, and never for `alg: none`.
3. **Is it current?** `exp` not passed, `nbf` reached, `iat` not in the future, each within the clock tolerance you allow.
4. **Is it meant for you?** `iss` equal to the issuer you expect and `aud` containing the audience you expect, when you enter them.
The verdict joins every failure into one "Rejected:" sentence. Otherwise it states how long the token stays current and whether the signature matched. The chips repeat each check in the order above.
## Inputs
### The token
The page takes a token typed, pasted anywhere on the page, dropped as a file or brought from the Unix Timestamp Converter. Before anything is read:
| Step | What happens |
| --- | --- |
| Trim | Surrounding whitespace is removed, then a leading `Bearer ` (any case) |
| Size | More than 100,000 characters is refused: "This token is too large" |
| Shape | Five dot-separated parts is declined as an encrypted JWE; any count other than three gets "A JWT needs three parts: header.payload.signature" |
| Paste cleanup | A pasted `Authorization: Bearer` line or a quoted token is reduced to the token, and the hint says "Token kept from what you pasted". Text that is not a token stays in the field with the decode error under it; Heal it offers a fix when it is almost a token (Remove Base64 padding, Convert to Base64URL, Remove line breaks and spaces, Keep the token only). The key is pasted into its own field |
A segment must contain only letters, digits, `-` and `_`, as [RFC 4648 section 5](https://www.rfc-editor.org/rfc/rfc4648#section-5) defines the URL-safe alphabet, with no `=` padding, as [RFC 7515 section 2](https://www.rfc-editor.org/rfc/rfc7515#section-2) requires for JWS. A segment whose final character carries non-zero unused bits is refused as "noncanonical Base64URL padding bits"; it usually means a hand-edited or truncated token.
### The key or secret
The key field is read only when it holds text, and only after the token decoded. What it accepts depends on the algorithm:
| Algorithm family | Accepted | Refused with |
| --- | --- | --- |
| HS256, HS384, HS512 | The shared secret as UTF-8 text, whitespace included; with "Secret is Base64URL-encoded" ticked, unpadded Base64URL or padded standard Base64 | "The secret is not valid Base64URL" when `+` or `/` is mixed with `-` or `_`, "invalid Base64 padding" |
| RS, PS, ES, Ed25519 (and `EdDSA`) | A public JWK object, or a PEM block beginning `-----BEGIN PUBLIC KEY-----` (SPKI) | "Use a public key, not a private key" for a JWK with `d`; a JWK whose `alg` differs from the selection or whose `use` is not `sig`; an Ed25519 JWK without `kty: OKP` and `crv: Ed25519`; a PKCS#1 `BEGIN RSA PUBLIC KEY` block or a certificate |
A key over 100,000 characters is refused. Verification needs a secure context (HTTPS or localhost) because Web Crypto is unavailable elsewhere; the page says so instead of guessing.
### Expected algorithm, issuer, audience and tolerance
"Expected algorithm" defaults to the token's own `alg`. Choosing a different one stops verification with "The token algorithm does not match the algorithm you selected" rather than trying the key anyway. The expected issuer and audience are compared exactly, character for character. Clock tolerance takes 0 to 300 seconds and applies to `exp`, `nbf` and `iat`; outside that range the field is marked invalid, the claims are evaluated with zero tolerance, and the verdict adds "clock tolerance is invalid".
## Reading the result
### Verdict and chips
The verdict sentence names the outcome: "Rejected: expired 2 hours ago, signature does not match." or "Current for 59 minutes. Signature matches your key." or, with no key, "Current for 59 minutes. Signature unchecked." Each chip is one check: `exp`, `nbf`, `iat`, `iss`, `aud`, the signature and, when needed, the clock tolerance. A chip for a check you can act on is a button that focuses the field in question: the key for an unchecked or failed signature, the expected values for issuer and audience, the tolerance field when it is invalid.
### Findings
Under the verdict, every check is listed worst first with its detail: the UTC time of a time claim and its relative age, "Matches your expected value", "Not checked. Enter the value your application expects." Passes fold into one line once there are two or more.
### Header and Payload
The two segments are shown as tables (Enter applies an edit, Escape restores it) or as JSON you can edit freely. A time claim's value is followed by its UTC date. An edit that would change a claim's JSON type is refused ("exp must be a number of seconds since 1970"), as is a number that cannot be kept exactly.
### Token
The rebuilt token is shown with its state: unchanged, re-signed, unsigned, or "signature broken". An HMAC token is re-signed with the secret in the key field; the synthetic asymmetric examples are re-signed with the private key generated in this tab; a pasted RS, PS, ES or Ed25519 token keeps its old signature, which no longer matches the edited bytes, because the page never accepts a private key.
### History
Every Break it preset, every claim edit and every revert is logged with what it replaced. Revert undoes the newest change; Reset input restores the token you entered, which is also what happens when your edits bring every claim back to its original value.
### Take it further
Each time claim the token carries, `exp`, `nbf` and `iat`, is offered as a link into the [Unix Timestamp Converter](https://tamperbox.dev/unix-timestamp). The value travels through same-site tab memory to a new tab; the token and the key stay on this page and nothing is put in a URL.
## Method
The checks run in this order; a structural failure stops decoding, and the signature and claim checks then run independently of each other.
### Structure and encoding
| Rule | Finding wording | Source |
| --- | --- | --- |
| Each segment is unpadded Base64URL | "Header is not valid unpadded Base64URL." | [RFC 7515 section 2](https://www.rfc-editor.org/rfc/rfc7515#section-2), [RFC 4648 section 5](https://www.rfc-editor.org/rfc/rfc4648#section-5) |
| Unused bits in the last character are zero | "Header has noncanonical Base64URL padding bits." | [RFC 4648 section 3.5](https://www.rfc-editor.org/rfc/rfc4648#section-3.5) |
| Header and payload are UTF-8 | "Payload contains invalid UTF-8." | [RFC 7515 section 7.1](https://www.rfc-editor.org/rfc/rfc7515#section-7.1) |
| Both are JSON objects | "Payload must be a JSON object." | [RFC 7519 section 3](https://www.rfc-editor.org/rfc/rfc7519#section-3) |
| No duplicate member names, at any depth | "Payload has a duplicate key: sub." | [RFC 7519 section 4](https://www.rfc-editor.org/rfc/rfc7519#section-4) |
| Integers within 2^53 - 1 | "Payload has a number too large to display without losing precision." | tool rule |
RFC 7519 section 4 says "The Claim Names within a JWT Claims Set MUST be unique; JWT parsers MUST either reject JWTs with duplicate Claim Names or use a JSON parser that returns only the lexically last duplicate member name". The tool rejects, because implementations that keep the first and those that keep the last disagree about what the token says, and the duplicate check sees escaped spellings of the same name. The safe-integer rule is the tool's own: JavaScript cannot show a larger integer exactly, so the page refuses rather than display a rounded claim.
### Header and algorithm
| Rule | Finding wording | Source |
| --- | --- | --- |
| `alg` is a non-empty string | "The header must name a signing algorithm in alg." | [RFC 7515 section 4.1.1](https://www.rfc-editor.org/rfc/rfc7515#section-4.1.1) |
| `alg: none` has an empty signature | "Unsigned tokens (alg: none) must have an empty signature." | [RFC 7518 section 3.6](https://www.rfc-editor.org/rfc/rfc7518#section-3.6) |
| Any other algorithm has a signature | "The signature is missing." | [RFC 7515 section 5.2](https://www.rfc-editor.org/rfc/rfc7515#section-5.2) |
| `crit` or `b64: false` present | verification stops: "Critical header extensions and unencoded payloads are not supported for verification." | [RFC 7515 section 4.1.11](https://www.rfc-editor.org/rfc/rfc7515#section-4.1.11), [RFC 7797](https://www.rfc-editor.org/rfc/rfc7797) |
An `alg: none` token still decodes, so you can read it, but the verdict always lists "unsigned (alg: none)" among the rejection reasons and the chip reads "alg: none · unsigned". [RFC 8725 section 2.1](https://www.rfc-editor.org/rfc/rfc8725#section-2.1) describes why: a verifier that accepts `none` because the token asked for it has no verifier at all. `jku`, `x5u` and `jwk` are shown as plain header members and never acted on, so a token cannot choose its own key, the attack that [RFC 8725 section 2.1](https://www.rfc-editor.org/rfc/rfc8725#section-2.1) and [section 3.10](https://www.rfc-editor.org/rfc/rfc8725#section-3.10) warn about.
### Signature verification
Verification runs only with a key in the field and only for the expected algorithm, which must equal the token's `alg`. [RFC 8725 section 3.1](https://www.rfc-editor.org/rfc/rfc8725#section-3.1) is the rule: "Libraries MUST enable the caller to specify a supported set of algorithms and MUST NOT use any other algorithms when performing cryptographic operations". The algorithm also decides which key shapes are accepted, which is the key-algorithm binding of [RFC 8725 section 3.2](https://www.rfc-editor.org/rfc/rfc8725#section-3.2): an HMAC secret is never tried against an RSA token and a public key is never used as an HMAC secret.
| Identifier | Web Crypto operation | Notes |
| --- | --- | --- |
| HS256, HS384, HS512 | HMAC with SHA-256, SHA-384, SHA-512 | [RFC 7518 section 3.2](https://www.rfc-editor.org/rfc/rfc7518#section-3.2); a secret shorter than the hash output verifies but is flagged |
| RS256, RS384, RS512 | RSASSA-PKCS1-v1_5 | [RFC 7518 section 3.3](https://www.rfc-editor.org/rfc/rfc7518#section-3.3) |
| PS256, PS384, PS512 | RSA-PSS with salt lengths 32, 48 and 64 bytes | [RFC 7518 section 3.5](https://www.rfc-editor.org/rfc/rfc7518#section-3.5): the salt length equals the hash output |
| ES256, ES384, ES512 | ECDSA on P-256, P-384, P-521 | [RFC 7518 section 3.4](https://www.rfc-editor.org/rfc/rfc7518#section-3.4) |
| Ed25519, `EdDSA` | Ed25519 | [RFC 8037 section 3.1](https://www.rfc-editor.org/rfc/rfc8037#section-3.1); `EdDSA` accepted for the Ed25519 curve only |
The signed input is the ASCII of the first two segments joined by a dot, as [RFC 7515 section 5.2](https://www.rfc-editor.org/rfc/rfc7515#section-5.2) step 8 requires. The result is "Signature matches this key" or "Signature does not match this key". A mismatch means the wrong key, a key rotated out of the issuer's set (compare `kid`), the wrong secret encoding, or a token changed after signing. A match proves only that this key verifies these bytes; it says nothing about whether this key should be trusted.
### Time claims
`exp`, `nbf` and `iat` must be NumericDates, "a JSON numeric value representing the number of seconds from 1970-01-01T00:00:00Z UTC until the specified UTC date/time" ([RFC 7519 section 2](https://www.rfc-editor.org/rfc/rfc7519#section-2)). A quoted value, or a number a JavaScript date cannot hold, fails with "Expected a NumericDate". With tolerance `t` seconds and the device clock `now`:
| Claim | Fails when | Chip |
| --- | --- | --- |
| `exp` | `now` is at or past `exp + t` | "exp · expired 2 hours ago"; inside the tolerance: "exp · within tolerance" |
| `nbf` | `nbf` is later than `now + t` | "nbf · starts in 59 minutes" |
| `iat` | later than `now + t`, as a notice only | "iat · in the future" |
| `exp` absent | never; a notice | "exp · never expires" |
RFC 7519 [section 4.1.4](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.4): "the current date/time MUST be before the expiration date/time listed in the exp claim"; [section 4.1.5](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.5): "the current date/time MUST be after or equal to the not-before date/time". Both allow "some small leeway, usually no more than a few minutes, to account for clock skew", which is the 300-second ceiling. Whether `exp` is required is the application's decision, so its absence is a notice. Times come from your device clock and the verdict re-evaluates every ten seconds while the tab is visible. `auth_time` is shown as a date but not checked.
### Issuer and audience
`iss` must be a string; `aud` a string or an array of strings ([RFC 7519 section 4.1.1](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.1) and [section 4.1.3](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.3)). Another type fails as "wrong type". With an expected value entered, `iss` must equal it and the expected audience must equal `aud` or be one of its members: "If the principal processing the claim does not identify itself with a value in the aud claim when this claim is present, then the JWT MUST be rejected." The comparison is exact, so a trailing slash or a different case is a mismatch, which is what [RFC 7519 section 2](https://www.rfc-editor.org/rfc/rfc7519#section-2) prescribes for StringOrURI values. An empty expectation reports "not checked", never a pass; [RFC 8725 section 3.8](https://www.rfc-editor.org/rfc/rfc8725#section-3.8) is why the audience check matters: a token for one service replayed against another passes every other check.
### Rebuilding edited tokens
An edited header or payload is re-encoded and the token rebuilt. With a usable signing key the state is "signed"; otherwise the old signature is kept and the state is "stale", because the bytes it covered have changed. Break signature replaces the first character of the signature with `A` (or `B` when it already is `A`), which keeps the segment canonical and guarantees a mismatch. A rebuilt `alg: none` token has an empty signature.
## Standards and conformance
### RFC 7519: JSON Web Token
Implemented: section 2 (NumericDate, exact StringOrURI comparison), section 3 (the Claims Set is a JSON object), section 4 (unique claim names), sections 4.1.1 to 4.1.6 (`iss`, `sub` shown only, `aud`, `exp`, `nbf`, `iat`), section 7.2 steps 1 to 10 for a JWS. Not implemented: step 7 for nested JWTs (`cty: JWT` is shown, not unwrapped), section 5.3 replicating claims as header parameters, and any JWE path. Stricter than the text: duplicates are rejected, not resolved. The tool does not apply [RFC 7519 section 4.1.7](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.7) `jti` replay detection; it has no store to check against.
### RFC 7515 and RFC 7518: JWS and JWA
Implemented: compact serialisation (section 3.1 of RFC 7515), Base64URL without padding (section 2), the signing input of section 5.2, the `alg` parameter (section 4.1.1), refusal of `crit` (section 4.1.11) and of `b64: false` from [RFC 7797 section 3](https://www.rfc-editor.org/rfc/rfc7797#section-3). From RFC 7518 the algorithms in the table above and `none` (section 3.6). Not implemented: `jku`, `x5u`, `x5c` and `x5t` resolution (section 4.1.2 to 4.1.8), JSON serialisation, RS and PS keys supplied as PKCS#1, and ECDSA curves beyond the three named. Where it differs: [RFC 7518 section 3.2](https://www.rfc-editor.org/rfc/rfc7518#section-3.2) says an HMAC key "MUST be" at least the hash size; the tool verifies a shorter secret and flags it, so a test token still reads while the weakness is named.
### RFC 7517 and RFC 8037: keys
A JWK is accepted when it is a public key ([RFC 7517 section 4](https://www.rfc-editor.org/rfc/rfc7517#section-4) members; a private `d` from [RFC 7518 section 6](https://www.rfc-editor.org/rfc/rfc7518#section-6) is refused), its `use` is absent or `sig` ([section 4.2](https://www.rfc-editor.org/rfc/rfc7517#section-4.2)) and its `alg` is absent or equal to the selection ([section 4.4](https://www.rfc-editor.org/rfc/rfc7517#section-4.4)). Ed25519 keys follow [RFC 8037 section 2](https://www.rfc-editor.org/rfc/rfc8037#section-2) (`kty: OKP`, `crv: Ed25519`); the JOSE identifier `EdDSA` from [section 3.1](https://www.rfc-editor.org/rfc/rfc8037#section-3.1) is accepted and mapped to the browser's Ed25519 operation. Ed448 is not supported. JWK Sets (`keys` arrays) are not read; paste the one key, chosen by `kid`.
### RFC 8725: best current practices
Followed: 2.1 and 3.1 (no `none` verification, caller-chosen algorithms), 3.2 (key and algorithm bound), 3.8 (audience checked when supplied), 3.10 (no key fetched from the token). Partly followed: 3.9 (the typ header is shown, not enforced) and 3.11 (exact issuer match when supplied). Not applicable: 3.5 to 3.7 (key management in the application).
### What the tool does not check
Revocation, replay (`jti`), nested tokens, encryption ([RFC 7516](https://www.rfc-editor.org/rfc/rfc7516) tokens are declined on sight), certificate chains behind `x5c`, and the application's own rules about scopes, subjects and roles.
## Finding reference
### Structure
| Finding | Severity | Section | Meaning for the receiver | Fix |
| --- | --- | --- | --- | --- |
| A JWT needs three parts | fail | RFC 7515 §3.1 | Not a JWS; nothing can be read | Copy the whole token; check for a cut at a line break |
| Encrypted token (JWE) | fail | RFC 7516 | Claims are encrypted; this tool cannot read them | Decrypt with the recipient's key elsewhere |
| Not valid unpadded Base64URL | fail | RFC 7515 §2 | Standard Base64 or padding was used | Re-encode with the URL-safe alphabet, no `=` |
| Noncanonical padding bits | fail | RFC 4648 §3.5 | The segment was edited or truncated by hand | Regenerate the token |
| Invalid UTF-8, not valid JSON, must be a JSON object | fail | RFC 7519 §3 | The segment does not hold a claims object | Fix the producer's serialisation |
| Duplicate key | fail | RFC 7519 §4 | Libraries disagree about which value wins | Emit each claim once |
| Number too large | fail | tool rule | The value cannot be shown exactly | Carry large identifiers as strings |
### Header, algorithm and signature
| Finding | Severity | Section | Meaning for the receiver | Fix |
| --- | --- | --- | --- | --- |
| alg: none · unsigned | fail | RFC 8725 §2.1 | Anyone can change the claims; the token proves nothing | Sign with a real algorithm; reject `none` at the verifier |
| The signature is missing | fail | RFC 7515 §5.2 | A signed algorithm with nothing to verify | Regenerate the token |
| signature · add a key | notice | RFC 7515 §5.2 | Nothing verified yet | Enter the secret or public key |
| signature · does not match | fail | RFC 7515 §5.2 | Wrong key, wrong encoding or altered token | Compare `kid` with the issuer's current keys; check the secret's encoding |
| signature · broken by your edits | fail | RFC 7515 §5.2 | The old signature no longer covers the bytes | Re-sign with the secret, or use as a negative test |
| signature · broken on purpose | fail | RFC 7515 §5.2 | A deliberate bad-signature fixture | None; every server should reject it |
| signature · could not check | fail | RFC 7518 §3 | Algorithm mismatch, key refused, `crit`, `b64: false` or no secure context | Read the detail: it names the cause |
| Weak HMAC secret | notice | RFC 7518 §3.2 | Fine for tests, not for production | Use a secret at least as long as the hash output |
### Time, issuer and audience
| Finding | Severity | Section | Meaning for the receiver | Fix |
| --- | --- | --- | --- | --- |
| exp · expired | fail | RFC 7519 §4.1.4 | A conforming server rejects it | Issue a new token; check the issuer's clock and lifetime |
| exp · within tolerance | pass | RFC 7519 §4.1.4 | Accepted only thanks to your leeway | Treat as expiring; renew |
| exp · never expires | notice | RFC 7519 §4.1.4 | Valid until the key is rotated | Add `exp` unless the application forbids it |
| nbf · starts in | fail | RFC 7519 §4.1.5 | Not yet usable | Wait, or fix the issuer's clock |
| iat · in the future | notice | RFC 7519 §4.1.6 | The issuer's clock is ahead | Synchronise clocks |
| not a valid time | fail | RFC 7519 §2 | A string or out-of-range number where seconds were expected | Emit a JSON number of seconds |
| iss or aud · does not match | fail | RFC 7519 §4.1.1, §4.1.3 | Issued by, or for, someone else | Check the expected value for trailing slashes and case; reject the token otherwise |
| iss or aud · wrong type | fail | RFC 7519 §4.1.1, §4.1.3 | The claim is not a string (or array of strings) | Fix the issuer |
| iss or aud · not checked | notice | RFC 8725 §3.8 | You have not said who you are | Enter the expected value |
| clock tolerance · invalid | fail | RFC 7519 §4.1.4 | Settings error; claims judged with zero tolerance | Enter 0 to 300 |
## Guide
### The token is expired, or not valid yet
1. Read the `exp` or `nbf` chip: it names the UTC time and the distance from now.
2. Compare with the issuer's clock. A few seconds off is clock skew; set a tolerance of up to 300 seconds and see whether the chip turns to "within tolerance". More than that is a lifetime or a clock problem at the issuer.
3. Obtain a fresh token and paste it. The verdict should read "Current for" the remaining lifetime.
### exp decodes to a year far in the future
The claim was written in milliseconds. The table shows the date tens of thousands of years ahead and the chip says the token never meaningfully expires. Divide by 1000 at the issuer, or send the value to the Unix Timestamp Converter from Take it further and read it as milliseconds to confirm.
### Wrong audience or issuer
Enter the exact values your application compares against. A mismatch chip opens the expected-values panel and focuses the field. If the token's `aud` is an array, your value must be one of its members; a trailing slash on either side is a mismatch, not a near miss.
### alg: none
The token decodes, the chip reads "alg: none · unsigned", and the verdict rejects it. No key makes it pass. If a token from a real issuer arrives this way, the issuer is misconfigured; if your server accepted it, the verifier must be configured with an explicit algorithm allow-list.
### The signature does not match
| Check | How |
| --- | --- |
| Algorithm | Expected algorithm equals the header's `alg`; a different selection stops with a mismatch message |
| Key | `kid` in the header against the issuer's current key set; a rotated key is the usual cause |
| Secret encoding | A Base64-exported secret needs "Secret is Base64URL-encoded"; a text secret must keep its exact whitespace |
| Token integrity | Any edit after signing changes the bytes; Reset input and verify the original |
### A key is refused
The detail names the cause: a private JWK (`d` present), a JWK with `use` other than `sig` or a conflicting `alg`, a PKCS#1 block instead of SPKI, a certificate, or no secure context. Export the public key as SPKI (`openssl pkey -in private.pem -pubout`) or as a public JWK.
### Break it cases
| Button | What it loads | What it shows |
| --- | --- | --- |
| Expire it | `exp` one hour ago | "Rejected: expired 1 hour ago" |
| Not yet valid | `nbf` one hour ahead, `exp` moved to two hours ahead if it was sooner | "not valid for another 1 hour" |
| Wrong audience | `aud` set to `someone-else` | "audience does not match" once an expected audience is entered |
| alg: none | header `alg` set to `none` | "unsigned (alg: none)" |
| Break signature | the first signature character swapped | "signature altered" |
Every preset rebuilds the token: the HMAC example is re-signed with its secret, so the only failing check is the one the preset introduced. Revert in History undoes one preset; Reset input undoes them all.
### An independent check
Verify an HS256 token outside Tamperbox with OpenSSL. With the token in `$T` and the secret in `$S`:
```sh
data=$(printf %s "$T" | cut -d. -f1-2)
printf %s "$data" | openssl dgst -sha256 -hmac "$S" -binary | openssl base64 -A | tr '+/' '-_' | tr -d '='
printf %s "$T" | cut -d. -f3
```
The two printed lines are equal when the signature verifies. For an RS256 token, decode the signature segment to a file and run `openssl dgst -sha256 -verify public.pem -signature sig.bin` over the same first two segments.
## Worked example
The page opens on a synthetic HS256 token created in your tab when the page loads: header `alg: HS256`, `typ: JWT`; payload `sub: qa-example`, `name: Demo user`, `iss: https://issuer.example`, `aud: tamperbox-demo`, `role: tester`, `iat` now and `exp` one hour ahead. It is signed with a demo secret the page shows you, and the secret is already in the key field, so the opening verdict reads "Current for 59 minutes. Signature matches your key." with every chip green except `iss` and `aud`, which read "not checked" until you enter `https://issuer.example` and `tamperbox-demo`. The token is reserved sample data with a reserved issuer name; it is not a credential for anything and a new one is signed on every load, so its exact bytes are never the same twice. Choosing an asymmetric example generates a key pair in the tab, shows the public key as PEM, and keeps the private key non-extractable, which is why those examples can be re-signed while a pasted RS256 token cannot.
## For agents
The Tamperbox MCP server exposes two MCP tools: use `search` to inspect the catalog and schema, then use `execute` to call the selected operation through `tamperbox.request`. The JWT page maps to four `POST /v1/tools/...` operations:
| Operation | Required input | Result |
| --- | --- | --- |
| `jwt_decode` | An `application/jwt` text artifact | A JSON artifact containing the decoded header and payload; no verification |
| `jwt_verify` | `token`, `key` and an explicit `algorithm` | Separate signature evidence, claim checks, header and payload |
| `jwt_rebuild` | JSON `header` and `payload`; optionally an HMAC secret or old signature | A token whose state is `unsigned`, `signed`, `stale` or `broken` |
| `jwt_fixture` | Optional `algorithm`, `header` and `payload` | A synthetic token, selected algorithm and verification key; private keys are not retained |
All responses also carry warnings and provenance. Keep these with any conclusion:
- A decoded token is readable, not trusted; a verified signature proves only that the supplied key verifies those bytes.
- Fixtures are not application credentials, and a rebuilt token passes for anyone who trusts the same key.
- None of the operations establishes application authorisation: scopes, roles and `jti` replay are the application's rules.
See [Connect an MCP client](https://docs.tamperbox.dev/mcp/connect/) and [Code mode](https://docs.tamperbox.dev/mcp/code-mode/) for the calling pattern, and [Security and privacy](https://docs.tamperbox.dev/reference/security/) for where a token travels when an agent, rather than the browser page, decodes it.
## Limits
- Decoding proves readability, not origin; verification proves that one key verifies these bytes, not that the key is the right one to trust.
- No key is fetched from `jku`, `x5u` or an embedded `jwk`, and no key set is read; choose the key by `kid` yourself.
- JWE, nested JWTs, `crit` extensions and `b64: false` payloads are declined or left unverified.
- Private asymmetric keys are never accepted, so only HMAC tokens and the tab's own examples can be re-signed.
- Times come from your device clock; a wrong local clock produces wrong expiry findings.
- Exact string comparison for issuer and audience; no normalisation of case or trailing slashes.
- Input is limited to 100,000 characters for the token and the key.
- Nothing is stored, and the token is not put in a URL; the Take it further link carries only the selected claim value.
## References
- [RFC 7519, JSON Web Token (JWT)](https://www.rfc-editor.org/rfc/rfc7519). Proposed Standard, May 2015; updated by RFC 7797 and RFC 8725. Sections used: [2](https://www.rfc-editor.org/rfc/rfc7519#section-2), [3](https://www.rfc-editor.org/rfc/rfc7519#section-3), [4](https://www.rfc-editor.org/rfc/rfc7519#section-4), [4.1.1](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.1), [4.1.3](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.3), [4.1.4](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.4), [4.1.5](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.5), [4.1.6](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.6), [4.1.7](https://www.rfc-editor.org/rfc/rfc7519#section-4.1.7) (not applied) and [7.2](https://www.rfc-editor.org/rfc/rfc7519#section-7.2).
- [RFC 7515, JSON Web Signature (JWS)](https://www.rfc-editor.org/rfc/rfc7515). Proposed Standard, May 2015. Sections used: [2](https://www.rfc-editor.org/rfc/rfc7515#section-2), [3.1](https://www.rfc-editor.org/rfc/rfc7515#section-3.1), [4.1.1](https://www.rfc-editor.org/rfc/rfc7515#section-4.1.1), [4.1.11](https://www.rfc-editor.org/rfc/rfc7515#section-4.1.11), [5.2](https://www.rfc-editor.org/rfc/rfc7515#section-5.2) and [7.1](https://www.rfc-editor.org/rfc/rfc7515#section-7.1).
- [RFC 7518, JSON Web Algorithms (JWA)](https://www.rfc-editor.org/rfc/rfc7518). Proposed Standard, May 2015; updated by [RFC 9864](https://www.rfc-editor.org/rfc/rfc9864). Sections used: [3.2](https://www.rfc-editor.org/rfc/rfc7518#section-3.2), [3.3](https://www.rfc-editor.org/rfc/rfc7518#section-3.3), [3.4](https://www.rfc-editor.org/rfc/rfc7518#section-3.4), [3.5](https://www.rfc-editor.org/rfc/rfc7518#section-3.5), [3.6](https://www.rfc-editor.org/rfc/rfc7518#section-3.6) and [6](https://www.rfc-editor.org/rfc/rfc7518#section-6).
- [RFC 7517, JSON Web Key (JWK)](https://www.rfc-editor.org/rfc/rfc7517). Proposed Standard. Sections used: [4](https://www.rfc-editor.org/rfc/rfc7517#section-4), [4.2](https://www.rfc-editor.org/rfc/rfc7517#section-4.2) and [4.4](https://www.rfc-editor.org/rfc/rfc7517#section-4.4).
- [RFC 8037, CFRG Elliptic Curve Diffie-Hellman (ECDH) and Signatures in JOSE](https://www.rfc-editor.org/rfc/rfc8037). Proposed Standard, January 2017. Sections used: [2](https://www.rfc-editor.org/rfc/rfc8037#section-2) and [3.1](https://www.rfc-editor.org/rfc/rfc8037#section-3.1).
- [RFC 8725, JSON Web Token Best Current Practices](https://www.rfc-editor.org/rfc/rfc8725). Best Current Practice, February 2020; updates RFC 7519. Sections used: [2.1](https://www.rfc-editor.org/rfc/rfc8725#section-2.1), [3.1](https://www.rfc-editor.org/rfc/rfc8725#section-3.1), [3.2](https://www.rfc-editor.org/rfc/rfc8725#section-3.2), [3.8](https://www.rfc-editor.org/rfc/rfc8725#section-3.8) and [3.10](https://www.rfc-editor.org/rfc/rfc8725#section-3.10).
- [RFC 7797, JSON Web Signature (JWS) Unencoded Payload Option](https://www.rfc-editor.org/rfc/rfc7797). Proposed Standard; updates RFC 7519. Section used: [3](https://www.rfc-editor.org/rfc/rfc7797#section-3) (declined for verification).
- [RFC 7516, JSON Web Encryption (JWE)](https://www.rfc-editor.org/rfc/rfc7516). Proposed Standard. Five-part tokens are declined; no section is applied.
- [RFC 4648, The Base16, Base32, and Base64 Data Encodings](https://www.rfc-editor.org/rfc/rfc4648). Proposed Standard. Sections used: [3.5](https://www.rfc-editor.org/rfc/rfc4648#section-3.5) and [5](https://www.rfc-editor.org/rfc/rfc4648#section-5).
---
# Limits
Execution, request, result, and concurrency limits for Tamperbox MCP code mode.
Canonical: https://docs.tamperbox.dev/reference/limits/
Updated: 2026-10-20
Applies to: MCP 0.3.0
Outputs: Constraints for planning a bounded MCP execution
Limitations: Hosted infrastructure can add limits outside the package defaults.
## Default budgets
| Resource | Default |
| --- | --- |
| Code | 64 KiB UTF-8 |
| Input to each API call | 1 MiB UTF-8 |
| Result from each API response | 2 MiB serialized JSON |
| JSON nesting | 40 levels |
| Execution time | 15 seconds, selectable from 1-30 seconds |
| Upstream request time | 30 seconds, bounded by the execution deadline |
| API calls per execution | 20 |
| Parallel API calls | 4 |
| Active executions per process | 4 |
| QuickJS allocator budget | 32 MiB |
| WebAssembly linear memory | 64 MiB |
## Counting API calls
`apiCalls` counts bridge calls attempted, including host-validation failures. Guest serialization failures happen before dispatch and do not consume the call budget.
## Execution completion
Await every API request. Calls still running when the submitted function completes are cancelled. Responses do not retry or follow redirects unless the documented operation itself defines that behavior.
The hosted API applies an approximate 60-request-per-minute caller budget; network probes have a separate, lower budget. Hosted MCP users can share the server's upstream caller identity. Twenty permitted bridge calls are therefore not a promise that twenty network checks will be accepted. Start with small batches, stop on rate denial and respect `details.retryAfterSeconds` when supplied. MCP exposes a bounded upstream error code and retry delay, not raw upstream error bodies, and does not automatically retry submitted work.
## Result serialization
Return JSON-compatible values. BigInt values, cyclic objects, functions, symbols, and non-finite numbers are rejected. `undefined` becomes `null` at the top level and is omitted from object properties under normal JSON rules.
---
# llms.txt Checker & Generator
How the llms.txt checker fetches and reads a site's llms.txt, the outline an agent builds from it, every rule it applies, how it compares the file with the sitemap and drafts a new one, and how to fix each finding.
Canonical: https://docs.tamperbox.dev/tools/llms-txt/
Updated: 2026-10-24
Applies to: tamperbox.dev, October 2026
Prerequisites: A public host name, or an llms.txt file to paste
Outputs: A verdict, findings, the outline an agent reads, the file line by line, a draft built from the site and repairs
Limitations: llms.txt is a proposal some AI tools read and many do not; links are checked for form, not requested; the draft names pages from the site's own links and paths
The tool page is [tamperbox.dev/llms-txt](https://tamperbox.dev/llms-txt). It checks `tamperbox.dev` live when opened, says whether its llms.txt follows the format and what an agent would read from it, and drafts a file from the site. This page is the long version: what is fetched, every rule, how the outline and the draft are made, and how to fix each finding.
Where processing happens: a pasted llms.txt is read in your browser and never uploaded. A host name is sent to the Tamperbox server, which fetches /llms.txt, the home page, /robots.txt and up to two top-level sitemaps, each with up to four index children, without cookies; the file is read and the draft written in your browser. Explicit sibling-tool transfers use temporary browser storage; diagnostic results are not a monitoring history. Sample hosts on the reserved .example zone are answered in the browser from a fixture.
## What the tool answers
1. **Does the site publish an llms.txt an agent can use?** Served as text at `/llms.txt`, not as a web page.
2. **What does an agent read from it?** The name, the summary, the notes and each section's links, as an outline.
3. **Does it follow the format?** Every line an agent would misread: a missing name, relative links, links outside lists, empty sections.
4. **What does it leave out?** Sitemap pages the file never links.
5. **What could it say?** A draft from the site's own name, description, navigation and sitemap.
## Inputs
### A host
`example.com` or a full address; the host is kept. The check runs when you stop typing, or on Enter. The usual refusals apply: spaces, other schemes, credentials, non-standard ports, IP addresses and single labels.
### A pasted file
Text with a newline, or starting with `#`, `>`, a list link `- [` or a link `[Name](`, is read as llms.txt, in the browser. Nothing is fetched, so there is no draft and no sitemap comparison, and relative links cannot be resolved unless the current programmatic load retains a base from the preceding host lookup; direct typing or pasting into the field clears that base. Files up to 2 MiB (`.txt`, `.md`) can be dropped or opened.
A pasted address loses quotes, brackets and trailing punctuation and keeps the host; text that fits nothing stays as pasted.
## Reading the result
### Verdict and chips
"llms.txt with n links in m sections, in the expected format." or "… and n things to fix.", "No llms.txt at host." with the draft offered, "llms.txt is a web page." or "Empty llms.txt." The chips count problems and scroll to the findings. Two items say where the input went.
### Findings
Worst first, line findings prefixed with their line; two or more passing findings are grouped under an expandable **checks passed** row. A single passing finding stays visible.
### What an agent reads
The outline: the name (or "No name" in red), the summary (or the first note shown in its place), then each section with its link count and links. A section titled Optional is marked as one an agent may skip. Relative links are tinted.
### llms.txt
The file as written, numbered, issue lines tinted and noted. Long files show their first 60 lines and every marked line; **Show all n lines** lists the rest, and **Show the first 60 lines** folds it again. Folding is offered only when at least ten unmarked lines after line 60 can be hidden; marked lines remain visible.
### Draft from the site
A complete llms.txt built from the site, with a note saying how many link names came from the site's own links and how many from URL paths. **Copy**, **Download llms.txt**, and **Check the draft**, which loads the draft into the box and checks it.
### History
History follows the current result evidence. Break it and Heal it record input changes, newest first. **Revert** restores the input before the latest recorded change; **Reset input** restores the input before the first retained change and clears the list. **Restore input before this break** under Heal it also reverts the latest change.
These actions reload and check the restored input. Restoring a public address can therefore send a new request, including on the two tools whose ordinary typing waits for a run button. Editing or pasting a new input clears History. The list lives in the current page; it is not saved monitoring data and does not restore an old network response.
**Check the draft** is also logged as an input change, so Revert returns to the input that produced the draft.
### Take it further
Only links with an available result value appear.
| Follow-up | Value carried | When offered |
| --- | --- | --- |
| [Redirect Checker](https://tamperbox.dev/redirect-checker) | `https://host/llms.txt` | A host was checked |
| [Website Down Checker](https://tamperbox.dev/website-down-checker) | First absolute HTTP(S) link | Such a link appears in the file |
| [SSL Checker](https://tamperbox.dev/ssl-checker) | A linked host outside the checked site | The file names a different host outside the site's subdomains |
### Related checks and input transfer
Check the rest carries the current address to a sibling tool. Take it further carries the specific value listed above. Pasted file contents are not transferred through these links. Transfers temporarily use browser `sessionStorage` with a one-use random token in the URL fragment; the input itself is not in the URL. The destination accepts a transfer for less than one minute and removes the token. Manual destination tools wait for their run button or Enter.
Each destination collects new evidence. A follow-up link does not confirm the original verdict or repair the remote site.
### Result cards
Six expandable cards below the result explain its meaning, give a beginner explanation, offer an agent prompt, describe good practice, suggest fixes and state limitations. Their text follows the current result; it is not another measurement. Open a card for its full explanation and the relevant [Method](#method) or [Guide](#guide). The agent prompt can be copied for review; copying it does not run a repair or send it to an agent.
## Method
### Fetching
For a host, three fetches start together on `https://host`: `/llms.txt` (up to 5 redirects, first 2 MiB), the home page (up to 10 redirects, first 512 KiB of HTML) and `/robots.txt`. Then up to two sitemaps are read: those robots.txt lists, or `/sitemap.xml`, plus up to four children of each top-level sitemap index. The home page and sitemaps feed the draft and the coverage check; if they fail, the file is still checked.
| Response for /llms.txt | Severity |
| --- | --- |
| 400 or above | INFO, "No llms.txt": nothing breaks; a draft is offered |
| HTML content type or body | FAIL: an agent reads a web page |
| Empty | CHECK |
### Structure
The [llms.txt proposal](https://llmstxt.org/#format) defines the file as Markdown with "an H1 with the name of the project or site. This is the only required section", then optionally a blockquote summary, other details, and "Zero or more markdown sections delimited by H2 headers, containing “file lists” of URLs". Each list item is "a required markdown hyperlink `[name](url)`, then optionally a : and notes about the file". Headings and lists follow [CommonMark](https://spec.commonmark.org/0.31.2/). The tool reads the file line by line, skipping fenced code blocks:
| Rule | Severity | Repair |
| --- | --- | --- |
| No `#` line at all | FAIL | Add "# Name" first (when a host was checked) |
| Text before the `#` line | FAIL | |
| A second `#` line | CHECK | Turn extra H1s into sections |
| No `>` summary under the name | INFO | |
| A name but no `##` sections | CHECK | |
| A `##` section with no links | CHECK | |
| `###` or deeper heading | INFO: read as a note | |
| A link without a scheme | CHECK | Make links absolute (when a host was checked) |
| A `[Name](url)` that is not a list item | CHECK | Put links in a list |
| A list item with no link | CHECK | |
| The same URL twice | INFO | |
Lines inside sections are links when they match `- [Name](url)` with an optional quoted title and `: notes`; `*` and `+` bullets are accepted. Blockquote lines before the first section join into the summary; other text before it is kept as notes. A section titled `Optional`, which the proposal uses "by convention, for secondary information: links an agent can skip when a shorter context is needed", is marked so in the outline.
### Coverage
When both the file and a sitemap were read, every sitemap URL other than the home page is compared with the file's links, after resolving relative links. Missing pages are INFO with the count and two example paths: a prompt to link what an agent needs most, not a target to list everything.
### The draft
Only what the site publishes:
- the name from `og:site_name`, else the `` cut at the first " | ", " · ", " – ", " — " or " - ";
- the summary from `og:description`, else the meta description;
- same-origin links with text inside the home page's `nav`, `header` and `footer`, named with that text;
- same-origin sitemap URLs not already in the navigation, named from the last part of their path.
Privacy, terms, cookies, legal, imprint, impressum and disclaimer pages go into Optional; other pages one level deep go under Pages, deeper ones grouped by their first path segment, at most 40 links per section. Navigation added by scripts is not seen.
## Standards and conformance
### The llms.txt proposal (llmstxt.org)
Implemented: the H1 name as the only required part, the blockquote summary, notes, H2 sections of link lists with optional notes, the Optional section. Stricter than the text: a relative link is a CHECK, because the proposal's examples use full URLs and agents fetch the file on its own. Not implemented: the companion `.md` versions of pages and `llms-full.txt`, which the proposal suggests but does not require. The proposal is a community document, not an RFC or W3C specification; [llmstxt.org](https://llmstxt.org/) is its canonical text.
### CommonMark
Headings, block quotes, list items and inline links are recognised by their CommonMark forms; fenced code blocks are skipped. The parser is line-based, so multi-line list items and reference-style links are not read as links.
### Sitemaps protocol and RFC 9309
Sitemaps are found through robots.txt Sitemap lines ([RFC 9309, section 2.2.4](https://www.rfc-editor.org/rfc/rfc9309#section-2.2.4)) or at `/sitemap.xml`, and read as the [Sitemaps protocol](https://www.sitemaps.org/protocol.html) defines; the [robots.txt checker](https://docs.tamperbox.dev/tools/robots-txt-checker/) checks a bounded set of their syntax and entries.
## Finding reference
| Finding | Severity | What it means for an agent | Fix |
| --- | --- | --- | --- |
| No llms.txt (n) | INFO | Nothing to read; nothing breaks | Optional; the draft is a start |
| llms.txt is a web page | FAIL | Reads HTML | Serve Markdown text |
| Empty llms.txt | CHECK | Nothing to read | Write the file |
| Line 1: No H1 name | FAIL | Cannot tell what the file describes | Add "# Name" (Heal it) |
| Line n: H1 is not the first line | FAIL | May miss the name | Move it first |
| Line n: A second H1 | CHECK | Extra name ignored | Use ## (Heal it) |
| Line n: No summary | INFO | No one-line context | Add "> summary" |
| Line n: No ## sections | CHECK | Nothing to follow | Add sections |
| Line n: Section "x" has no links | CHECK | Adds nothing | Add links or remove |
| Line n: Hn heading | INFO | Read as a note | Use ## |
| Line n: Relative link | CHECK | Leads nowhere out of context | Full URLs (Heal it) |
| Line n: Link outside a list | CHECK | Read as a note | List item (Heal it) |
| Line n: List item without a link | CHECK | Skipped | Add the link |
| Line n: Linked twice | INFO | | Keep one |
| n sitemap pages not linked | INFO | Pages not pointed to | Link the important ones |
## Guide
### No llms.txt
Decide whether you want one. If yes: review the draft, rename links that came from paths, add a note to each that needs it, cut what an agent does not need, and publish it at `/llms.txt` as `text/plain` or `text/markdown`. Confirm outside Tamperbox: `curl -s https://example.com/llms.txt | head`.
### The file is served as HTML
Exempt `/llms.txt` from the catch-all route, so the file is served before the app.
### Format problems
Use Heal it for the name, extra H1s, relative links and links outside lists; each rewrites the whole file for review. For list items without links and empty sections, add the links by hand. Then check again.
### Choosing what to link
Link the pages an agent is asked about: getting started, pricing, the API reference, support. Put the rest under `## Optional`. The coverage finding lists sitemap pages you left out; it is not a target.
### Break it cases
| Case | What it loads | What to look at |
| --- | --- | --- |
| No H1 name | Your file without its `#` line | No H1 name; Heal it adds it back |
| Relative links | Your file with every link made relative | Relative link; Heal it makes them absolute |
| Links without a list | Your file with the list bullets removed | Link outside a list; Heal it lists them |
| No llms.txt | A sample host without the file | The draft |
| Served as a web page | A sample single-page app | llms.txt is a web page |
The first three are built from your own file when you entered a host or pasted one.
## Worked example
The page opens with a live check of `tamperbox.dev`. It fetches llms.txt and supporting site material to build the outline and, when the home page is available, a draft. Link counts, findings and suggested sections depend on the current site.
Read the outline first: the name, summary, notes and H2 link lists should describe what a reader can learn. A file can be syntactically readable yet omit the task-specific pages an agent needs. Compare the draft's navigation-derived names with names inferred from URL paths; rename ambiguous entries before using them.
Use **Check the draft** to load the generated text into the parser. This is a local input replacement, not publication. Review every destination and remove irrelevant links before saving the file at the site's root. A missing or blocked supporting sitemap can reduce coverage without making the existing llms.txt invalid. The checker does not request the linked pages, so verify important destinations independently before presenting the file as ready to publish.
## For agents
Use the Tamperbox MCP `search` tool to inspect the schema, then `execute` with `tamperbox.request` to call `POST /v1/tools/llms_txt`. Supply the required `domain` field as a public domain name. The operation is named `llms_txt`; the browser page’s hyphenated identifier is not the API operation name.
The operation fetches llms.txt and returns `evidence` and a parsed `report`. It does not fetch the home page and sitemap collection to generate the browser’s draft or coverage comparison. Links are parsed, not requested.
Keep the input, observation time, source limitations and inconclusive results with any summary. Browser fixtures are demonstrations, not live MCP targets. Do not turn missing evidence into a passing check or infer that an inaccessible resource has been deleted.
See [Connect an MCP client](https://docs.tamperbox.dev/mcp/connect/), [Code mode](https://docs.tamperbox.dev/mcp/code-mode/) and [Security and privacy](https://docs.tamperbox.dev/reference/security/).
## Limits
- One fetch each of llms.txt (2 MiB), the home page (512 KiB) and robots.txt, and up to two top-level sitemaps with up to four children each.
- Links are not requested.
- The parser is line-based CommonMark; multi-line list items and reference links are not read as links.
- Live checks are rate limited. Explicit sibling-tool transfers use temporary browser storage; diagnostic results are not a monitoring history.
## References
- [The llms.txt proposal](https://llmstxt.org/), [format](https://llmstxt.org/#format).
- [CommonMark specification 0.31.2](https://spec.commonmark.org/0.31.2/): [ATX headings](https://spec.commonmark.org/0.31.2/#atx-headings), [block quotes](https://spec.commonmark.org/0.31.2/#block-quotes), [list items](https://spec.commonmark.org/0.31.2/#list-items), [links](https://spec.commonmark.org/0.31.2/#links).
- [Sitemaps XML format, protocol 0.9](https://www.sitemaps.org/protocol.html).
- [RFC 9309, Robots Exclusion Protocol](https://www.rfc-editor.org/rfc/rfc9309#section-2.2.4), section 2.2.4.
---
# Minecraft Server Status Checker
How the Minecraft Server Status Checker follows the SRV record, asks a Java Edition server for its server list status and ping, what every verdict and finding means, and how to fix servers players cannot join.
Canonical: https://docs.tamperbox.dev/tools/minecraft-server-status/
Updated: 2026-10-24
Applies to: tamperbox.dev, October 2026
Prerequisites: A Java Edition server address (host name or public IP), with or without a port; the name must resolve only to public addresses
Outputs: The server list entry the game would draw (message, players, version, signal bars), the SRV target and address connected to, connect and ping times, and findings for full, offline, old or misconfigured servers
Limitations: Java Edition only (Bedrock uses UDP); one region; version, players and message are self-reported; the check never logs in or joins
The tool page is [tamperbox.dev/minecraft-server-status](https://tamperbox.dev/minecraft-server-status). It opens on `play.harbor-supply.example`, a local sample survival server with fixed answers, and checks your own server when you press Check server. This page is the long version: how the address is resolved the way the game resolves it, what the status exchange asks, what each verdict and finding means for players, and how to confirm a fix without Tamperbox.
Where processing happens: typing sends nothing. Names under the reserved .example zone, which the example and Break it use, are answered in your browser with fixed sample answers. Pressing Check server or Enter sends the address to the Tamperbox server, which resolves it, connects once from Eastern North America, asks for the server list status and a ping, and returns the observation. It never logs in, joins or sends a player name.
## What the tool answers
1. **Where does the game connect?** The SRV record, if there is one, or the name on port 25565, and the address it resolves to.
2. **Does a Java Edition server answer there?** Online, refused, no answer, or something that is not a Minecraft server.
3. **What does it say about itself?** Message of the day, players online and the maximum, version name and protocol number, and whether it enforces secure chat.
4. **How fast is it?** The TCP connect time and the status ping, drawn as the game's signal bars.
## Inputs
### A server address
`play.example.org`, `example.org:25570`, `203.0.113.10` or `[2001:db8::10]:25565`. A pasted link keeps its host and port. Without a port, the check follows the SRV record, using the tool's bounded SRV lookup; with a port, it connects to that port and ignores the SRV record. A port outside 1 to 65535 is refused with a repair that removes it. Private, loopback, link-local, unique local and carrier-grade NAT addresses are refused in the browser: no server on the internet can reach them, and a home server must be checked by its public address.
### When it runs
Only on Enter or the Check server button. Adding spaces around the input keeps the current result; a changed address stages a new check and waits. A sample name under `.example` runs at once because it costs nothing.
## Reading the result
### Verdict and chips
Online: "Online with N of M players", then the version, the ping and, when used, the SRV target. A full server is a warning. Offline outcomes read "Offline: … refused the connection", "No answer from …", "Not a Java server at …" or "Unavailable: … says it is not running". Chips give the player count, the version and "Uses SRV".
### Findings
Directly under the verdict, worst first; see the [Finding reference](#finding-reference).
### The server list entry
The server as the game's multiplayer list draws it: an initial in place of the icon (icons are not fetched), the address, the message of the day with its formatting codes removed, the player count with a fill bar, and the signal bars with the ping.
### How it answered
The address entered, the SRV record (or why it was not used), the address and port connected to, the TCP connect time, the status ping, the version, the protocol number with the release it belongs to where that is certain, the secure chat flag, and the region the check ran from.
### History
History records input changes made by Break it and Heal it. It appears after the complete result and before Take it further. Revert undoes the newest change; Reset input returns to the input before the first retained change. Restore input before this break is also available in Heal it when there is a change to undo. Restoring does not create another history entry. This history is held in the page and disappears on reload. Restoring a live target stages it for the run button; reserved samples can render immediately. Typing or pasting a different input starts a new history.
### Take it further
These links carry values derived from the current result. A link is absent when its required evidence is missing; the tool does not invent a target.
| Destination | Value and condition |
| --- | --- |
| [SRV Lookup](https://docs.tamperbox.dev/tools/srv-lookup/) | `_minecraft._tcp.` for a live hostname entered without a port |
| [TCP Ping](https://docs.tamperbox.dev/tools/tcp-ping/) | The connected address and game port, for live results |
| [ASN Lookup](https://docs.tamperbox.dev/tools/asn-lookup/) | The server address, when available |
Reserved sample results offer only the derived links supported by local sample data. A live destination applies its own input rules; measurement tools still wait for an explicit run.
### What this result means
Six cards explain the current findings: Ask your agent, In plain words, What does it mean?, What good looks like, How to fix it, and Tool limitations. Expand a card to read the explanation and its method or guide link. Copying an agent prompt does not run another check. The cards interpret the evidence already shown; they are not an additional measurement or an independent confirmation.
When the input is cleared, a check fails, or no result is available, result-specific cards clear and contextual links disappear. Read the verdict before relying on a card, and distinguish a missing observation from a confirmed negative result.
## Method
### 1. Following the SRV record
When no port is given and the host is a name, the server asks DNS for SRV records at `_minecraft._tcp.` ([RFC 2782](https://www.rfc-editor.org/rfc/rfc2782#page-2), "The format of the SRV RR"). An answer with more than 32 records, or with a priority or weight outside 0 to 65535, is refused as invalid. The selection approximates RFC 2782's usage rules ([page 6](https://www.rfc-editor.org/rfc/rfc2782#page-6)): keep the records with the lowest priority, then pick one at random in proportion to its weight ([page 5](https://www.rfc-editor.org/rfc/rfc2782#page-5)), or uniformly when all weights are zero. The implementation is not an exact RFC selection algorithm: zero-weight entries receive no chance when positive weights exist. When the positive weight total is smaller than the number of candidates, its random range can also leave no target selected, causing a failed lookup. A failure in this case is not evidence that every listed server is down. A target of `.` stops the check with "Unavailable", because, in the RFC's words, "A Target of "." means that the service is decidedly not available at this domain" ([page 4](https://www.rfc-editor.org/rfc/rfc2782#page-4)). No SRV record means the name itself on port 25565. A DNS failure other than "no such record" is reported as an error rather than silently falling back.
The check tries one target. It does not fall back to the next target when the chosen one fails, so a network with several SRV targets of equal priority can show different results on different checks.
### 2. Resolving and connecting
The target name must resolve to between 1 and 64 addresses, all public; the first is used. The server opens one TCP connection from Eastern North America. The time to complete the handshake is the TCP connect time. The whole exchange must finish within 5 seconds of starting and the request within 7 seconds.
### 3. Server List Ping
On connect, the server sends the two packets of the Java Edition status exchange, as documented in the [Server List Ping](https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping) protocol description: a handshake with protocol number `-1` (asking for status without claiming a client release), the address and port as the player typed them, and the next state "status", followed by an empty status request. The original name and port are sent, not the SRV target, because proxies and hosting panels use them to pick the virtual server.
The answer must be one framed packet with id 0 holding UTF-8 JSON, with a string `version.name`, an integer `version.protocol`, and non-negative integer `players.online` and `players.max`; anything else is "Not a Minecraft status reply". The message is read from `description`, as a plain string or a text component with `extra` parts, and its `§` formatting codes are removed. `enforcesSecureChat` is reported when the server sends it. Responses are limited to 256 KiB in total and 128 KiB per packet. Player samples and the server icon are ignored.
Then the server sends a ping packet with eight random bytes and times the matching pong. A server that answers the status but not the ping is still online, with "No status ping".
### 4. Outcomes
| Outcome | Socket or protocol event | Verdict |
| --- | --- | --- |
| Online | Status JSON read | Online with N of M players |
| Refused | `ECONNREFUSED` | Offline: refused the connection |
| No answer | No connection or no reply within 5 seconds | No answer |
| Unreachable | `ENETUNREACH`, `EHOSTUNREACH` | No answer |
| Not Minecraft | Connected, but the reply is not a status packet, or the connection closed | Not a Java server |
| Unavailable | SRV target is `.` | Unavailable: says it is not running |
### 5. Signal bars and releases
The bars follow the game client's thresholds: five under 150 ms, four under 300 ms, three under 600 ms, two under 1000 ms, one above, none without a ping. Protocol numbers map to releases only where the mapping is certain (for example 47 is 1.8 to 1.8.9, 769 is 1.21.4); other numbers are shown as reported. A protocol below 754 (1.16.4) raises "An older game version", because current clients join such a server only through a version translation layer.
## Standards and conformance
| Specification | Parts used | How the tool applies them | Where it stops |
| --- | --- | --- | --- |
| [RFC 2782](https://www.rfc-editor.org/rfc/rfc2782) DNS SRV | [Format](https://www.rfc-editor.org/rfc/rfc2782#page-2), [target "."](https://www.rfc-editor.org/rfc/rfc2782#page-4), [weight](https://www.rfc-editor.org/rfc/rfc2782#page-5), [usage rules](https://www.rfc-editor.org/rfc/rfc2782#page-6) | Lowest priority, weighted random choice, "." means unavailable | One target is tried; no fallback to other targets. Obsoletes [RFC 2052](https://www.rfc-editor.org/rfc/rfc2052) |
| [RFC 9293](https://www.rfc-editor.org/rfc/rfc9293) TCP | [3.5](https://www.rfc-editor.org/rfc/rfc9293#section-3.5) | Connect time is the handshake | |
| [Server List Ping](https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping) (community protocol documentation; there is no formal standard) | Handshake, status request and response, ping and pong | Modern status exchange (1.7 and later) | The legacy pre-1.7 ping and Bedrock's UDP protocol are not implemented |
| [RFC 1918](https://www.rfc-editor.org/rfc/rfc1918#section-3), [RFC 6598](https://www.rfc-editor.org/rfc/rfc6598#section-4) | as listed | Private and shared addresses are refused | |
## Finding reference
| Finding | Severity | What it means for players | Fix |
| --- | --- | --- | --- |
| Connection refused | FAIL | "Connection refused": nothing listens on the port | Start the server; check server-port; check the port forward |
| No answer within 5 seconds | FAIL | "Timed out": stopped, firewalled, or no port forward | Allow TCP on the port; forward it; check the server is up |
| Not a Minecraft status reply | FAIL | Another program is on the port, or a proxy blocks status | Check the port and the proxy's listener |
| Service unavailable (verdict) | FAIL | The game stops at the record and cannot join by this name | Point the SRV record at the real host and port, or remove it |
| The server is full | CHECK | "The server is full" until someone leaves | Raise max-players or grant a bypass permission |
| Port 19132 (or 19133) is Bedrock's default | CHECK | A Bedrock server cannot be checked this way | Use the Java port, or test Bedrock in the game |
| Found through an SRV record | INFO | Players can type the name without a port | Keep the record in step with the server |
| An older game version | INFO | Current clients need a translation layer to join | Update the server or add ViaVersion |
| No status ping | INFO | The game shows no signal bars | Usually harmless on old servers and some proxies |
| Reported by the server | INFO | Version, players and message are unverified | |
## Guide
### Friends see "Connection refused"
1. Start the server and wait for "Done" in its console.
2. Check `server-port` in `server.properties` matches the port players use.
3. For a server at home, forward that TCP port in the router to the computer running it; the [Open Port Checker](https://tamperbox.dev/open-port-checker) tests the forward from outside.
4. Check again here.
### Friends see "Timed out"
The connection is dropped before it reaches the server: a firewall on the computer, a missing port forward, carrier-grade NAT at the provider (your router's WAN address starts with 100.64 to 100.127), or a server that is off.
### Players must type a port
Add an SRV record so the name alone works:
```
_minecraft._tcp.example.org. 3600 IN SRV 0 5 25577 mc.example.org.
```
Confirm it without Tamperbox:
```sh
dig +short SRV _minecraft._tcp.example.org
```
The answer lists priority, weight, port and target. Then check the name here without a port: "How it answered" should show the SRV target.
### Break it cases
| Case | What it loads | What it shows |
| --- | --- | --- |
| Server full | `full.harbor-supply.example` | 20 of 20 players and the full-server warning |
| Old server, no ping | `old.harbor-supply.example` | Protocol 47 (1.8) and a server that skips the ping |
| Offline | `down.harbor-supply.example` | A refused connection and its fix |
| Bedrock address | `bedrock.harbor-supply.example:19132` | The Bedrock port warning with a timeout |
| Private address | `192.168.1.50` | The refusal in the browser; nothing is sent |
| Port out of range | your server with port 99999 | The port error and the repair without the port |
## Worked example
The opening input is `play.harbor-supply.example`, a reserved sample survival server answered locally. Read its message, reported version and player counts as fixture data. The sample does not contact a real game server or establish that any player can join.
Replace it with a public Java Edition server and press Check server. When no explicit port is supplied, inspect whether an SRV target was selected and which address and port were actually contacted. Compare TCP connect time with status ping: the latter includes the application-level pong exchange.
An online result means a valid status reply arrived. Whitelists, authentication, protocol compatibility and gameplay performance can still prevent a player from joining. A full-server fixture illustrates the capacity warning, while a reply without pong remains online with no status-ping timing.
## For agents
The Tamperbox MCP server runs the same check. Search its catalog for `minecraft_server_status`, inspect the input schema, and call it with the host and, only when the player uses one, the port. Each call spends one probe. Keep these warnings with any conclusion:
- Online here does not mean a player can join: whitelists, versions and authentication are not tested.
- Players, version and message are self-reported.
- Bedrock servers cannot be checked.
See [Connect an MCP client](https://docs.tamperbox.dev/mcp/connect/) and [Code mode](https://docs.tamperbox.dev/mcp/code-mode/), and [Security and privacy](https://docs.tamperbox.dev/reference/security/) for where the input travels. [TCP Ping](https://docs.tamperbox.dev/tools/tcp-ping/) measures the game port from three regions.
### Transfers to another tool
Explicit sibling-tool controls can carry the current input or observed address to another page. The shared navigation stores that value temporarily in tab-scoped `sessionStorage`, accepts a transfer for at most one minute, and puts a random transfer token in the URL fragment. The input itself is not placed in the URL. The receiving page consumes the transfer and applies its own input rules; tools with a manual Run button still require that action for live checks. This is not a saved result, and the destination can send the value to the services it needs. Ordinary edits and lookups do not create a durable report archive.
## Limits
- Java Edition only; Bedrock uses UDP.
- One check from Eastern North America; one SRV target, no fallback.
- The legacy pre-1.7 status ping is not implemented.
- Names that resolve to private or reserved addresses are refused.
- Sample answers under `.example` are fixed and labelled.
- The page keeps results in memory. Explicit transfers to another tool use temporary session storage.
## References
- [RFC 2782](https://www.rfc-editor.org/rfc/rfc2782), A DNS RR for specifying the location of services (DNS SRV): the record format, target ".", the weight field and the usage rules (pages 2 to 6). Obsoletes [RFC 2052](https://www.rfc-editor.org/rfc/rfc2052).
- [RFC 9293](https://www.rfc-editor.org/rfc/rfc9293), Transmission Control Protocol: section 3.5.
- [Java Edition protocol: Server List Ping](https://minecraft.wiki/w/Java_Edition_protocol/Server_List_Ping), community documentation of the status exchange.
- [RFC 1918](https://www.rfc-editor.org/rfc/rfc1918#section-3), [RFC 6598](https://www.rfc-editor.org/rfc/rfc6598#section-4): private and shared address space.
---
# Mixed Content & Cookie Checker
How the Mixed Content & Cookie Checker fetches or reads a page, sorts every insecure address by what browsers do with it, judges every Set-Cookie header, and how to fix and verify each finding.
Canonical: https://docs.tamperbox.dev/tools/mixed-content-checker/
Updated: 2026-10-24
Applies to: tamperbox.dev, October 2026
Prerequisites: A public HTTP or HTTPS address on port 80 or 443, or the page's HTML, optionally preceded by its response headers and a blank line
Outputs: A verdict naming the insecure addresses and the cookies that need attention, chips, findings worst first, the insecure addresses grouped by browser behaviour with tag and line, every cookie with its attributes and notes, and corrected Set-Cookie lines
Limitations: Only the first 512 KiB of the HTML as served is read with a tag scanner; resources added by scripts, addresses inside external style sheets and requests made inside frames are not seen, and upgraded addresses are not tested over HTTPS
The tool page is [tamperbox.dev/mixed-content-checker](https://tamperbox.dev/mixed-content-checker). It opens by fetching `tamperbox.dev` live, answers in one sentence whether the page loads anything over plain HTTP and how its cookies fare, and lists the evidence under it. This page is the long version: what is fetched, every rule the page applies, what each finding means, and how to fix and verify each one.
Where processing happens: an address is sent to the Tamperbox server, which fetches the page once, following up to 10 redirects, and returns the headers of every response and the first 512 KiB of HTML. Cookie values are replaced on the server by their length, so only names and attributes reach your browser. Pasted HTML and headers are read in your browser and never uploaded. History and tool-to-tool transfers remain in the browser; see Browser transfers below.
## What the tool answers
Four questions, in the order a browser meets them:
1. **Is the page itself on HTTPS?** If the final address is `http://`, mixed content does not apply; the whole page is exposed, and that is the finding.
2. **What does the page load over plain HTTP?** Each `http://` address in the HTML, sorted by what browsers do: block it, upgrade it to `https://`, or warn before a form sends.
3. **Does a policy soften it?** A Content-Security-Policy with `upgrade-insecure-requests`, and a `` that turns relative addresses insecure.
4. **Are the cookies safe?** Every Set-Cookie header, on the final response and on each redirect, judged for what a browser rejects, what it exposes and what it merely notes.
The verdict sentence answers the second and fourth questions; the chips count blocked, upgraded and form addresses and rejected or weak cookies, and each chip scrolls to its section. Findings under the verdict list the problems worst first, with passes folded into one line.
## Inputs
The field takes three forms. What runs depends on the first characters of the text.
| Input | Recognised by | What runs | Where |
| --- | --- | --- | --- |
| An address | anything that is not HTML or a header block | one page fetch, up to 10 redirects | Tamperbox server |
| HTML | starts with `<` after leading whitespace | tag scan and classification | your browser |
| Headers, blank line, HTML | starts with a status line or header lines | cookie checks, CSP read, then the HTML scan | your browser |
### An address
`example.com`, `https://www.example.com/shop` or a full address with a query. The fetch starts when you stop typing, or on Enter. An address without a scheme is fetched over `https://`. The parser refuses, with a message in the field, an address with spaces, a scheme other than `http` or `https`, an IP address, a host without a top-level domain, a port other than 80 or 443, and a user name or password; the last two offer a repair under Heal it. The server repeats these checks for the input and every redirect target and requires each host to resolve to public addresses only. It sends `Accept: text/html,application/xhtml+xml;q=0.9,*/*;q=0.8` with a fixed user agent and no cookies. If the final response is not `text/html` or `application/xhtml+xml`, its body is discarded and only headers and cookies are checked.
### Pasted HTML and headers
Paste the page source, or the output of `curl -si https://example.com/`: headers first, a blank line, then the HTML. Only the last response's headers are used, so cookies set on a redirect in pasted output are not read. Pasted source is treated as an HTTPS page at an unknown address, so relative addresses never count as insecure. Files (`.html`, `.htm`, `.txt`) up to 2 MiB can be dropped or opened and are read the same way.
### What the page does to pasted text
The shared input cleaner runs on paste, drop and Enter. This is a page tool, so a pasted full address stays whole (path and query included) rather than being cut to a host; quotes and angle brackets around it are dropped. Text that is neither an address nor HTML nor headers stays exactly as pasted and the parser's error explains it. Break it and Heal it loads are never rewritten.
## Reading the result
### Verdict and chips
The verdict leads with the count of insecure addresses and what browsers do with them, or "No mixed content on" the host, or "Plain HTTP page." The second line counts the cookies that need attention and how many browsers reject. Copy result copies the verdict and evidence as text.
### Findings
One line per problem class: each group of insecure addresses, each cookie rule broken (with the cookie names), the plain-HTTP page, an `http://` base, and notes such as "upgrade-insecure-requests is set". Informational cookie notes stay with their cookie in the Cookies section. The count on the right says how many findings need fixing.
### Insecure addresses
Grouped into Blocked by browsers, Upgraded to https:// and Forms that send in plain text. Each row shows the tag and attribute (`