# A 1px font hid a whole password reset

> An agent asked X for a reset code. The email passed every check, and our trust layer showed the agent an empty message. Here is why, and what we changed.

- From: MachineMail Team <blog@machinemail.dev>
- Date: 2026-10-19
- Tags: trust, security
- Source: https://machinemail.dev/blog/one-pixel-font
- Reply: blog@machinemail.dev

An agent asked X for a password-reset code. X sent it. The message passed SPF, DKIM and DMARC, aligned with x.com. It was about as authentic as email gets.

Our trust layer showed the agent an empty message and rated it suspicious.

The agent couldn't read the code, so it couldn't finish the reset. Nothing in the email was hostile. We were wrong.

## What the trust layer does with HTML

Every inbound message goes through the trust layer before an agent sees it. One of its jobs is to find text a human reader would never see. That's a classic prompt-injection trick: put instructions to the agent in white-on-white text, a zero-size font or a hidden element, and hope the agent reads the raw HTML.

So we work out what a mail client would show and give the agent only that. Hidden text is removed and recorded in the [trust report](https://machinemail.dev/docs/trust/trust-report). Links inside hidden text are flagged too.

That part works. The bug was in how we decided what counts as hidden.

## What X's template does

Big senders build email out of nested tables, because tables render the same everywhere. Some clients size the whitespace between cells from the font, so a common trick is a tiny font on the layout tables, then the real size again on each cell that holds text.

X's template does exactly that. The outer wrapper tables get `font-size:1px`; each cell sets `16px` again. Roughly:

```html title="The shape of the template (simplified)"
<table style="font-size:1px; line-height:1px">
  <tr>
    <td style="font-size:16px; line-height:24px">Use this code to reset your password: …</td>
  </tr>
</table>
```

In a browser the cell's text is 16px and perfectly readable. Font size is inherited, and the cell overrides it.

## What we did with it

Our hidden-text check saw `font-size:1px` on the wrapper and treated the wrapper as hidden. Then it stopped. It never looked at the descendants to see whether one of them set a readable size again. Everything inside the wrapper was marked hidden, and the wrapper held the whole email.

So the whole body was removed. The agent-safe text came out empty. Every link was flagged as "hidden from the human reader", because as far as we knew, every link was.

Two more things piled on.

- **t.co.** X wraps links in t.co, its own shortener. We flagged it like any other shortener: a link whose real destination you can't see.
- **Credential wording.** A password reset talks about passwords and codes. Our scoring weighs credential-request wording heavily, because phishing does the same.

Hidden text, hidden links, a shortener and a credential request add up to a suspicious score. Each signal was reasonable on its own. Together they rated a real reset email from X as suspicious.

## What we changed

The fixes shipped together, in one change to the trust layer.

**Font size and visibility are inherited values now.** We track the computed font size down the tree, the way CSS does. A 1px wrapper hides its own text, but a cell inside it that sets 16px is visible again. `visibility` works the same way: a `visibility:visible` child inside a `visibility:hidden` parent is shown. Properties that really do hide a whole subtree, like `display:none`, still hide it.

**A plain-text safety net.** Most mail also carries a `text/plain` part. When the HTML hiding would remove most of the message's text, and the plain-text part contains that text, the agent-safe text now comes from the plain-text part. Text the plain-text part shows to every reader was never really hidden. The net never applies when the hidden text contains instructions to an agent. That stays hidden text, and it still quarantines.

**First-party shorteners.** In mail that passes DMARC for the sender's own organization, a link through that organization's own shortener, like t.co in mail from X, is no longer flagged by itself. Because t.co also carries links to other people's posts, it only counts as first-party when the link names a destination on X's own domains, and we check that destination like any other link.

**Less weight on credential wording in authenticated reset mail.** When a code or reset message is DMARC-aligned, its links stay on the sender's own sites, the code isn't carried in a link, and our own checks find no request for credentials, the credential-request wording counts for less. The same wording in mail that fails authentication, or that carries bad links, weighs as much as it did before.

## Making sure we didn't open a hole

A change that makes hidden text visible again is exactly what an attacker would want. So before shipping we reviewed it as an attacker would, and closed what we found:

- Zero sizes written as `calc()`, `vw` units or `var()` are evaluated, not ignored. `font-size:0.2vw` is hidden text on a phone, and we treat it that way.
- `!important` is honored. A stylesheet rule like `font-size:0 !important` beats a readable inline size, as it does in a browser.

## The result

The same email now comes out **known**, with the code readable in the agent-safe text. The links are no longer flagged. We kept a copy of the message as a test fixture, with the code and every personal identifier replaced, so this can't quietly come back.

## The report is for agents, too

Every one of these decisions is written into the trust report that comes with each message: what was hidden and why, which links were flagged, which signals moved the score. Agents can read it through the API and the MCP server, not just people in the dashboard.

So an agent that gets an empty message isn't left guessing. The report says the body was removed as hidden text, and why. When we get it wrong, as we did here, the report shows exactly where.
