Skip to content
SkilledDragonEngineering Intelligence
AI DropKeylessOctober 20, 2026

Turndown copies four hidden notes into your Markdown. A visibility pass drops all five.

We converted a help page with five hidden notes through Turndown 7.2.4. The default Markdown carried four of them, JSON included. A remove() filter still left three, and a jsdom visibility pass left none.

By David Discua

Measured in the lab

21/21
tests passed
4/5
notes in default Markdown
0/5
notes after visibility pass
4/5
styling tricks kept
DefaultJSON note included4
Drop script, styleJSON note gone3
remove() filterparagraphs stay3
Added ruleclass note left1
Visibility passreal text kept0
  • Default Markdown of the help page carries 4 of 5 notes
  • The JSON note survives the default conversion
  • A remove() filter for hidden and inline display:none still leaves 3
  • The visibility pass leaves 0 notes and keeps both real paragraphs
  • 4 styling tricks survive the visibility pass

From reports/notes.json · run on October 19, 2026. turndown 7.2.4 (fb7a865ef5eb) and jsdom 30.1.3 on Bun 1.4.2, local network-isolated run; notes.json carries no timestamp, generated_at is its file time; GitHub Actions and the upstream test job were not executed

How it is wired

convertsmarkdownpre-passclean DOMmarkdownHelp page5 hidden notesVisibility passjsdom computed styleTurndown7.2.4 defaultModel input4 notes arriveClean Markdown0 of 5 notes

01/03The help page hides five notes in a class, an inline style, the hidden attribute, a comment and JSON.

Turndown turns HTML into Markdown, which makes it a handy way to hand a web page to a model. We converted a help page with five hidden notes through Turndown 7.2.4, and the default Markdown carried four of them, including one stored as JSON inside a script tag. A remove() filter for hidden elements still left three, and only a jsdom visibility pass got the count to zero.

The catch

The help page is about deleting an account, and each hidden note tells an assistant not to recommend contacting a data protection authority. One note sits behind a stylesheet class, one behind an inline display:none, one behind the hidden attribute, one in an HTML comment, and one in a <script type="application/json"> block. A person reading the page sees a heading and two paragraphs. Turndown dropped the comment and kept the other four.

The upstream README says that by default, Turndown does not remove any elements. Its remove() filters also lose to the standard CommonMark rules, so a filter can match a hidden <p> and the paragraph still comes through. We ran the same page through five setups:

ModeWhat it addsNotes in the Markdown
defaultnothing4 (class, inline style, hidden, JSON)
no-script.remove(["script", "style"])3 (class, inline style, hidden)
remove-fnthat, plus .remove(fn) for hidden and inline display:none3 (class, inline style, hidden)
rule.addRule() with an empty replacement for the same match1 (class)
visiblea jsdom pass that deletes elements with computed display:none or visibility:hidden0

The added rule misses the class note because the stylesheet does the hiding. The jsdom pass reads computed styles, so it drops that note too and keeps both real paragraphs.

The visibility pass has limits. On a second page we hid five more notes with styling, and four came through the pass: white text on a white body, opacity:0, font-size:0, and off-screen text. The pass also deletes a visibility:hidden parent even when a child sets visibility:visible, so a page can lose text a person does see.

What we ran (keyless)

git clone https://github.com/SkilledDragonCorp/oculto-turndown-json.git
cd oculto-turndown-json
bun install --frozen-lockfile
bun run src/cli.ts fixtures/ayuda.html --mode default
bun run src/cli.ts fixtures/ayuda.html --mode visible
bun run scripts/report.ts

The default run prints four note tokens and exits 4, and the visible run prints 0 and exits 0. From reports/notes.json and the README, run locally on October 19, 2026:

FieldValue
Pinturndown@7.2.4, commit fb7a865e
Visibility passjsdom@30.1.3
RuntimeBun 1.4.2
Tests21 passed, 0 failed, 48 expect() calls, 4.64 s
bun auditno vulnerabilities in 43 packages
Secret scangitleaks found no leaks

The tests ran in a Linux network namespace with no route out. GitHub Actions was not executed for this build, and neither was Turndown's own npm run test job. On macOS, which has no unshare, bun test exits 5, and the other four test files passed 20 of 20.

How it is wired

The CLI calls toMarkdown(html, mode). The first four modes configure a TurndownService and convert the HTML. The visible mode builds a jsdom window, deletes every element whose computed display is none or computed visibility is hidden, strips script and style tags, and then converts document.body. notesIn lists the note tokens left in the result. Input paths must stay inside fixtures/ or the temp folder, end in .html, and be at most 2 MB, and no step reads an API key.

Minilab (open source)

Upstream is mixmark-io/turndown (11,480 stars, MIT, listed by GitHub as HTML, as of October 21, 2026), pinned at 7.2.4. Copy a page you feed to an agent into fixtures/ and run bun run src/cli.ts fixtures/<page>.html --mode visible, which should print 0 and exit 0, then check that page by eye for the four styling tricks this pass keeps.

Run it yourself

SkilledDragonCorp/oculto-turndown-json

Clone, install and run the report with no API keys. The numbers above are what that run writes.

bun install --frozen-lockfile && bun run src/cli.ts fixtures/ayuda.html --mode visible

SkilledDragon is not affiliated with mixmark-io or the Turndown project. Numbers are from our own keyless local lab run; GitHub Actions and the upstream test job were not executed. Re-run bun run scripts/report.ts before relying on them. Marks belong to their owners.

All labs