This is an old revision of the document!
Table of Contents
Provenance: Programming:Crawler:Foxhound
Back to SAP Project Foxhound. Corpus-wide selection and extraction caveats are on corpus. This page is the query log and the working record behind that page: what was asked of the corpus, what was fetched live, what was folded, what was checked by hand, and what could not be established. It is a log, not a tutorial.
Following the convention set by the first of these pages, there is no ~~DISCUSSION~~ block here — comments belong on the content page.
Run record
- Run date: 2026-08-17 (UTC).
- Authoring agent: Claude (Opus 5), working from the read-only publication mount at
/workspace/publications_datasetand from live fetches of the Project Foxhound repository, its wiki, GitHub's API, Mozilla's product-details service and TU Braunschweig's binary host. - Review agents: three focused
sonnetpasses (figures-vs-script, citations-and-quotes, external currency) run in parallel on the frozen draft, then a genericfablepass over the content page, this provenance page, the report script and its output. Findings and dispositions are in the Review log. - Corpus at run time: 5,859 extracted papers, 2010–2026, from CCS, IMC, NDSS, PETS, USENIX Security, TheWebConf and IEEE S&P. Crawled population 1,120. These are the post-2026-08-11 denominators; no figure was carried over from any earlier page or dossier.
- This is a creation, not an extension. See Why this page, and not a section of a neighbour.
- Full text read directly (not only through the extraction): all nine papers that ran Foxhound, the two citation-only papers, the two homographs, and the four lineage papers — seventeen
paper.cols.txtfiles. Nothing was written to the publication mount.
Why this page, and not a section of a neighbour
programming:crawler already carries a Foxhound row in its specialised-crawler table and explicitly promises this child page; privacy:javascript names Project Foxhound in its tool table, its methods table and its Related-pages list, and promises the same child. The sitemap before this run listed programming:crawler:foxhound as promised but missing, linked from both.
So the judgement was easy on whether to create, and harder on scope. Three candidate scopes were considered:
- A “taint tracking” page covering Foxhound, PanoptiChrome and the interpreter-level tools together. Rejected: the wiki's per-tool namespace is
programming:crawler:<tool>,programming:crawler:panoptichromeis separately promised fromprogramming:crawler, and a method page would duplicate the comparisonprivacy:javascriptalready makes. The lineage and alternatives sections carry what a reader needs about the wider method without claiming that scope. - A narrow tool page — install, flags, output format. Rejected as too thin to be worth a reader's time; the repository's own wiki does that better.
- What was written: what the instrument reports, what it cannot see, what a paper must report about it, and where it sits against PageGraph and filter lists. The task's brief named “what taint flows it reports, the build burden, and what it enables that PageGraph does not”, and those are the three load-bearing sections.
One deliberate overlap: the PETS 2026 taint-versus-syntactic-matching result also appears on privacy:requests. It is used here for a different purpose — as evidence about the instrument's own false-positive and false-negative behaviour rather than about filter lists — and the numbers were re-derived from the paper rather than copied from that page.
Population and queries
Every count is a count of papers. Sentinels are not involved: the population is defined by a named tool, not by a field that can be not-stated.
| Query | Denominator | Result |
|---|---|---|
/fox ?hound/i over data/fulltext/<year>/<venue>/<slug>/paper.cols.txt | 5,859 corpus papers | 13 papers |
tools[].name or otherToolsMentioned[].name matching the same regex | 5,859 | 9 papers, 9 tuples |
Sweep papers whose hand verdict is homograph | 13 | 2 (excluded from every published figure) |
Sweep papers whose hand verdict is citation (cited, not run) | 13 | 2 |
Papers that ran the browser (used + extended + baseline) | 13 | 9 |
| Same 9, as a share of the corpus | 5,859 | 0.2% |
| Same 9, as a share of papers that ran a crawl | 1,120 | 0.8% |
Of those 9, how many the schema also puts in POPULATIONS.crawled | 9 | 9 (none outside it) |
| Papers that ran it, 2010–2023 | 3,984 corpus papers in those years | 0 |
| Papers that used or extended it (excluding the 2 baseline-only) | 13 | 7 |
| Of those 7, how many state a Foxhound version or commit | 7 | 3 (42.9%) |
| Of all 9 that ran it, how many state a version or commit | 9 | 5 (55.6%) |
| Of the 9, how many have a listed project contributor as co-author | 9 | 6 (66.7%) |
| Taint browsers in the corpus that predate Foxhound, built by their own authors | web-platform papers with a /taint/i tool in the browser / program-analysis / infrastructure categories (7 tuples over 6 papers) | 4 kept |
The sweep is the upper bound and the schema is the lower one, and neither is the answer. The schema misses both citation-only papers, which are precisely the ones the page has to separate out; the sweep catches two papers that are not about the browser at all. Using either number alone would be wrong in a different direction, so the page reports 13 → 9 explicitly.
scripts/report_foxhound.mjs re-runs the sweep on every invocation and compares it with the hand map in scripts/fh_fold.mjs. If the sweep returns a paper the map does not cover, or the map names a paper the sweep no longer finds, the report prints FAILURE and every figure below it is to be treated as suspect. Currently both residues are 0.
Folding, and the residue
No name fold was needed for the population. The three spellings in the corpus are case variants of one token with no synonyms:
Raw names in the schema: "Foxhound" (6), "Project Foxhound" (2), "FoxHound" (1). Fold residue: 0
This is the opposite situation from design:crawling_location (MaxMind under 41 spellings) or privacy:requests (Disconnect under 28). Saying “the residue is empty” here is a statement about the token, not a claim of folding rigour, and the page says so.
Two hand classifications do the real work, both in fh_fold.mjs, both carrying the sentence they were decided on:
ROLE— a verdict per swept paper fromused/extended/baseline/citation/homograph. Merging these would produce a meaningless count: three of the nine papers rebuilt the browser, and two ran it only to be beaten by their own tool. The distinction betweenusedandextendedis what makes the page's central practical warning — check the pref list before you start — a measured observation rather than an opinion.VERSION_STATED— the Foxhound version or commit each paper names, read by hand. The report re-runs a regex sweep beside it and printsagree/DISAGREEper paper; all nine agree. The regex is deliberately kept as a cross-check rather than as the source, because it also matches unrelated “Firefox 1xx” sentences.
The taint-instrument context table (section F of the report) is the one place with a genuine ordered fold, and it prints its residue. First match wins across nine families, and every remaining tool string matching /taint/i is listed:
Family | Papers Project Foxhound — patched Firefox (in-engine) | 9 PanoptiChrome — patched Chromium (in-engine) | 2 ChromiumTaintTracking — patched Chromium (in-engine) | 0 Jalangi / JalangiTT — source instrumentation | 6 JSFlow — instrumented interpreter | 1 LinvailTaint — source instrumentation | 1 IF-Transpiler — source rewriting | 1 TaintDroid — Android, not a web instrument | 9 Android taint analysers — not web instruments | 68 Residue — tool strings matching /taint/i that no family claims: 21 distinct strings. "Taint tracker" (1 papers) "taint-aware Firefox engine" (1 papers) "static taint analysis engine (custom)" (1 papers) "Multiple Taint-Style Vulnerabilities Detector (MTS-VD)" (1 papers) "static taint analysis tool" (1 papers) "custom taint-aware infrastructure" (1 papers) "taint tracking engine" (1 papers) "TAINT INDUCE" (1 papers) "Taint'em All" (1 papers) "Taintless" (1 papers) "Taintgrind" (1 papers) "TaintGrep" (1 papers) "OctopusTaint" (1 papers) "TaintScope" (1 papers) "ParcelTaint" (1 papers) "DeFiTainter" (1 papers) "E M TAINT" (1 papers) "O CTOPUS TAINT" (1 papers) "LuaTaint" (1 papers) "file_taint" (1 papers) "EmTaint" (1 papers)
Reading that residue is what produced the lineage table, which is the finding of this run I would least have predicted. Four of the unmapped strings — “taint-aware Firefox engine”, “custom taint-aware infrastructure”, “taint tracking engine”, “Taint tracker” — are papers whose own contribution was a taint browser, under no product name at all. Without them the corpus reads as if in-browser taint tracking began in 2024, which is false and would have made the page's currency claim badly wrong. The page's four-row lineage table is the correction. “E M TAINT” / “EmTaint” and “O CTOPUS TAINT” / “OctopusTaint” are the same tools twice, split by small-caps rendering in the PDF; they are binary-analysis tools and off-topic either way, and are left in the residue rather than folded, because folding them would imply the residue had been curated.
The Android taint analysers family at 68 papers is deliberately in the table and deliberately labelled “not web instruments”. It is there so a reader cannot mistake the small web numbers for a claim that taint analysis is rare in general.
Quotes and figures checked
Three separate checks, all in the report:
- Schema evidence quotes (section G): the 9
tools[].evidence.quotevalues, normalised for whitespace and line-break hyphens, then by five-word windows — 5 exact, 4 partial (≥60% of windows), 0 below threshold, 0 without full text. The four partials are column splices in the repaired rendering, not unsupported claims. Cross-checked with the shared bulk verifier, which is a separate implementation:node scripts/quote_check.mjs –tools 'fox.?hound' –showreturns the identical 5/4/0/0. - Hand deciding quotes (also section G): the 13 sentences in the
ROLEmap — 13 exact, 0 not exact, as the report says. Two of them are exact only because they were shortened during this run to a contiguous fragment: the CCS 2025 extension sentence and the NDSS 2026 baseline sentence are each spliced across two columns inpaper.cols.txt, and the longer versions I first wrote matched at 50% and 60%. Both are marked with an inline comment infh_fold.mjssaying why they are short, so a later reader does not “restore” them. (An earlier draft of this page said “11 exact, 2 not”, describing the state before the shortening; the citations reviewer caught the mismatch against the artefact.) - Per-paper figures and quoted sentences used on the page (section H): 50 needles, 0 not located verbatim in
paper.cols.txt. This check exists becausedetection[].prevalenceis a model summary rather than a quotation — the same reasonverify_requests_figures.mjsexists. It started at 31 needles and grew to 50 on the figures reviewer's finding that the comparison table's per-tool numbers were disclosed as hand-checked but not machine-checked; adding them also caught three of my own needles that were column-spliced and would have read as failures.
A self-caught defect in the same check, worth recording because nothing would have flagged it: three needles were too short to mean anything. “38.5”, “13” and “55 event handlers” will match almost any paper's digits, so they were passing for the wrong reason — a false pass is worse than a failure, because it looks like verification. All three were replaced with the full clause from the source (“false negative rate of 38.5%”, “From each of the remaining 13 libraries we create a benchmark sample”, “a total of 55 event handlers”), and “9.6% of the top 10K” was split into two needles because the paper states the share and the flow count in different sentences. The rule this suggests for the next page: audit the needle list for anything under about twenty characters before trusting the “0 not located” line.
One figure is quoted from the extraction with a formatting change: FP-tracer's 269 784 fingerprinting flows (thin space in the paper and in the extraction) is written 269,784 on the page. That is a rendering choice, logged here so a future check does not read it as a mismatch.
External facts, and how each was verified
scripts/foxhound_probe.sh re-fetches every one of these and prints FAILED per check rather than failing quietly. Its unedited output is in Probe output. Nothing on the content page about the tool's current state comes from recall.
| Fact on the page | Primary source | How checked |
|---|---|---|
main is Firefox 142.0.1 | browser/config/version.txt on main | raw.githubusercontent fetch |
| Playwright pin v1.58.2, Rust pin 1.86.0 | .PLAYWRIGHT_VERSION on main | raw fetch, grep |
| Current stable Firefox 153.0.4 | product-details.mozilla.org/1.0/firefox_versions.json | JSON fetch |
| Playwright's current release v1.62.1, baselined on Firefox 153.0; v1.58's baseline is 142.0.1 | browser_patches/firefox/UPSTREAM_CONFIG.sh in microsoft/playwright, then version.txt at that Firefox revision | two-hop fetch, so the version is derived from the pinned revision rather than assumed |
34 sources, 61 sinks, 1 master switch, all default true | modules/libpref/init/all.js on main | grep and count; both full name lists printed |
taintfox.escapeURL defaults to false on the main thread, true on workers, and is not declared in all.js | netwerk/base/nsStandardURL.cpp line 116, plus issues #259 (closed 2025-02-14, where the maintainer documents the pref and the legacy-IE reason) and #260 (open, asking for it to be renamed and added to the default config) | the probe prints the call site with context, counts taintfox occurrences in all.js (0), and prints both issue states. Found only because the generic reviewer pointed out the page demanded a setting it never named |
The Ubuntu and Windows mozconfigs set --disable-sandbox, --without-wasm-sandboxed-libraries and --disable-crashreporter; the macOS one does not; there is no unsuffixed taintfox_mozconfig despite the README's cp command | taintfox_mozconfig_{ubuntu,win,mac} and the repository contents listing | the probe prints every ac_add_options/mk_add_options line of all three and asserts on the missing file. The first draft called the build “debug-instrumented”, which was wrong; checking it produced a better fact |
“URLSearchParams.get” trace operation absent at v130.0, present from v140.0.2 | netwerk/base/nsURLHelper.cpp per release tag, plus the PR #350 diff | four-tag check in the probe. The diff shows MarkTaintOperation called after an assignment that already carries taint, which is what limits the page's claim to the trace, not the tracking |
| Pref history: none up to v115, 85 at v118.0.1, 96 (34/61) from v119.0 | all.js at each of 15 release tags | per-tag fetch and count, plus a per-tag test for the document.querySelector source pref |
| DOM-read sources landed in PR #198 (merged 2024-02-08); URL taint operations in PR #350 (merged 2025-11-26) | GitHub pulls API, including the changed-file lists | API fetch; #350 touches dom/tainting/nsTaintingUtils.cpp and dom/url/URL.cpp, which is what limits the claim to flow-trace operations, not taint propagation |
| 17 releases, only v128.0 and v140.0.2 carry binary assets | GitHub releases API | API fetch, asset names printed |
| Build times 81.3 / 134.1 / 148.1 min plus 34.2 + 7.2 min of tests | GitHub Actions API, browser.yml run id 26110426051 (2026-05-19) | per-job and per-step timings computed from started_at/completed_at |
browser.yml and playwright.yml currently fail on main at 7ce49d32 (2026-07-24) in Bootstrap / Configure, while main.yml passes | GitHub Actions API | newest run per workflow on main, then its failing steps |
| 10 GB swap, 4–10 GB pagefile, disk purge | .github/workflows/browser.yml | grep of the workflow's own resource steps |
| 6.1 GiB checkout, GPL-3.0, repo created 2021-06-04 | GitHub repos API | API fetch |
TU Braunschweig hosts 31 per-commit Linux builds under Debian Bookworm, newest 1bb8dbd6 (2026-05-19), with SHA-256 checksums and the matching Playwright version | foxhound.ias.tu-bs.de | HTML fetch with a browser User-Agent, then regex extraction of commits, dates and Playwright versions |
report.detail field list; the worked taint-flow JSON; “one release behind Playwright”; the gkrust OOM; the Playwright patch workflow constraints; the git-history resync | the project wiki, cloned as a git repository (project-foxhound.wiki.git) and the README | git clone –depth 1, then read; the probe asserts nine specific README substrings still exist |
| The JIT / best-effort caveat | taint/docs/TaintFlow.md on main | raw fetch, and the probe greps the two sentences |
| Klein et al., EuroS&P 2022 is the requested citation | README Cite us! section | probe asserts the BibTeX key KleBarBen is still present |
| Satellite tools alive: taint-flow extension, ZAP add-on; PanoptiChrome public | GitHub repos API for all three | API fetch: archived flag, push date, licence |
| 14 publications on the project's Hall of Fame | wiki Publications.md | count of list items in the cloned wiki |
| All 14 external URLs on the page return 200 | curl with a browser User-Agent | probe prints the status per URL and marks anything other than 200 |
Sources considered and rejected:
taint/README.md's source and sink lists. Rejected as the page's source of truth, and the disagreement is now a footnote: it markselement.getAttribute,element.innerHTMLandelement.outerHTMLas struck-through/disabled sources, while the pref for element attributes has been on since v119.0. The repository documentation is stale relative to the prefs; the prefs are the artefact that decides behaviour.- The wiki's
Source-and-Sink-Listingspage. Used as a reading aid, rejected as a count. Its lists are produced by agrepover the tree and contain the grep's own artefacts — the literalReportTaintSinkand a truncatednavigator.sendBeacon(url)n— so counting its lines gives 26 sources and 51 sinks, neither of which is the real surface. This is exactly the kind of “authoritative-looking list” that would have put two wrong numbers on the page. - Any secondary write-up, blog post or listicle about Foxhound. None used. Every external claim traces to the repository, its wiki, an API, or a paper.
pagegraph-side facts were not re-derived here; the PageGraph comparison rows point at pagegraph and its own provenance page rather than restating its verification.
The published code
foxhound_flows.py on the content page is not a wrapper around a browser: it reduces already-collected taint reports. The first version of it had two real bugs, both found by the generic reviewer and both visible in its own self-test — see findings 1 and 2 in the Review log — and a third, found afterwards by writing an adversarial stress harness against the rewritten version: script_of required a scripthash and therefore threw away the filename, so the documented --unit script fallback to a filename never fired and every unhashed flow collapsed into one empty key. Fixed, with a regression test, plus tests for an empty flow and for the encode/decode ordering. The stress harness also confirmed one limitation that cannot be fixed by a name heuristic and is now documented in the code: a page function called something like reportTaintSinkStats would be misclassified as harness code, which is why --harness exists. That is the strongest argument on this page for publishing code with a self-test rather than a snippet: the fixture disagreed with the prose, and it was checkable. That choice was deliberate — a script that needs a Foxhound binary could not have been tested in this run, and an untested script on the page would violate the task's own rule. As written it is testable, and its self-test is the fixture from the project's documented worked example plus five mutations of it:
$ python3 pages/foxhound_flows.py --selftest selftest: 25 checks passed flows: 1 sites: 1 pages: 1 scripts: 1 dropped as encoded at the sink: 0 (report this number whichever way you decide) tainted substring held no HTML/JS syntax character at crawl time: 1 of 1 — a property of your input, NOT a safe verdict flows with no recorded application function call (JIT-blind): 1 of 1 = 100% flows observed in a subframe: 0 (a landing-page-only crawl still sees these; say so) source sink sites flows jit-blind location.hash innerHTML 1 1 1
The copy embedded in the page's <file python> block was byte-compared against pages/foxhound_flows.py before publication (21,889 bytes each, identical), because a published script that has drifted from the one that was tested is worse than no script:
python3 - <<'EOF'
OPEN = '<' + 'file python foxhound_flows.py>' # split so this snippet can live
CLOSE = '<' + '/file>' # inside a wiki page unharmed
page = open('pages/programming_crawler_foxhound.txt').read()
start = page.index(OPEN) + len(OPEN) + 1
embedded = page[start:page.index(CLOSE, start)]
print('identical:', embedded.rstrip('\n') == open('pages/foxhound_flows.py').read().rstrip('\n'))
EOF
The four checks that justify the file existing are: the source is the last source-flagged node (flow is ordered sink-first, and reading it forwards mislabels every row); a chain with no application function node is marked JIT-blind, with the reporting harness's own call excluded, rather than treated as “no sanitiser was called”; encoded_at_sink (a property of the code) is kept apart from no_syntax_chars (a property of the input you sent), and only the former can drop a row; and the unit is chosen explicitly, so the same pair on two pages of one site is 1 site and 2 pages. Neither flag is a verdict, and the page says so.
What could not be established
- No throughput benchmark exists. Nothing in the corpus or the project's material gives pages-per-hour, memory per instance, or a crash rate for Foxhound on a modern population. Calzavara et al.'s 1.4× is an analysis-time ratio on landing pages of sites where the tool was already compatible and transparent — it is not a crawl-rate measurement, and the page refuses to present it as one. The page's advice is therefore to pilot, which is weaker than a figure and is the honest answer.
- How often the JIT blind spot bites, in practice. The project documents it; nobody has measured what share of real flows lose their function-call nodes. The published script counts it so a future paper can report it, but this page cannot say whether the number is 5% or 60%.
- Whether the 2024 arrival is adoption or coincidence. Nine papers over three years, six of them with a maintainer as co-author, is a diffusion signal too small to test. A per-year trend on nine papers would be noise, and none is drawn.
- The 2025–2026 slice is the provisional one. Five of the nine papers are in it. That is a real weakness of the currency claim on this page, and it is why the page leans on the pref history and the CI state — facts with dates attached that do not depend on the corpus edge — rather than on the year counts.
- EuroS&P is not in the corpus, and it is where the browser is described and where the earlier work in this line was published. Every count here is a lower bound and the page names the project's own 14-entry list as the demonstration.
- Not attempted: building it. A Foxhound build is an hours-long, tens-of-gigabytes operation and no binary for this container's platform was worth fetching for a documentation page. Everything about the build is therefore the project's CI and documentation, not our own experience, and the page attributes it that way. The alternative — quoting a build time we had not measured, unattributed — is the failure mode this note exists to prevent.
- Deferred, not done: the errata on the neighbours.
programming:crawlerandprivacy:javascriptboth publish 8 Foxhound papers where this page's audit finds 9, because they count schema tuples and the extraction files Khodayari et al.'s NDSS 2025 paper ascompared. This page carries a reconciling footnote; the edits to those two tables are queued as a separate work item rather than made here, because changing another page's central table without re-running its own report script is how stale figures get created. - Not attempted: the taint reports themselves. No Foxhound binary was run, so no real
__taintreportpayload was captured. The field list and the worked example are the project's own documentation. If a later run does obtain a binary, the first thing worth checking is whetherstackis ever non-empty, since the documented example shows{}and the page currently warns against designing around it.
Judgement calls
| Call | Decision and why |
|---|---|
Create a per-tool page rather than widen privacy:javascript | Both neighbours promise this exact page id; see Why this page, and not a section of a neighbour |
| Count 9 papers, not 13 and not 11 | The role verdicts are published in full, so a reader who disagrees can recount. Reporting 13 would count a dog breed; reporting 11 would count two papers that never ran it, one of which rejected it |
| Publish the two homographs | Named on the provenance page and in the report, not on the content page. An exclusion nobody can see is indistinguishable from a mistake |
| Call the pre-2024 taint browsers “did not outlive their paper” | Grounded in the corpus: no paper outside each engine's own author lineage names it as a reused instrument, and a 2024 paper says in terms that it chose Foxhound over the 2019 Chromium tracker for modern-JS support. Stated as a claim about this corpus, not about the world |
| Report the maintenance lag as four Playwright releases, not “one” | The project documents an intent to be one release behind; the measured state is v1.58.2 against v1.62.1. The page gives both, because the intent explains the design and the measurement is what a reader will hit |
| Do not call the earlier tools “superseded” | They are not maintained and not reused, which is a weaker and checkable claim. “Superseded” would imply a comparison nobody in the corpus has run |
| Keep the PETS 2026 numbers on two pages | Different argument on each; re-derived rather than copied. Noted above as a deliberate overlap |
| Present the WWW 2025 bake-off as the currency evidence, with three caveats attached | It is the only head-to-head in the corpus and it is unambiguous, but it is one study, one 2024 landing-page crawl, and one of its authors is a listed project contributor. All three are on the page beside the table |
Report script and its output
Committed as scripts/report_foxhound.mjs with scripts/fh_fold.mjs; re-run with
cd /workspace/artifacts/wiki node scripts/report_foxhound.mjs > scripts/report_foxhound-output.txt node scripts/report_foxhound.mjs --wiki # DokuWiki tables node scripts/report_foxhound.mjs --hits 'srcdoc' # full-text context over the population bash scripts/foxhound_probe.sh > out/foxhound_probe.txt node scripts/check_tables.mjs pages/programming_crawler_foxhound.txt node scripts/check_page_numbers.mjs pages/programming_crawler_foxhound.txt scripts/report_foxhound-output.txt --code node scripts/check_attributions.mjs pages/programming_crawler_foxhound.txt out/bib_merged_foxhound.txt
check_page_numbers.mjs passes both windowed and whole-page, and with –code. Getting it to pass required a Z2 block in the report listing every literal on the page whose source is not the corpus — the build minutes, the version pins, the pref counts, the per-paper figures from cited papers, and the tokenizer artefacts (22.04 yielding 2.0; the line numbers inside the fixture JSON). That block is the audit trail for every number the corpus cannot account for, and it is why the guard is a guard rather than a formality.
check_attributions.mjs reported “0 attributions checked” against this page on first run, because its regex required the crawler-page style | Name et al., VENUE YEAR, //Title// {[key]} | and this page's tables omit the title. The regex was generalised to accept either form; both this page (9 attributions) and programming:crawler:pagegraph (8) pass afterwards. “0 checked” is not a pass — the guard says so itself, and this is the second page on which that message was the actual finding.
Report output
- report_foxhound-output.txt
Corpus: 5859 extracted papers from CCS, IMC, NDSS, PETS, USENIX Security, TheWebConf and IEEE S&P, 2010-2026. Crawled population: 1120 papers (19.1%), defined by crawlConfig != null or studyTypes includes automated-web-crawl. Run date: figures below are recomputed on every run; external facts are in scripts/foxhound_probe.sh. === B. Population: full-text sweep versus schema, and the role verdicts === Sweep: /fox ?hound/i over data/fulltext/<year>/<venue>/<slug>/paper.cols.txt -> 13 papers. Schema: tools[].name or otherToolsMentioned[].name matching the same regex -> 9 papers, 9 tuples. Raw names in the schema: "Foxhound" (6), "Project Foxhound" (2), "FoxHound" (1). No name fold is needed: every string is a case variant of one token. There are no synonyms and therefore no residue. Hand role verdicts (fh_fold.mjs ROLE), one per swept paper: key | role | in schema | what it did CCS/2025/in-the-dom-we-trust-exploring-the-hidden-dangers-of-reading-from-the-dom-on-the | extended | yes | made every DOM read a taint source, to measure DOM-gadget data flows IEEE-SP/2024/a-picture-is-worth-500-labels-a-case-study-of-demographic-disparities-in-local-m | homograph | no | "americanfoxhound" — an ImageNet class label in a list of image categories IEEE-SP/2024/the-great-request-robbery-an-empirical-study-of-client-side-request-hijacking-vu | extended | yes | added request-API instrumentation ("Foxhound+") and fed the flows into a hybrid property graph IEEE-SP/2024/to-auth-or-not-to-auth-a-comparative-analysis-of-the-pre-and-post-login-security | used | yes | collected taint reports pre- and post-login, then generated XSS exploits from them NDSS/2025/do-not-follow-the-white-rabbit-challenging-the-myth-of-harmless-open-redirection | baseline | yes | ran it as one of six baseline vulnerability detectors on 42K pages NDSS/2026/dom-xss-detection-via-webpage-interaction-fuzzing-and-url-component-synthesis | baseline | yes | ran two configurations of it as the prior-work baseline, and showed the URL-encoding setting quintuples the count PETS/2024/fp-tracer-fine-grained-browser-fingerprinting-detection-via-taint-tracking-and-e | extended | yes | extended it with numeric tainting and a larger source/sink set (FP-tracer) PETS/2026/from-syntactic-matching-to-taint-tracking-and-back-a-comparative-study-of-web-tr | used | yes | used it as the taint-tracking arm of a head-to-head against filter-list-style syntactic matching USENIX/2024/dancer-in-the-dark-synthesizing-and-evaluating-polyglots-for-blind-cross-site-sc | used | yes | used it as the client-side-XSS baseline that the synthesized polyglots were measured against USENIX/2025/the-domino-effect-detecting-and-exploiting-dom-clobbering-gadgets-via-concolic-e | citation | no | cited it in related work and rejected it as unsuitable, because it tracks only strings WWW/2024/panoptichrome-a-modern-in-browser-taint-analysis-framework | citation | no | cited it as one row of a related-work comparison table for their own Chromium taint tracker WWW/2025/dynamic-security-analysis-of-javascript-are-we-there-yet | used | yes | benchmarked it against seven other JavaScript information-flow tools on live sites WWW/2025/query-design-for-crowdsourced-clustering-effect-of-cognitive-overload-and-contex | homograph | no | "English Foxhound" — a dog breed used as an example crowdsourcing label Sweep papers with no hand verdict: 0 Hand verdicts with no sweep hit: 0 Role tally: baseline: 2 citation: 2 extended: 3 homograph: 2 used: 4 RAN-IT population (used + extended + baseline): 9 papers. as a share of the 5859-paper corpus: 0.2% as a share of the 1120 papers that ran a crawl: 0.8% of those 9, how many the schema also puts in the crawled population: 9 (0 not: none) The two homograph papers are excluded from every figure on the page. They are listed above so the exclusion is visible rather than silent. === C. Papers that ran Foxhound, by year === The denominator is that year's whole corpus, not the crawled subset, so the share is comparable with the other tool pages. Year | Corpus papers | Ran Foxhound | Share | Provisional 2010 | 119 | 0 | 0.0% | no 2011 | 116 | 0 | 0.0% | no 2012 | 151 | 0 | 0.0% | no 2013 | 125 | 0 | 0.0% | no 2014 | 166 | 0 | 0.0% | no 2015 | 190 | 0 | 0.0% | no 2016 | 182 | 0 | 0.0% | no 2017 | 231 | 0 | 0.0% | no 2018 | 254 | 0 | 0.0% | no 2019 | 402 | 0 | 0.0% | no 2020 | 404 | 0 | 0.0% | no 2021 | 379 | 0 | 0.0% | no 2022 | 546 | 0 | 0.0% | no 2023 | 719 | 0 | 0.0% | no 2024 | 690 | 4 | 0.6% | no 2025 | 770 | 3 | 0.4% | yes 2026 | 415 | 2 | 0.5% | yes Aggregate row used on the page: 2010-2023 = 3984 corpus papers, 0 ran Foxhound (0.0%). That is 14 consecutive zero years. Same table for the sweep population including citation-only papers, for contrast: Year | Named anywhere in full text (excl. homographs) 2024 | 5 2025 | 4 2026 | 2 === D. Does the paper say which Foxhound it ran? === Population: the papers that ran it. A version or commit is the only way a reader can know which source/sink set and which propagation bugs were in play. key | role | version stated CCS/2025/in-the-dom-we-trust-exploring-the-hidden-dangers-of-reading-from-the-dom-on-the | extended | NONE IEEE-SP/2024/the-great-request-robbery-an-empirical-study-of-client-side-request-hijacking-vu | extended | v98.0.2 IEEE-SP/2024/to-auth-or-not-to-auth-a-comparative-analysis-of-the-pre-and-post-login-security | used | commit 2916e01 (Firefox 109 fork) NDSS/2025/do-not-follow-the-white-rabbit-challenging-the-myth-of-harmless-open-redirection | baseline | v98.0.2 NDSS/2026/dom-xss-detection-via-webpage-interaction-fuzzing-and-url-component-synthesis | baseline | v126.0 PETS/2024/fp-tracer-fine-grained-browser-fingerprinting-detection-via-taint-tracking-and-e | extended | v96.0.3 PETS/2026/from-syntactic-matching-to-taint-tracking-and-back-a-comparative-study-of-web-tr | used | NONE USENIX/2024/dancer-in-the-dark-synthesizing-and-evaluating-polyglots-for-blind-cross-site-sc | used | NONE WWW/2025/dynamic-security-analysis-of-javascript-are-we-there-yet | used | NONE Stated: 5 of 9 (55.6%). Restricted to the 7 papers that used or extended it (excluding the 2 that ran it only as a baseline): 3 state a version (42.9%). Broken down by role (computed, not asserted — an earlier hand-written sentence here claimed both extension papers state a version, which is false: Drescher et al. do not): baseline: 2 of 2 state a version extended: 2 of 3 state a version used: 1 of 4 state a version The sweep that produced the hand map (re-run here, so a new edition of the corpus cannot silently change it): CCS/2025/in-the-dom-we-trust-exploring-the-hidden-dangers-of-reading-from-the-dom-on-the | regex: no match | hand: NONE | agree IEEE-SP/2024/the-great-request-robbery-an-empirical-study-of-client-side-request-hijacking-vu | regex: "Firefox known as Foxhound (v98.0.2" | hand: v98.0.2 | agree IEEE-SP/2024/to-auth-or-not-to-auth-a-comparative-analysis-of-the-pre-and-post-login-security | regex: "Foxhound browser commit 2916e01" | hand: commit 2916e01 (Firefox 109 fork) | agree NDSS/2025/do-not-follow-the-white-rabbit-challenging-the-myth-of-harmless-open-redirection | regex: "Firefox (v98.0.2" | hand: v98.0.2 | agree NDSS/2026/dom-xss-detection-via-webpage-interaction-fuzzing-and-url-component-synthesis | regex: "FoxHound v126.0" | hand: v126.0 | agree PETS/2024/fp-tracer-fine-grained-browser-fingerprinting-detection-via-taint-tracking-and-e | regex: "Foxhound [50] (v96.0.3" | hand: v96.0.3 | agree PETS/2026/from-syntactic-matching-to-taint-tracking-and-back-a-comparative-study-of-web-tr | regex: no match | hand: NONE | agree USENIX/2024/dancer-in-the-dark-synthesizing-and-evaluating-polyglots-for-blind-cross-site-sc | regex: no match | hand: NONE | agree WWW/2025/dynamic-security-analysis-of-javascript-are-we-there-yet | regex: no match | hand: NONE | agree The regex is an upper bound (it also matches an unrelated "Firefox 1xx" sentence), which is why the hand column decides. === E. Measured results, with each paper's own denominator === Straight from detection[].prevalence for the papers that ran it. metric names the unit; the denominator is from population[] in the same record. CCS/2025/in-the-dom-we-trust-exploring-the-hidden-dangers-of-reading-from-the-dom-on-the — In the DOM We Trust: Exploring the Hidden Dangers of Reading from the DOM on the Web. population: 15000 domains from "Tranco list (ID: W88P9)" (top-n), listVersion="W88P9" population: 522860 web-pages from "custom seed list" (seed-and-crawl), listVersion=null "DOM-to-sink data flows" | verified flow count and affected sites | 357,982 verified gadgets across 14,345 webpages and 2,259 sites "Markup injection vulnerabilities" | verified dataflow count | 4,722 verified dataflows across 34,223 webpages "DOM-gadget exploitability" | end-to-end verified flows and sites | 657 flows across 37 sites "Missing sanitization or validation" | share of static flows without relevant patterns | 10.38% contained no sanitization or validation patterns "DOM selector complexity" | mean and median complexity | average 1.80 and median 2 "Element-order exploitation requirement" | share requiring reordering techniques | 34% of 253K combinations had injected markup after the selected element "Detection false negatives" | false negative rate | 38.5% IEEE-SP/2024/the-great-request-robbery-an-empirical-study-of-client-side-request-hijacking-vu — The Great Request Robbery: An Empirical Study of Client-side Request Hijacking Vulnerabilities on the Web. population: 10000 websites from "Tranco" (top-n), listVersion="downloaded September 29, 2022; ID N7QWW" "client-side request hijacking data flows" | verified vulnerable data flows, affected webpages and sites | 202,834 verified flows affecting 17,805 webpages and 961 sites; 9.6% of the top 10K sites "request-sending API usage" | API calls, webpages and domains | Approximately 7.9M API calls across 1,032,795 webpages and 9,901 domains "request hijacking exploitability" | proof-of-concept exploits and affected websites | 67 proof-of-concept exploits across 49 websites "defense adoption and coverage" | pages and sites adopting defenses; mitigated flow share | CSP mitigated information leakage and XSS in 58.7% of flows; 7.6% of webpages deployed the relevant CSP directive IEEE-SP/2024/to-auth-or-not-to-auth-a-comparative-analysis-of-the-pre-and-post-login-security — To Auth or Not To Auth? A Comparative Analysis of the Pre- and Post-Login Security Landscape. population: 445 websites from "Tranco" (top-n), listVersion="ID N76GW, July 4, 2023" population: 5000 websites from "CrUX" (top-n), listVersion="Top 5K, April 2023" population: 4485 websites from "Tranco and CrUX" (purposive), listVersion=null population: 200 websites from "Tranco and CrUX" (purposive), listVersion=null "client-side XSS" | vulnerable sites and verified exploits | 7 unique vulnerable sites; 6 persistent and 1 reflected "security-header deployment" | site-level secure, insecure, or missing headers | X-Frame-Options appeared on average in 56% of responses "security-header inconsistency" | share of URLs with consistent headers | over 99% of URLs had consistent headers "JavaScript inclusions" | parsed scripts and unique scripts | 42,437,161 scripts parsed "vulnerable JavaScript libraries" | sites containing vulnerable libraries | 143 authenticated and 145 unauthenticated sites "third-party scripts and tracking" | unique third-party scripts and trackers | 15.47% more unique tracking entities in Sauth "postMessage handlers" | AST-unique handlers | 899 authenticated versus 493 unauthenticated AST-unique handlers "postMessage vulnerabilities" | verified exploitable handlers | 2 exploits affecting one authenticated-state site NDSS/2025/do-not-follow-the-white-rabbit-challenging-the-myth-of-harmless-open-redirection — Do (Not) Follow the White Rabbit: Challenging the Myth of Harmless Open Redirection population: 10000 websites from "Tranco" (top-n), listVersion="ID N7QWW, downloaded Sept. 29, 2022" population: 1034521 web-pages from "Tranco" (top-n), listVersion="ID N7QWW, collected October 2022" population: 42288 web-pages from "P2 dataset" (random), listVersion=null population: 4000 websites from "candidate sites matching indicators" (random), listVersion=null "open redirect vulnerabilities" | number of confirmed vulnerabilities and affected websites | 20,898 confirmed open redirections across 623 websites "open redirect prevalence" | share of top-10K websites | approximately 8.7% of the top 10K websites "archived open redirects" | confirmed vulnerabilities and affected websites | 375 vulnerabilities across 326 websites "DOM-based XSS escalation" | share of vulnerabilities and affected sites | about 9% of vulnerabilities across 33.2% of affected sites "client-side CSRF escalation" | number and share of open redirects | 42 vulnerabilities, over 2.4% of open redirects "information leakage escalation" | number and share of open redirects | 3 vulnerabilities, about 0.2% of open redirects "redirect mitigations" | share of audited sites | six mitigation types; redirect notices used by 54.4% "indicator false negatives" | false-negative rate | 76% for indicators compared with static analysis NDSS/2026/dom-xss-detection-via-webpage-interaction-fuzzing-and-url-component-synthesis — DOM-XSS Detection via Webpage Interaction Fuzzing and URL Component Synthesis population: 30000 domains from "Tranco" (top-n), listVersion="generated 04 March 2025" population: 44480 web-pages from "Tranco" (seed-and-crawl), listVersion="generated 04 March 2025" population: 13555 web-pages from "custom seed list" (random), listVersion=null population: 194 web-pages from "custom seed list" (exhaustive), listVersion=null "DOM-XSS vulnerabilities" | unique confirmed vulnerable flows and pages | 114 unique DOM-XSS vulnerable flows in 146 pages "Interaction-triggered DOM-XSS" | confirmed-flow increase over passive analysis | 15% more confirmed flows than Passive "URL-parameter and fragment-triggered DOM-XSS" | new confirmed vulnerabilities | 20 new vulnerabilities "Synthesized GET parameters" | overlap with ffuf/wfuzz wordlists | 95.6% of DSE-synthesized keys absent from those wordlists PETS/2024/fp-tracer-fine-grained-browser-fingerprinting-detection-via-taint-tracking-and-e — FP-tracer: Fine-grained Browser Fingerprinting Detection via Taint-tracking and Entropy-based Thresholds population: 100000 domains from "Tranco" (top-n), listVersion="N7QVW" population: 85576 other from "Ad Fingerprinting dataset from a related research study [14]" (pre-existing-dataset), listVersion=null "browser fingerprinting flows" | number of fingerprinting flows and attribute combinations | 269 784 fingerprinting flows and 15 239 unique browser attribute combinations "entropy-based fingerprinting severity" | share of crawled domains per entropy category | Very High 8.08%, High 61.49%, Medium 30.94%, Low 58.99% "audio fingerprinting" | number of crawled domains | 54 crawled domains "canvas fingerprinting" | number of crawled domains | 6237 crawled domains "storage fingerprinting" | number of domains | storageEstimate.quota detected in 148 domains "fingerprinting obfuscation" | share of transmitted attributes obfuscated | 46% of transmitted attributes in the most severe category "collaborative fingerprinting" | share of fingerprinting involving multiple domains | 38% of fingerprinters involve two or more domains "consent-banner impact" | domain prevalence by entropy category | Accepting all data collection increased activity to almost 90% of domains PETS/2026/from-syntactic-matching-to-taint-tracking-and-back-a-comparative-study-of-web-tr — From Syntactic Matching to Taint Tracking and Back: A Comparative Study of Web Tracking Detection Techniques population: 10000 websites from "Tranco" (top-n), listVersion="generated February 8, 2026" "stateful tracking requests" | number of tracking requests | 40,605 total; 33,584 detected by syntactic matching "stateful tracking requests" | number of tracking requests | 23,109 detected by taint tracking "syntactic-matching false positives" | false-positive rate | 16%-19% overall; 27%-30% for syntactic-only requests "syntactic-matching false negatives" | share of dataset | 7,021 requests, around 17%, detected by taint tracking alone USENIX/2024/dancer-in-the-dark-synthesizing-and-evaluating-polyglots-for-blind-cross-site-sc — Dancer in the Dark: Synthesizing and Evaluating Polyglots for Blind Cross-Site Scripting population: 10000 domains from "Tranco" (top-n), listVersion="W95V9, Dec. 15, 2022" population: 100000 domains from "Tranco" (top-n), listVersion="824JV, Oct. 9, 2022" population: 111 other from "Google Firing Range" (purposive), listVersion="0.48" "XSS polyglot coverage" | number of injection contexts solved | Seven polyglots covered all 111 selected GFR test cases. "client-side XSS" | validated vulnerabilities | 147 vulnerabilities triggered by synthesized polyglots versus 145 by precise payload generation. "blind XSS" | vulnerabilities and affected websites | 20 vulnerabilities on 18 websites. "blind XSS" | share of backends by submission type | Headers triggered 10 vulnerabilities, URLs 9, and forms 1. "crawler page failures" | share of visited pages failing | Approximately 7.4% of 1,676,812 visited pages failed to load. WWW/2025/dynamic-security-analysis-of-javascript-are-we-there-yet — Dynamic Security Analysis of JavaScript: Are We There Yet? population: 6921 domains from "Tranco" (top-n), listVersion="27 September 2024" "JavaScript compatibility" | percentage of scripts compatible | Project Foxhound successfully analyzed 95% of collected scripts "Transparency violations" | transparency rate | Project Foxhound was transparent on 97% of websites "Information flows" | number of detected flows and estimated true positives | Project Foxhound detected 919 flows; 94% met at least one agreement heuristic "Runtime overhead" | average overhead ratio | PanoptiChrome 36.7x, LinvailTaint 23.9x, JalangiTT 8.6x, Project Foxhound 1.4x "JavaScript language features" | feature frequency among analyzed scripts | Block scoping 25% and arrow functions 24% === F. Other in-browser taint trackers named in the corpus === Counts papers over tools[] and otherToolsMentioned[]. Ordered families from fh_fold.mjs TAINT_TOOLS, first match wins; every remaining tool string matching /taint/i is printed as residue. Family | Papers Project Foxhound — patched Firefox (in-engine) | 9 PanoptiChrome — patched Chromium (in-engine) | 2 ChromiumTaintTracking — patched Chromium (in-engine) | 0 Jalangi / JalangiTT — source instrumentation | 6 JSFlow — instrumented interpreter | 1 LinvailTaint — source instrumentation | 1 IF-Transpiler — source rewriting | 1 TaintDroid — Android, not a web instrument | 9 Android taint analysers — not web instruments | 68 Residue — tool strings matching /taint/i that no family claims: 21 distinct strings. "Taint tracker" (1 papers) "taint-aware Firefox engine" (1 papers) "static taint analysis engine (custom)" (1 papers) "Multiple Taint-Style Vulnerabilities Detector (MTS-VD)" (1 papers) "static taint analysis tool" (1 papers) "custom taint-aware infrastructure" (1 papers) "taint tracking engine" (1 papers) "TAINT INDUCE" (1 papers) "Taint'em All" (1 papers) "Taintless" (1 papers) "Taintgrind" (1 papers) "TaintGrep" (1 papers) "OctopusTaint" (1 papers) "TaintScope" (1 papers) "ParcelTaint" (1 papers) "DeFiTainter" (1 papers) "E M TAINT" (1 papers) "O CTOPUS TAINT" (1 papers) "LuaTaint" (1 papers) "file_taint" (1 papers) "EmTaint" (1 papers) These are schema counts, not sweep counts, so they are lower bounds in exactly the way the Foxhound count is. The point the page makes from this table is the shape — one web instrument with most of the use, everything else in single digits — not the absolute numbers. === F2. Taint-tracking browsers in the corpus BEFORE Foxhound === Query: tools[] or otherToolsMentioned[] with /taint/i in the name and category in {browser, program-analysis, infrastructure}, on papers whose platforms include "web". Then the sentence was read and the paper kept only if the taint browser was its own contribution or its own group's. year | key | engine | quote located in paper.cols.txt 2013 | CCS/2013/25-million-flows-later-large-scale-detection-of-dom-based-xss | patched Chromium — V8 and the WebKit DOM, byte-level taint | true 2014 | USENIX/2014/precise-client-side-protection-against-dom-based-cross-site-scripting | taint-aware parsers inside the browser, for client-side XSS filtering | true 2015 | CCS/2015/from-facepalm-to-brain-bender-exploring-client-side-cross-site-scripting | patched Firefox — SpiderMonkey and Gecko | true 2019 | NDSS/2019/dont-trust-the-locals-investigating-the-prevalence-of-persistent-client-side-cross-site-scripting-in-the-wild | modified Chromium, storage and cookie sources | true Papers in the lineage table: 4. Papers that ran Foxhound: 9. Earliest Foxhound year in the corpus: 2024. The full sweep those four were selected from, printed in full so the selection is auditable: 2015 CCS/2015/from-facepalm-to-brain-bender-exploring-client-side-cross-site-scripting | "taint-aware Firefox engine" [produced] browser 2015 NDSS/2015/ekhunter-a-counter-offensive-toolkit-for-exploit-kit-infiltration | "Multiple Taint-Style Vulnerabilities Detector (MTS-VD)" [produced] program-analysis 2014 USENIX/2014/precise-client-side-protection-against-dom-based-cross-site-scripting | "custom taint-aware infrastructure" [produced] infrastructure 2019 NDSS/2019/dont-trust-the-locals-investigating-the-prevalence-of-persistent-client-side-cross-site-scripting-in-the-wild | "taint tracking engine" [used] program-analysis 2021 CCS/2021/spinner-automated-dynamic-command-subsystem-perturbation | "Taint'em All" [used] program-analysis 2021 CCS/2021/spinner-automated-dynamic-command-subsystem-perturbation | "Taintless" [compared] program-analysis 2025 WWW/2025/dynamic-security-analysis-of-javascript-are-we-there-yet | "LinvailTaint" [used] program-analysis === F3. How much of the use is by the people who maintain the browser === A listed contributor on the project README is counted as a maintainer. Author lists come from data/corpus2/.meta, except PETS and USENIX which carry none (100% of both venues) and were read off the venue landing page — see AUTHORS_FROM_VENUE_PAGE in fh_fold.mjs. key | authors | contributor co-author(s) CCS/2025/in-the-dom-we-trust-exploring-the-hidden-dangers-of-reading-from-the-dom-on-the | Jan Drescher; Sepehr Mirzaei; Soheil Khodayari; David Klein; Thomas Barber; Martin Johns; Giancarlo Pellegrino | Thomas Barber, David Klein IEEE-SP/2024/the-great-request-robbery-an-empirical-study-of-client-side-request-hijacking-vu | Soheil Khodayari; Thomas Barber; Giancarlo Pellegrino | Thomas Barber IEEE-SP/2024/to-auth-or-not-to-auth-a-comparative-analysis-of-the-pre-and-post-login-security | Jannis Rautenstrauch; Metodi Mitkov; Thomas Helbrecht; Lorenz Hetterich; Ben Stock | none NDSS/2025/do-not-follow-the-white-rabbit-challenging-the-myth-of-harmless-open-redirection | Soheil Khodayari; Kai Glauber; Giancarlo Pellegrino | none NDSS/2026/dom-xss-detection-via-webpage-interaction-fuzzing-and-url-component-synthesis | Nuno Sabino; Darion Cassel; Rui Abreu; Pedro Adão; Lujo Bauer; Limin Jia | none PETS/2024/fp-tracer-fine-grained-browser-fingerprinting-detection-via-taint-tracking-and-e | Soumaya Boussaha; Lukas Hock; Miguel Bermejo; Ruben Cuevas Rumin; Angel Cuevas Rumin; David Klein; Martin Johns; Luca Compagna; Daniele Antonioli; Thomas Barber | Thomas Barber, David Klein, Soumaya Boussaha, Lukas Hock PETS/2026/from-syntactic-matching-to-taint-tracking-and-back-a-comparative-study-of-web-tr | Stefano Calzavara; Samuele Casarin; Marco Squarcina; Matteo Maffei | Samuele Casarin USENIX/2024/dancer-in-the-dark-synthesizing-and-evaluating-polyglots-for-blind-cross-site-sc | Robin Kirchner; Jonas Moller; Marius Musch; David Klein; Konrad Rieck; Martin Johns | David Klein WWW/2025/dynamic-security-analysis-of-javascript-are-we-there-yet | Stefano Calzavara; Samuele Casarin; Riccardo Focardi | Samuele Casarin Papers with at least one listed contributor as co-author: 6 of 9 (66.7%). This is a lower bound in both directions: the README contributor list is "in no particular order ... ..." and is not exhaustive, and name matching is exact after diacritic folding. === G. Evidence quotes checked against paper.cols.txt === PETS/2024/fp-tracer-fine-grained-browser-fingerprinting-detection-via-taint-tracking-and-e | Foxhound [used] | implementation | exact (100%) | "Our instrumented browser FP-tracer extends on a modified version of Foxhound [50] (v96.0.3)" USENIX/2024/dancer-in-the-dark-synthesizing-and-evaluating-polyglots-for-blind-cross-site-sc | Project Foxhound [used] | methodology | exact (100%) | "we re-use a state-of-the-art taint tracking engine [36] and the associated exploit generation by Bensalim et al. [4]." CCS/2025/in-the-dom-we-trust-exploring-the-hidden-dangers-of-reading-from-the-dom-on-the | Foxhound [produced] | methodology | partial (67%) | "we created an extended version of the taint-aware Foxhound browser" WWW/2025/dynamic-security-analysis-of-javascript-are-we-there-yet | Project Foxhound [used] | methodology | exact (100%) | "Project Foxhound [32]: a modified version of Mozilla Firefox supporting dynamic taint tracking" NDSS/2026/dom-xss-detection-via-webpage-interaction-fuzzing-and-url-component-synthesis | FoxHound [used] | evaluation | partial (62%) | "We run FoxHound v126.0, the latest release at the time of the crawl, as an end-to-end tool" NDSS/2025/do-not-follow-the-white-rabbit-challenging-the-myth-of-harmless-open-redirection | Foxhound [compared] | evaluation | partial (62%) | "We considered the following state-of-the-art detectors as baselines: dynamic taint-tracking [24, 36, 45] using Foxhound [44, 45]" PETS/2026/from-syntactic-matching-to-taint-tracking-and-back-a-comparative-study-of-web-tr | Foxhound [used] | methodology | partial (95%) | "To experiment with taint tracking, we use Foxhound [26], a fork of Firefox which modifies its internal string operations to propagate taint information." IEEE-SP/2024/the-great-request-robbery-an-empirical-study-of-client-side-request-hijacking-vu | Foxhound [used] | methodology | exact (100%) | "We developed a taint-aware crawler based on Playwright [59], an instrumented version of Firefox known as Foxhound (v98.0.2) [22, 23], and Firefox DevTools [60]." IEEE-SP/2024/to-auth-or-not-to-auth-a-comparative-analysis-of-the-pre-and-post-login-security | Foxhound [used] | methodology | exact (100%) | "To collect taint reports, we used the Foxhound browser engine [39], a fork of Firefox with taint tracking capabilities." Summary: 9 quotes: 5 exact, 4 partial (>=60% of 5-word windows), 0 below threshold, 0 without full text. The deciding quotes in the ROLE hand map are checked separately, because they were read by hand rather than taken from the schema: PETS/2024/fp-tracer-fine-grained-browser-fingerprinting-detection-via-taint-tracking-and-e | exact (100%) IEEE-SP/2024/the-great-request-robbery-an-empirical-study-of-client-side-request-hijacking-vu | exact (100%) IEEE-SP/2024/to-auth-or-not-to-auth-a-comparative-analysis-of-the-pre-and-post-login-security | exact (100%) USENIX/2024/dancer-in-the-dark-synthesizing-and-evaluating-polyglots-for-blind-cross-site-sc | exact (100%) CCS/2025/in-the-dom-we-trust-exploring-the-hidden-dangers-of-reading-from-the-dom-on-the | exact (100%) WWW/2025/dynamic-security-analysis-of-javascript-are-we-there-yet | exact (100%) PETS/2026/from-syntactic-matching-to-taint-tracking-and-back-a-comparative-study-of-web-tr | exact (100%) NDSS/2025/do-not-follow-the-white-rabbit-challenging-the-myth-of-harmless-open-redirection | exact (100%) NDSS/2026/dom-xss-detection-via-webpage-interaction-fuzzing-and-url-component-synthesis | exact (100%) WWW/2024/panoptichrome-a-modern-in-browser-taint-analysis-framework | exact (100%) USENIX/2025/the-domino-effect-detecting-and-exploiting-dom-clobbering-gadgets-via-concolic-e | exact (100%) IEEE-SP/2024/a-picture-is-worth-500-labels-a-case-study-of-demographic-disparities-in-local-m | exact (100%) WWW/2025/query-design-for-crowdsourced-clustering-effect-of-cognitive-overload-and-contex | exact (100%) Summary: 13 deciding quotes: 13 exact, 0 not exact (column-repair splices; each was read on screen when the verdict was made). === H. Per-paper figures the page quotes, re-checked against the source text === paper | figure | found verbatim in paper.cols.txt WWW/2025 | 18 tools to evaluate | true WWW/2025 | eight tools run | true WWW/2025 | 6,921 domains | true WWW/2025 | 95% compatibility | true WWW/2025 | 97% transparency | true WWW/2025 | 919 flows, 94% agreeing | true WWW/2025 | 1.4x overhead | true WWW/2025 | PanoptiChrome 36.7x | true WWW/2025 | six tools evaluated on 6,921 domains | true WWW/2025 | JSFlow timed out on every site | true WWW/2025 | GIFC dropped, legacy Linvail | true WWW/2025 | PanoptiChrome 50% compatibility, unresponsive | true WWW/2025 | PanoptiChrome unresponsive on complex sites | true WWW/2025 | PanoptiChrome authors drove it by hand | true WWW/2025 | PanoptiChrome transparency 77% | true WWW/2025 | JalangiTT transparency 71% | true WWW/2025 | JalangiTT 67% / 96% compatibility | true WWW/2025 | LinvailTaint 47% / 63% compatibility | true WWW/2025 | LinvailTaint transparency 7% | true WWW/2025 | JEST 32% and IF-Transpiler 16% eventual compatibility | true WWW/2025 | IF-Transpiler transparency 82%, JEST 60% | true WWW/2025 | flow counts 0/35/67/919/128 | true WWW/2025 | the quoted conclusion | true NDSS/2026 | 55 event handlers | true CCS/2025 | one site per library in the benchmark | true CCS/2025 | 8 of 13 detected = 38.5% FN rate | true PETS/2026 | 40,605 requests on 7,614 sites | true PETS/2026 | 23,109 by taint tracking | true PETS/2026 | 7,021 taint-only | true PETS/2026 | 4%-7% taint FP vs 16%-19% | true PETS/2026 | union 34,358 (+49%) | true PETS/2026 | thousands of LOC to add data types | true PETS/2026 | yandex numeric round-trip loses taint | true PETS/2026 | slice over-tainting | true NDSS/2025 | 72 flows, 2 confirmed XSS | true NDSS/2025 | why the FP rate is high | true NDSS/2026 | 68 -> 347 with URL encoding off | true NDSS/2026 | the extra ones are not exploitable | true NDSS/2026 | TalkGen ran with encoding disabled | true IEEE-SP/2024 | Playwright + Foxhound v98.0.2 + DevTools | true IEEE-SP/2024 | 202,834 flows, 9.6% of top 10K | true IEEE-SP/2024 | 202,834 verified flows | true IEEE-SP/2024 | no iframe.srcdoc support | true IEEE-SP/2024 | Playwright 1.33, commit 2916e01 | true IEEE-SP/2024 | chose it for modern JS features | true PETS/2024 | 62 sources, 25 sinks | true PETS/2024 | added numeric tainting | true CCS/2025 | DOM reads as sources | true CCS/2025 | 38.5% false negatives on benchmark | true USENIX/2025 | rejected: interpreter-level string taint | true 50 figures checked, 0 not located. === Z. Every number on the page that does NOT come from this corpus === figure | primary source | re-checked by Firefox 142.0.1 | browser/config/version.txt on SAP/project-foxhound main | foxhound_probe.sh Playwright v1.58.2, Rust 1.86.0 | .PLAYWRIGHT_VERSION on main | foxhound_probe.sh 34 source prefs, 61 sink prefs, 1 master switch (96 tainting.* prefs) | modules/libpref/init/all.js on main | foxhound_probe.sh taintfox.escapeURL defaults to false on the main thread and is not declared in all.js; issues #259 (closed 2025-02-14) and #260 (open) | netwerk/base/nsStandardURL.cpp line 116 and the GitHub issues API | foxhound_probe.sh the Ubuntu and Windows mozconfigs set --disable-sandbox, --without-wasm-sandboxed-libraries and --disable-crashreporter; the macOS one does not; there is no unsuffixed taintfox_mozconfig | taintfox_mozconfig_* on main and the repository contents listing | foxhound_probe.sh "URLSearchParams.get" trace operation absent at v130.0, present from v140.0.2 | netwerk/base/nsURLHelper.cpp per release tag | foxhound_probe.sh 17 GitHub releases, 2 with binary assets (v128.0, v140.0.2) | GitHub releases API | foxhound_probe.sh prefs first appear in v118.0.1 (85 prefs, 25 sources); the DOM-read sources and the current 34/61 arrive in v119.0 | all.js at each release tag | foxhound_probe.sh DOM-read sources added in PR #198, merged 2024-02-08 | GitHub pulls API | foxhound_probe.sh URL.parse / URLSearchParams operations added in PR #350, merged 2025-11-26 | GitHub pulls API | foxhound_probe.sh build 81 min Ubuntu / 134 min Windows / 148 min macOS; 34 min JS tests; 7 min taint tests | GitHub Actions job timings, browser.yml run 26110426051 (2026-05-19) | foxhound_probe.sh browser.yml and playwright.yml fail on main at 7ce49d32 (2026-07-24), in Bootstrap/Configure, while main.yml passes | GitHub Actions API | foxhound_probe.sh the playwright-1.62 catch-up branch fails the browser build (newest run 2026-08-06) | GitHub Actions API, browser.yml runs filtered to non-main branches | foxhound_probe.sh TU-BS: "All binaries are compiled under Debian Bookworm (i.e., stable) and target Linux", 61 SHA256 Checksum labels | foxhound.ias.tu-bs.de, quoted verbatim by the probe | foxhound_probe.sh newest prebuilt binary: commit 1bb8dbd6, 2026-05-19 | foxhound.ias.tu-bs.de | foxhound_probe.sh Firefox stable 153.0.4 | product-details.mozilla.org | foxhound_probe.sh Playwright 1.62.1 baseline is Firefox 153.0; 1.58 baseline is 142.0.1 | playwright browser_patches/firefox/UPSTREAM_CONFIG.sh | foxhound_probe.sh GPL-3.0, 6.1 GiB checkout, repository created 2021-06-04 | GitHub repos API | foxhound_probe.sh Klein et al., EuroS&P 2022 is the citation the project asks for | README "Cite us!" section | foxhound_probe.sh 14 publications on the project wiki Hall of Fame | project-foxhound.wiki Publications.md | foxhound_probe.sh flow trace is "best-effort" and records no function-call nodes under JIT | taint/docs/TaintFlow.md | foxhound_probe.sh report.detail fields: subframe, loc, parentloc, referrer, str, sink, stack | wiki Playwright-Integration.md | foxhound_probe.sh Ubuntu CI asks for 10 GB swap and a disk purge; Windows for a 4-10 GB pagefile | .github/workflows/browser.yml | foxhound_probe.sh TU-BS offers 31 per-commit builds, Debian Bookworm, Linux only | foxhound.ias.tu-bs.de | foxhound_probe.sh === Z2. The exact literals those facts appear as on the page === So that check_page_numbers.mjs can pass over the whole page without polluting its shared ALLOW map. Every line here is a number printed on the page whose source is NOT this corpus. literal | source 142.0.1 | Foxhound main version.txt 153.0.4 | current stable Firefox (product-details.mozilla.org) 153.0 | Firefox baseline of Playwright main v1.58.2 / 1.58 | PLAYWRIGHT_VERSION on Foxhound main v1.62.1 / 1.62 | Playwright's current release, and the name of Foxhound's catch-up branch 1.86.0 | RUST_VERSION in .PLAYWRIGHT_VERSION 34 / 61 / 96 | tainting.* pref counts on main 85 / 25 / 59 | tainting.* pref counts at v118.0.1 17 releases, 2 with binaries, v128.0 and v140.0.2 | GitHub releases API 31 commits | builds offered by foxhound.ias.tu-bs.de SHA-256 | checksum algorithm named on the TU-BS page — not a figure 1bb8dbd6 / 7ce49d32 | commit hashes, GitHub Actions API 81.3 / 34.2 / 7.2 / 130.6 min (Ubuntu) | browser.yml run 26110426051 step timings 134.1 / 147.5 min (Windows) | same run 148.1 / 153.0 min (macOS) | same run ubuntu-22.04 | runner image named in browser.yml — not a figure. The number scanner also extracts 22.04 and 2.0 from this token; neither is a figure. 10 GB swap, 4-10 GB pagefile | browser.yml resource steps 6.1 GiB | repository size, GitHub repos API 14 publications | project wiki Publications.md taint-flow extension last pushed 2026-03-11; ZAP add-on last pushed 2026-08-06; ZAP catalogue status Alpha | GitHub repos API and zaproxy.org/docs/desktop/addons/foxhound/ v119.0, 2024-03-07 / PR #198, 2024-02-08 / PR #350, 2025-11-26 / issues #259, #260, #289 | release tags, pulls and issues API taintfox.escapeURL, default false, line 116; 0 occurrences of "taintfox" in all.js | netwerk/base/nsStandardURL.cpp and all.js 68 -> 347 confirmed DOM-XSS flows, i.e. 5x | Sabino et al., NDSS 2026, already in section H 25 self-test checks, 10 mutations of the documented fixture | python3 pages/foxhound_flows.py --selftest PR #198 touched 36 files | the PR object's changed_files field. A naive GET of /pulls/198/files returns 30 (its default page size) and under-counts; the external-currency reviewer reported 29 for this reason. The probe asks for per_page=50 and prints changed_files beside it. 12345, TRK-a55bd7c6, 11-16, 22 characters, 5 characters | strings from the documented worked example and from a cited paper — not figures 200, 202, 203, 204 | line numbers inside the documented worked-example JSON reproduced in the embedded script fixture, plus the digits of citekeys such as klein2022_handsanitizers — not figures. Only visible with check_page_numbers.mjs --code. And the per-paper literals the page quotes that are in a cited paper but not in the extraction schema (all re-checked in section H): 16,088 requests found by both techniques (Calzavara et al., PETS 2026) 34,358 requests in the union, +49% (same) 4%-7% and 16%-19% false-positive estimates (same) 40,605 / 33,584 / 23,109 / 7,021 requests (same) 7,614 websites correctly accessed (same) 18 tools screened, eight run, 6,921 domains (Calzavara et al., WWW 2025) 95% / 97% / 919 / 94% / 1.4x / 36.7x / 23.9x / 8.6x / 128 / 67 / 35 / 0 / 77% / 71% / 82% / 63% / 16% (same, Tables 1-3) 68 -> 347 confirmed flows, 15% more confirmed flows, 20 new vulnerabilities, 55 event handlers (Sabino et al., NDSS 2026) 72 flows, 2 confirmed, 42,288 pages (Khodayari et al., NDSS 2025) 202,834 flows, 9.6% of the top 10K (Khodayari et al., IEEE S&P 2024) 7 vulnerable sites, ~200 sites (Rautenstrauch et al., IEEE S&P 2024) 62 sources, 25 sinks, 269,784 flows, top 100K (Boussaha et al., PETS 2024) — the extraction renders the flow count with a thin space, the page with a comma 357,982 gadgets, 14,345 pages, 2,259 sites, 38.5%, 13 sites (Drescher et al., CCS 2025)
Probe output
- foxhound_probe-output.txt
foxhound_probe.sh — run at 2026-08-17T17:36:18Z === 1. Repository state === full_name: SAP/project-foxhound description: A web browser with dynamic data-flow tracking enabled in the Javascript engine and DOM, based on Mozilla Firefox (https://github.com/mozilla-firefox/firefox). It can be used to identify insecure data flows or data privacy leaks in client-side web applications. license: GPL-3.0 archived: False pushed_at: 2026-07-29T12:55:26Z updated_at: 2026-07-31T16:57:03Z stargazers_count: 177 forks_count: 30 open_issues_count: 60 size: 6354292 KB checkout (6.1 GiB) default_branch: main created_at: 2021-06-04T12:32:12Z === 2. What main is pinned to === browser/config/version.txt: 142.0.1 PLAYWRIGHT_VERSION="v1.58.2" RUST_VERSION="1.86.0" --- current upstream, for the lag --- Firefox stable: 153.0.4 ESR: 140.13.0esr Playwright latest release: v1.62.1 Playwright release-1.58: Firefox baseline e0df3d59 = 142.0.1 Playwright main: Firefox baseline f1b6c0f8 = 153.0 === 3. Taint sources and sinks, counted from the pref list on main === tainting.* prefs total: 96 sources: 34 sinks: 61 master switch: pref("tainting.active", true) prefs not defaulting to true: 0 --- source names --- location.hash location.host location.hostname location.href location.origin location.pathname location.port location.protocol location.search window.name document.referrer document.baseURI document.documentURI document.cookie localStorage.getItem sessionStorage.getItem MessageEvent PushMessageData PushSubscription.endpoint WebSocket.MessageEvent.data XMLHttpRequest.response input.value textarea.value script.innerHTML document.getElementById document.getElementsByTagName document.getElementsByTagNameNS document.getElementsByClassName document.querySelector document.querySelectorAll document.elementFromPoint document.elementsFromPoint element.attribute element.closest --- sink names --- element.after element.before EventSource Function.ctor Range.createContextualFragment(fragment) WebSocket WebSocket.send XMLHttpRequest.open(password) XMLHttpRequest.open(url) XMLHttpRequest.open(username) XMLHttpRequest.send XMLHttpRequest.setRequestHeader(name) XMLHttpRequest.setRequestHeader(value) a.href area.href document.cookie document.writeln document.write element.style embed.src eval eventHandler fetch.body fetch.url form.action iframe.src iframe.srcdoc img.src img.srcset innerHTML insertAdjacentHTML insertAdjacentText localStorage.setItem localStorage.setItem(key) location.assign location.hash location.host location.href location.pathname location.port location.protocol location.replace location.search media.src navigator.sendBeacon(body) navigator.sendBeacon(url) object.data outerHTML script.innerHTML script.src script.text script.textContent sessionStorage.setItem sessionStorage.setItem(key) setInterval setTimeout source srcset track.src window.open window.postMessage === 3b. taintfox.escapeURL — the URL-encoding knob, which is NOT in all.js === 114- 115- // Foxhound: check whether to encode URL 116: bool encodeURL = NS_IsMainThread() ? Preferences::GetBool("taintfox.escapeURL", false) : true; 117- if (!encodeURL) { 118- aMask |= esc_Never; 119- } declared in all.js? 0 occurrences of "taintfox" (expect 0) issue #259: Passing URL directly in FoxHound command brings back URL encoding | state closed | created 2025-02-13 | closed 2025-02-14 issue #260: Clear up URL encoding flags | state open | created 2025-02-14 | closed - === 3c. The build configuration actually shipped === --- taintfox_mozconfig_ubuntu --- ac_add_options --disable-updater ac_add_options --with-branding=browser/branding/foxhound export MOZ_PACKAGE_JSSHELL=1 ac_add_options --with-app-name=foxhound mk_add_options MOZ_APP_NAME=foxhound mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-tf-release ac_add_options --disable-sandbox ac_add_options --without-wasm-sandboxed-libraries ac_add_options --disable-crashreporter ac_add_options --disable-backgroundtasks ac_add_options --enable-release ac_add_options --enable-js-shell --- taintfox_mozconfig_win --- ac_add_options --disable-updater ac_add_options --with-branding=browser/branding/foxhound export MOZ_PACKAGE_JSSHELL=1 ac_add_options --with-app-name=foxhound mk_add_options MOZ_APP_NAME=foxhound mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-tf-release ac_add_options --disable-sandbox ac_add_options --without-wasm-sandboxed-libraries ac_add_options --disable-crashreporter ac_add_options --disable-backgroundtasks ac_add_options --enable-release --- taintfox_mozconfig_mac --- mk_add_options MOZ_MAKE_FLAGS="-s -j5" ac_add_options --with-ccache=/usr/bin/ccache ac_add_options --enable-macos-target=10.10 mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/obj-tf-release ac_add_options --disable-tests --- is there an unsuffixed taintfox_mozconfig, as the README command implies? --- mozconfig files present: ['taintfox_mozconfig_mac', 'taintfox_mozconfig_spidermonkey', 'taintfox_mozconfig_ubuntu', 'taintfox_mozconfig_ubuntu_clang_plugin', 'taintfox_mozconfig_ubuntu_debug', 'taintfox_mozconfig_win'] unsuffixed taintfox_mozconfig present: False (README shows `cp taintfox_mozconfig .mozconfig`) === 4. When the pref mechanism and the DOM-read sources arrived === v80.0: total=0 sources=0 sinks=0 querySelector-source=0 v96.0.3: total=0 sources=0 sinks=0 querySelector-source=0 v98.0.2: total=0 sources=0 sinks=0 querySelector-source=0 v112: total=0 sources=0 sinks=0 querySelector-source=0 v115: total=0 sources=0 sinks=0 querySelector-source=0 v118.0.1: total=85 sources=25 sinks=59 querySelector-source=0 v119.0: total=96 sources=34 sinks=61 querySelector-source=1 v121.0: total=96 sources=34 sinks=61 querySelector-source=1 v123.0: total=96 sources=34 sinks=61 querySelector-source=1 v125.0: total=96 sources=34 sinks=61 querySelector-source=1 v126.0: total=96 sources=34 sinks=61 querySelector-source=1 v128.0: total=96 sources=34 sinks=61 querySelector-source=1 v130.0: total=96 sources=34 sinks=61 querySelector-source=1 v140.0.2: total=96 sources=34 sinks=61 querySelector-source=1 142.0.1: total=96 sources=34 sinks=61 querySelector-source=1 --- URLSearchParams.get trace operation, per release tag (PR #350) --- v130.0: 0 v140.0.2: 1 142.0.1: 1 main: 1 --- the two PRs the page dates a capability to --- PR #198: DOM Related Sources and Sinks | author tmbrbr | merged True at 2024-02-08T09:35:43Z | changed_files 36 (+957/-235) NOTE: changed_files comes from the PR object. The /files endpoint pages at 30 by default, so a naive fetch under-counts; this probe asks for per_page=50. modified +2 -0 dom/base/CharacterData.h modified +2 -0 dom/base/DocumentFragment.h modified +24 -3 dom/base/DocumentOrShadowRoot.cpp modified +64 -23 dom/base/Element.cpp modified +29 -13 dom/base/Element.h modified +10 -6 dom/base/XPathGenerator.cpp modified +4 -1 dom/base/XPathGenerator.h modified +15 -4 dom/base/nsAttrValue.cpp modified +4 -0 dom/base/nsAttrValue.h modified +3 -0 dom/base/nsAttrValueInlines.h modified +20 -5 dom/base/nsContentList.cpp modified +8 -0 dom/base/nsContentUtils.cpp modified +2 -0 dom/base/nsIContent.h modified +52 -1 dom/base/nsINode.cpp modified +1 -1 dom/html/HTMLInputElement.cpp modified +1 -1 dom/html/HTMLTextAreaElement.cpp modified +2 -2 dom/html/HTMLTextAreaElement.h modified +4 -1 dom/html/nsGenericHTMLElement.cpp modified +73 -26 dom/tainting/nsTaintingUtils.cpp modified +13 -17 dom/tainting/nsTaintingUtils.h modified +22 -4 js/xpconnect/src/xpcpublic.h modified +38 -15 modules/libpref/init/all.js modified +3 -3 parser/html/nsHtml5Portability.cpp modified +1 -0 parser/html/nsHtml5Portability.h modified +6 -0 parser/html/nsHtml5String.cpp modified +20 -1 parser/html/nsHtml5String.h modified +72 -72 parser/html/nsHtml5Tokenizer.cpp modified +16 -11 parser/html/nsHtml5Tokenizer.h modified +1 -0 parser/html/nsHtml5TreeBuilder.cpp modified +2 -0 parser/html/nsHtml5TreeOperation.cpp modified +132 -20 taint/Taint.cpp modified +61 -3 taint/Taint.h added +0 -0 taint/test/mochitest/default16.png modified +1 -0 taint/test/mochitest/mochitest.ini added +247 -0 taint/test/mochitest/test_dom.html modified +2 -2 xpcom/string/nsStringBuffer.cpp PR #350: Adding URL related Taint Operations | author tmbrbr | merged True at 2025-11-26T16:50:06Z | changed_files 4 (+28/-1) NOTE: changed_files comes from the PR object. The /files endpoint pages at 30 by default, so a naive fetch under-counts; this probe asks for per_page=50. modified +22 -0 dom/tainting/nsTaintingUtils.cpp modified +1 -0 dom/tainting/nsTaintingUtils.h modified +3 -1 dom/url/URL.cpp modified +2 -0 netwerk/base/nsURLHelper.cpp === 5. Releases, and which ship a binary === 17 releases 142.0.1 2026-06-15 assets: 0 v140.0.2 2026-02-23 assets: 3 ['foxhound-140.0.2.en-US.linux-x86_64.tar.xz', 'foxhound-140.0.2.en-US.mac.dmg', 'foxhound-140.0.2.en-US.win64.installer.exe'] v130.0 2025-09-05 assets: 0 v128.0 2025-05-16 assets: 4 ['foxhound-128.0.en-US.linux-x86_64.tar.bz2', 'foxhound-128.0.en-US.macos-13.dmg', 'foxhound-128.0.en-US.macos-latest.dmg', 'foxhound-128.0.en-US.win64.installer.exe'] v126.0 2025-03-17 assets: 0 v125.0 2025-01-27 assets: 0 v123.0 2024-09-09 assets: 0 v121.0 2024-04-25 assets: 0 v119.0 2024-03-07 assets: 0 v118.0.1 2024-01-17 assets: 0 v115 2024-01-09 assets: 0 v114.0.2 2023-10-12 assets: 0 v113.0.1 2023-08-11 assets: 0 v112 2023-07-31 assets: 0 v98.0.2 2023-05-08 assets: 0 v96.0.3 2022-04-20 assets: 0 v80.0 2021-10-21 assets: 0 releases with binary assets: 2 -> ['v140.0.2', 'v128.0'] === 6. Prebuilt binaries hosted by TU Braunschweig === page bytes: 78313 platform sentence: All binaries are compiled under Debian Bookworm (i.e., stable) and target Linux SHA256 Checksum labels on the page: 61 commits offered: 31 1bb8dbd6 Tue, 19 May 2026 17:16:09 GMT a3def318 Wed, 13 May 2026 12:11:03 GMT 09dda176 Wed, 25 Feb 2026 14:22:21 GMT 8c191733 Mon, 23 Feb 2026 13:33:07 GMT playwright versions offered: ['v1.46.0', 'v1.47.0', 'v1.48.2', 'v1.49.1', 'v1.53.2', 'v1.56.1', 'v1.58.2'] === 7. CI state on main, and how long a build takes === browser.yml on main (newest first): 2026-07-24 failure 7ce49d32 2026-05-19 success 1bb8dbd6 2026-05-12 success a3def318 playwright.yml on main (newest first): 2026-07-24 failure 7ce49d32 2026-05-19 success 1bb8dbd6 2026-05-12 success a3def318 main.yml on main (newest first): 2026-07-24 success 7ce49d32 2026-05-19 success 1bb8dbd6 2026-05-12 success a3def318 --- newest browser.yml run on the Playwright catch-up branch (page claims it fails) --- 2026-08-06 failure playwright-1.62 b1610aba 2026-08-06 failure playwright-1.62 58a55435 2026-08-06 failure playwright-1.62 ca7868b2 2026-08-06 failure playwright-1.62 7beb96ee --- failing steps of the newest browser.yml run on main --- Windows: failure failing step: Configure = failure Ubuntu: failure failing step: Bootstrap = failure MacOS: failure failing step: Bootstrap = failure --- newest SUCCESSFUL browser.yml run on main, with per-step minutes --- run id 26110426051 MacOS: 153.0 min total, runner GitHub Actions 1018788187 Checkout release branch: 2.8 min Bootstrap: 1.1 min Build: 148.1 min Ubuntu: 130.6 min total, runner GitHub Actions 1018788185 Free Disk Space (Ubuntu): 1.5 min Checkout release branch: 1.8 min Bootstrap: 1.3 min Build: 81.3 min JavaScript Tests: 34.2 min Tainting Tests: 7.2 min Package: 3.0 min Windows: 147.5 min total, runner GitHub Actions 1018788188 Download Mozilla Build: 1.0 min Checkout release branch: 2.5 min Bootstrap: 6.8 min Build: 134.1 min Package: 2.2 min --- build resources the workflow itself asks for --- runs-on: ubuntu-22.04 uses: jlumbroso/free-disk-space@main swap-size-gb: 10 runs-on: windows-latest minimum-size: 4GB maximum-size: 10GB runs-on: macos-latest === 8. The reporting surface the README and wiki document === README contains: __taintreport README contains: report.detail README contains: JS_ReportTaintSink README contains: // Foxhound README contains: String.tainted README contains: foxhound.ias.tu-bs.de README contains: KleBarBen README contains: taintfox_mozconfig README contains: ./mach build --- wiki pages (the wiki is a git repo, so this is the whole set) --- Adding-and-Running-Mochi-Tests.md Adopting-Foxhound-for-your-Requirements.md Building-Foxhound-(with-playwright-integration).md Building-Manually.md Debugging-Crash-Dumps.md Firefox-String-Type.md Garbage-Collection-in-Spidermonkey.md Home.md Memory-Leak-Debugging.md Observing-Taint-Flows.md Playwright-Integration.md Publications.md Release-Scheduling.md Rust-Errors-due-to-invalid-Bindings-during-Foxhound-Compilation.md Source-and-Sink-Listings.md Spidermonkey-Debugging-and-Understanding.md Taint-Flow-Design-2.0.md TaintOperations-and-how-they-come-to-be.md Updating-from-upstream-Firefox.md Visualizing-Taint-Flows.md Publications.md lists: 14 publications report.detail fields documented in Playwright-Integration.md: subframe: loc: parentloc: referrer: str: sink: stack: TaintFlow.md best-effort / JIT caveat: (TaintFlow.md is in the repo, not the wiki — see below) --- taint/docs/TaintFlow.md in the repo: the JIT caveat --- 4:The flow is made up of several nodes, one for each operation that was performed on the tainted data. The taint flow tracing is currently considered a "best-effort" service. 12:The engine will generally fail to record function call information when executing in one of the JIT modes (instead of the interpreter). As such, functions 19:In theory, they should still be available during JIT execution, however, a few methods are actually "inlined" by the JIT compiler and will thus not be recorded. --- taint/docs/SupportedFunctions.md --- # List of supported functions ``` encodeURI encodeURIComponent decodeURI decodeURIComponent JSON.stringify JSON.parse Array.join escape unescape ``` and [String functions](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String) === 9. Satellite tooling === --- is the ZAP add-on in ZAP own catalogue? --- title: Foxhound status Alpha present: True m10x/project-foxhound-taint-flow-extension: archived=False pushed=2026-03-11 license=Apache-2.0 stars=1 SAP/project-foxhound-zap-addon: archived=False pushed=2026-08-06 license=Apache-2.0 stars=2 therahulkanyal/PanoptiChrome: archived=False pushed=2025-05-05 license=MIT stars=10 === 10. Every external URL the page links, with its HTTP status === 200 https://github.com/SAP/project-foxhound 200 https://github.com/SAP/project-foxhound/tree/main/taint 200 https://github.com/SAP/project-foxhound/wiki 200 https://github.com/SAP/project-foxhound/wiki/Observing-Taint-Flows 200 https://github.com/SAP/project-foxhound/wiki/Source-and-Sink-Listings 200 https://github.com/SAP/project-foxhound/wiki/Playwright-Integration 200 https://github.com/SAP/project-foxhound/wiki/Release-Scheduling 200 https://github.com/SAP/project-foxhound/wiki/Publications 200 https://foxhound.ias.tu-bs.de/ 200 https://github.com/SAP/project-foxhound-zap-addon 200 https://github.com/m10x/project-foxhound-taint-flow-extension 200 https://www.zaproxy.org/docs/desktop/addons/foxhound/ 200 https://firefox-source-docs.mozilla.org/setup/linux_build.html 200 https://firefox-source-docs.mozilla.org/writing-rust-code/update-policy.html 200 https://domgo.at/cxss/example/1 === done ===
Bibliography additions
Twelve entries were appended to bibliography, all generated with scripts/bibgen.mjs from the corpus index and then checked for key collisions against the live bibliography (0 collisions, 0 duplicate keys after merge). Four existing keys were reused: klein2022_handsanitizers, boussaha2024_tracer, kanyal2024_panoptichrome, calzavara2026_syntactic.
| Key | Metadata source | Note |
|---|---|---|
lekies2013_million | OpenAlex via DOI | |
stock2014_precise | USENIX landing page | index record has no authors and no DOI (true of 100% of USENIX and PETS records); bibgen.mjs failed loudly and the author list was read off the venue page with curl and a browser User-Agent |
stock2015_facepalm | OpenAlex via DOI | |
steffens2019_dont | NDSS venue page | bibgen.mjs emitted the slug as the title; corrected by hand |
kirchner2024_dancer | USENIX landing page | authors fetched as above |
khodayari2024_great | OpenAlex via DOI | |
rautenstrauch2024_auth | OpenAlex via DOI | |
khodayari2025_follow | NDSS venue page | no DOI; cited by URL |
calzavara2025_dynamic | OpenAlex via DOI | |
liu2025_domino | USENIX landing page | authors fetched as above |
drescher2025_trust | OpenAlex via DOI | |
sabino2026_detection | NDSS venue page | no DOI; cited by URL |
The bibtex plugin serves a cached parse, so after appending, both literature:bibliography and this run's two pages were purged with ?purge=true and the rendered pages re-read to count bibtex_citekey markers and reference-list entries. Skipping that step renders every new citation as broken while both raw sources look perfect.
Review log
Three focused reviewers were run in parallel on the frozen draft, then the generic pass. Each was told explicitly that the author's context may not be exhaustive and was handed the page text, both scripts, both outputs, the published code and the corpus paths.
sonnet — figures versus script
Re-ran both scripts. report_foxhound.mjs output byte-identical to the committed file; foxhound_probe.sh byte-identical except its own run timestamp — no drift in any external fact. Both guards and the code self-test reproduced. It traced ~90 numeric claims and found no wrong figure, no denominator mismatch and no invented pref name. Eight findings, all accepted:
| # | Finding | Disposition |
|---|---|---|
| 1 | The DOM-read source row dropped the document. prefix on 9 of its 10 entries while every other row kept it. | Accepted. All ten written out in full. |
| 2 | The Calzavara et al. comparison table's per-tool numbers (128/67/35/0 flows, 77%/71%/82%/7% transparency, the compatibility rates) were not in the report's machine-checked FIGURES array — disclosed in Z2 as hand-checked, but a reader who saw check_page_numbers.mjs pass would over-trust them. The reviewer hand-checked all of them and found every value exact. | Accepted. Sixteen further needles added to section H; it now checks 48 figures, 0 not located. Also caught two of my own needles that were column-spliced and would have read as failures. |
| 3 | “55 realistic event handlers” was in Z2 as “re-checked in section H” but was not actually in section H. The reviewer verified it by hand: the paper says “a total of 55 event handlers”. | Accepted, and it is the sharpest kind of finding — the disclosure was true of the intent and false of the artefact. Now in section H. |
| 4 | “13-site benchmark” looked like a mis-transcription of the paper's “13 libraries”, but reading the paragraph confirms one site is built per library. | Accepted as confirmation; needle added. |
| 5 | The prose called Firefox 153.0 “also current stable Firefox”, where stable is 153.0.4 — looser than the page's own Z2 bookkeeping, which lists the two separately. | Accepted. Rewritten to give both, and “eleven Firefox versions” is now “eleven Firefox major versions”. |
| 6 | A hardcoded sentence in report_foxhound.mjs asserted “both papers whose own contribution is a Foxhound extension state one”, which its own data contradicts: there are three extension papers and Drescher et al. state no version. Never quoted on the page. | Accepted, and it is the most useful finding of the round. The sentence is replaced by a computed per-role breakdown (baseline 2 of 2, extended 2 of 3, used 1 of 4). A hand-written sentence inside an audit script is exactly the thing nothing else checks. |
| 7 | The “Linux only” and “SHA-256 checksum” claims about the TU Braunschweig host were true but not printed by the probe, so not mechanically re-verifiable. | Accepted. The probe now quotes the platform sentence verbatim (“All binaries are compiled under Debian Bookworm (i.e., stable) and target Linux”) and counts the checksum labels (61). |
| 8 | The claim that the playwright-1.62 branch was failing CI was not fetched by any script and was untraceable from the artefacts. | Accepted. The probe now lists the newest non-main browser.yml runs; the branch's newest run is 2026-08-06, failure. |
sonnet — external currency
~25 live fetches. Confirmed as still true today: all 14 page URLs at 200; the Firefox/Playwright/Rust pins and the current-stable comparison; the release list and which two carry binaries; every row of the pref-history table including the document.querySelector pref flipping on at v119.0; both pull-request numbers and merge dates; issue #289 closed 2026-02-09, matching the page's re-clone cut-off; the CI failure on main at 7ce49d32 with no newer run; the per-step build minutes; the TU Braunschweig commit count, platform and newest commit; the taint/README.md staleness and the wiki grep artefacts, both of which the page describes as defects and both of which are still there; 14 publications; and the Cite us! request, unchanged. One finding accepted, one rejected:
| # | Finding | Disposition |
|---|---|---|
| 1 | The ZAP add-on is not merely “first-party”: it is listed in ZAP's own add-on catalogue. | Accepted. The page now links the catalogue entry and gives its status (Alpha), and the probe fetches it. The reviewer also reported a version and publication date from the catalogue; those were not reproducible on the page I fetched, so only the facts I could see are on the page. |
| 2 | PR #198 changed 29 files, not the 36 the page states. | Rejected — the reviewer is wrong, and the reason is worth recording. The GitHub pulls API's /files endpoint pages at 30 by default, so a naive fetch under-counts a 36-file PR. The PR object's own changed_files field says 36; the probe asks for per_page=50 and now prints changed_files beside the file list so the disagreement cannot recur. |
| 3 | The extension's last push is 2026-03-11, five months ago, so “maintained” is generous; PanoptiChrome's last commit is 2025-05-05, so it should not be called maintained (the page does not). | Accepted in part. The page no longer calls the extension “maintained”; it gives the push dates and lets the reader judge. No change for PanoptiChrome, which the page only cites as the Chromium-side alternative. |
sonnet — citations and quotes
16 of 16 citekeys resolve, 0 collisions with the live bibliography, 0 duplicates, all 12 additions valid BibTeX, and every author list, title, venue and year in all 16 entries verified against a primary source — Crossref for the DOI-bearing ones, the venue's own author block for USENIX, PETS and NDSS. It checked 17 quotations: 14 verbatim, 1 genuinely present but column-spliced, 2 wrong, 0 not found. It also independently recounted PR #198 at 36 files, which settles the disagreement with the currency reviewer.
| # | Finding | Disposition |
|---|---|---|
| 1 | The page quoted the project wiki as “always one release behind Playwright”. The wiki says “will always be one release behind Playwright”. Not a splice — this is a plain markdown file, so it was a transcription error. | Accepted. Fixed to the exact wording. A one-word slip in a quotation is still a wrong quotation. |
| 2 | The page silently corrected a typo in a quoted sentence: Khodayari et al. write “the presense of data flows”; the page wrote “presence”. | Accepted. Restored the source spelling with [sic]. Silently fixing a source's typo makes the quote unfindable by anyone who greps for it. |
| 3 | The provenance page's methodology paragraph said the 13 hand-picked deciding quotes were “11 exact, 2 shortened”, while the report it cites says “13 exact, 0 not exact”. | Accepted. The page was describing the state before I shortened the two spliced quotes. Rewritten to match the artefact and to say why the two are short. This is the failure the provenance page exists to catch, caught on the provenance page itself. |
| 4 | The “entire parts array is tainted” quote is present but interleaved line-by-line across two PDF columns in both .cols and .norm; the reviewer reconstructed it from paper.txt. | Accepted as confirmation, and noted here as a case where the repaired rendering is worse than the raw one — the opposite of the usual assumption. |
Everything else it checked came back verbatim, including all of the load-bearing ones: the JIT/best-effort sentences, the URL-encoding result and its manual sample, the yandex round-trip, “thousands of lines of code”, the 72/2 result, “Foxhound+”, the iframe.srcdoc gap, the modern-JS-features reason for choosing Foxhound over the 2019 Chromium tracker, every cell of the bake-off table, the Cite us! request, the gkrust note, the Atoms “indicate a bug in the engine” line, the worked example's offsets and operation chain, both homographs, and the 6-of-9 contributor overlap with its three named exceptions.
fable — generic
The strongest pass of the four. It read both pages, the assembled provenance page, the published code, and all four neighbouring pages, and it found two genuine defects in the published script plus a factual error, a missing definition, an omitted table row, and a structural problem. Twelve findings; eleven accepted in whole or part, none rejected outright.
| # | Finding | Disposition |
|---|---|---|
| 1 | The published script's encoded flag did not measure what the page claimed. It conflated “arrived URL-encoded” with “the crawl-time substring happened to contain no HTML-significant character”, ignored decoding operations and their order, and — provably, from the self-test — marked the documented location.hash → unescape → innerHTML flow, the textbook exploitable DOM-XSS, as droppable. --drop-encoded would have silently removed true positives. “Deliberately conservative” was also the wrong word: over-marking flows as droppable is aggressive. | Accepted in full; this is the most important finding of the whole review. The flag is split into encoded_at_sink (a property of the code: an encode is the last transformation before the sink, decided by walking the sink-first chain and letting the first encode or decode win) and no_syntax_chars (a property of the input you sent, reported beside the table and never dropped). Two new self-tests cover encode-then-decode and the dangerous-payload case; the encodeURIComponent leaves-'() caveat is in the code and on the page. The page paragraph was rewritten around the distinction. |
| 2 | jit_blind was satisfied by the reporting harness's own function call. In the documented fixture the only function node is ReportTaintSink from taint_reporting.js — the harness, not application code — so the metric the page presents as a contribution would read 0% blind on every flow of any harness whose report path is traced. script_of had the same bug: it could return the harness script's scripthash as the deduplication key. | Accepted in full. A HARNESS_RE (overridable with --harness) excludes harness nodes from both. The fixture's own verdict flipped to JIT-blind, and the self-test now asserts that — the comment that said the opposite was wrong. Running the script with a non-matching harness regex reports 0% instead of 100%, which is the trap made visible. |
| 3 | The page's count of 9 contradicts the live programming:crawler and privacy:javascript tables, which say 8. The difference is explicable — those tables count schema tuples, and the extraction files Khodayari et al.'s NDSS 2025 paper as compared — but this page's own reading shows that paper ran the browser, so the neighbours are slightly wrong and a reader following the link meets 8, then 9, unexplained. | Accepted. A footnote on this page reconciles the two and says which is the deeper audit. The errata edit to the two neighbours is deferred rather than made silently here: it is queued as its own work item, because editing another page's central table belongs with that page. |
| 4 | “Report whether URL encoding was enabled” never named the knob — the page's most emphasised advice was its least usable. | Accepted, and it turned into the best external finding of the run. The knob is taintfox.escapeURL, read at netwerk/base/nsStandardURL.cpp:116 with a false default on the main thread (true on workers), documented only in a closed issue thread, and absent from all.js — so it is not one of the 96 prefs and does not appear in about:config until you type it. Open issue #260 asks for exactly that to be fixed. The maintainer's stated reason (mimic legacy IE, which did not encode the hash) is now quoted. The probe fetches the call site, counts taintfox in all.js, and prints both issues' states. |
| 5 | The sentence “Part of the apparent decade-long decline in DOM-XSS prevalence is this setting, not the web” was an unattributed causal claim. | Accepted. It is Sabino et al.'s argument — “several factors … may contribute to the dwindling number … First, modern browsers encode any special characters in URLs” — and it is now attributed to them rather than asserted. |
| 6 | “A field with one instrument and a fringe” listed the three smallest families and omitted Jalangi/JalangiTT at 6, the largest row in the report's own table. | Accepted. Jalangi's 6 is now on the page with the reason it is not a competing instrument (a general source-instrumentation framework, mostly used for other things), and the claim is scoped to in-browser engines. Presenting the small rows and skipping the big one is exactly what the residue discipline exists to prevent. |
| 7 | Structure: the decision-relevant sections sat behind a 330-line code listing, and “Getting a browser” came after the reducer although you need a browser before you have reports. | Accepted, reordered: report contents → source/sink surface → what it does not track → versus the alternatives → getting a browser → reducing reports → use in publications → what to report. The full listing was kept inline (house style on this wiki publishes whole files) but now sits after both decision sections. |
| 8 | “Foxhound is a debug-instrumented browser build” was unsupported and probably wrong. | Accepted — it was wrong, and checking it produced a better fact. taintfox_mozconfig_ubuntu has the DEBUG block commented out and --enable-release set. What it does set is --disable-sandbox, --without-wasm-sandboxed-libraries and --disable-crashreporter — as does the Windows one, though not the macOS one. The page now has a box on crawling with the sandbox off and on counting crashes yourself, which is a stronger warning than the one it replaced. |
| 9 | Two date claims were inferences presented as facts: that PR #350 first shipped in v140.0.2, and that taint “still propagated” through URLSearchParams.get before it. | Accepted, both now verified rather than inferred. “URLSearchParams.get” is absent from netwerk/base/nsURLHelper.cpp at v130.0 and present from v140.0.2 (probe checks four tags). The PR diff shows MarkTaintOperation called after an assignment that already carries the taint, so the gap was in the trace, not the tracking — and the page says that, with the call site quoted. |
| 10 | “The single most common reporting failure on the tool” was a superlative over an unmeasured field, and “state any of this” over-claimed what the 3-of-7 figure covers. | Accepted. The superlative is gone, replaced by the honest reason it cannot be measured (the extraction has no field for which prefs a paper set). “Any of this” is now “even the version or commit”. |
| 11 | The provenance run record claimed a generic pass whose findings the log did not contain. | Accepted — this section is that content, added before publication, and the assembler refuses to build with an empty slot. |
| 12 | Minor: “recent and it is dated” ambiguous; the referrer row had an empty cell; “All three checked” followed a list of two repositories plus a catalogue entry; the intro footnote's “so” was a non-sequitur; the harness input contract lived only in the docstring with no link to the project's Playwright page; UNITS[“flow”] was dead code; the GPL-3.0 licence was missing. | All accepted and fixed. The harness contract is now a short paragraph above the listing with a link to the wiki page that gives the ten-line Node script — the reviewer was right that this saves a first-time user a confused half-hour. |
Two things the pass explicitly endorsed and which were therefore left alone: the fourteen-zero-years table with its lineage explanation, and the calibrated wording of the judgement-calls table (“did not outlive their paper” rather than “superseded”).
Re-review round
The task's rule is to re-run any reviewer whose findings were acted on. All four rounds' findings were acted on, so the figures/code pass and the citations pass were re-run against the published text (fetched back with ?do=export_raw and byte-identical to the local drafts).
One re-run failed to do its job: the first citations re-review returned a single line saying it would wait for a background agent, with no findings at all. That is recorded rather than quietly dropped — a reviewer that returns nothing is not a pass — and it was replaced with a re-run given an explicit A–K checklist of the specific claims that were new or changed since the first round, with an instruction not to delegate.
The re-review round also produced one finding I caught myself while it ran, recorded above under Quotes and figures checked: three of the section-H needles were short enough to match almost anything, so they were passing for the wrong reason.
The re-run of the claims check verified 15 items against primary sources and confirmed all of them except two, both accepted:
| # | Finding | Disposition |
|---|---|---|
| 1 | The page said Sabino et al. read the decade-long decline in DOM-XSS as this setting “rather than the web having got safer”. The paper actually names several contributing factors and credits genuine improvements among them — better ad blocking, HTTPS/mixed-content enforcement — so the “rather than” was a false dichotomy the authors do not assert. | Accepted. Rewritten to quote “several factors, such as dataset and methodology, may contribute”, to say the encoding is the first of them, and to name the genuine improvements alongside. The page now calls it “one measurement artefact among real effects rather than a debunking”. |
| 2 | The page stated as fact that PanoptiChrome's own authors avoided the unresponsiveness because they drove it by hand; the paper hedges with “likely because”. | Accepted. The hedge is restored. |
Everything else it checked came back verbatim, including the taintfox.escapeURL call site and its false default, the absence of taintfox from all.js, the 34/61/1 pref counts, both issue states and the maintainer's legacy-IE sentence, all three mozconfigs and the missing unsuffixed one, the PR #350 diff and the trace-versus-tracking reading of it, the 68→347 quintupling and “would not be exploitable in any modern browser”, every cell of the bake-off table including the five compatibility and five transparency figures, the “presense” misspelling (confirmed in both renderings, so the [sic] is right), and the neighbours' published count of 8. It also noted that for the NDSS 2026 paper paper.cols.txt is the well-ordered rendering and paper.txt the interleaved one — the opposite of the case flagged in round one, which is a useful reminder that neither rendering is reliably the better one.
Rendering check
Both pages were published, then literature:bibliography and both new pages were purged with ?purge=true and the rendered HTML re-read. Without the purge the bibtex plugin serves a cached parse and every new citation renders broken while both raw sources look perfect — the trap recorded on programming:crawler:webxray, where 14 of 20 citations were broken until the purge.
| Check | Result |
|---|---|
literature:bibliography entries after the append | 314 → 326 (12 added, 0 collisions, 0 duplicates) |
[citekey] markers rendered on the content page | 90 bibtex_citekey spans, 0 broken |
| Distinct citekeys on the content page | 16, all resolving |
| Reference list at the foot of the content page | 16 entries, each with author list, venue and a DOI or venue URL |
<WRAP important> boxes | 5, all rendered as boxes |
| Footnotes | 20 |
| Tables | 10 on the content page, 8 here |
Downloadable foxhound_flows.py snippet | present |
Literal {[ sequences left unrendered in prose | 0 |
| Real citations on this provenance page | 0, as intended; the one textual match is the words “bibtex_citekey” in the prose above |
{[key]} written as a literal example here | escaped with , renders as text |
| Unfilled double-brace marker slots in this page | 0 — and scripts/build_provenance_foxhound.py refuses to build if a slot has no content. It fired on the first draft of this very row, which wrote the marker syntax out literally; that is the guard working, and the row is now phrased so it cannot trip it |
Both <file text> blocks | render as downloadable snippets with the full output inline |
| Reachability | the content page was already linked from programming:crawler and privacy:javascript before it existed, so no new inbound link was needed; the two pages link each other, and this one links corpus |
Guards run against the final text, all passing: check_tables.mjs on both pages, check_page_numbers.mjs on the content page both windowed and whole-page and with --code, check_attributions.mjs (9 attributions), a citekey-resolution check, a byte-comparison of the embedded script against pages/foxhound_flows.py, and the script's own 19-check self-test.
