What the JavaScript actually does
The Drey article describes AutoMonitor in three sentences: it has 540 lines, it generates a session ID on line 7, it posts to analytics.infra.ndstudio.gov/metrics on line 8, and it "rewires the part of the browser that handles how a page talks to the outside world." All four claims are exactly true. What the article doesn't say is just how much the script captures, and how few of the standard guardrails a normal analytics tool would offer.
The full script source — sha256 cb4da9fc156568b09cdf9f01d4f343e204cbd4116db068e65099c74ba3ebb78e, served live from cdn.infra.ndstudio.gov/internal-analytics/script.js — reads as follows.
The architecture in one image
// Top-level structure of AutoMonitor: class AutoMonitor { constructor() { // generates session-id, sets endpoint, starts init() / setupInstrumentation() { // wires up all the trackers instrumentImages() // listens to every image load / error / cache hit instrumentFetch() // REPLACES window.fetch with a wrapper instrumentXHR() // REPLACES XMLHttpRequest.prototype.open + .send instrumentButtons() // click listener on every <button>, <a>, [role=button]โฆ instrumentConsoleErrors() // REPLACES console.error + console.warn // + window error + unhandledrejection listeners trackPageMetrics() // Core Web Vitals (LCP, FID, CLS) + nav timing setupUnloadHandler() // flush metrics on tab close + every 30s } track(name, duration, metadata) // queues a metric onto this.metrics[] flush() // sends batch via navigator.sendBeacon }
The piece that matters: three monkey-patches. AutoMonitor doesn't add a listener alongside window.fetch, XMLHttpRequest.send, and console.error. It replaces them with its own wrapper functions that call the originals after logging. From the moment the script loads, every network call the page makes โ to first-party APIs, to third parties, to anywhere โ and every console message and every JavaScript error is observed by this code before anything else.
This is not the usual way analytics libraries work. Most analytics tools add event listeners or hook into framework lifecycle methods. Monkey-patching low-level browser primitives is what tools like Sentry or DataDog do for application performance monitoring โ and even they make it opt-in. Here it is unconditional, on a federal government website, on a script that loads with no documentation, no privacy disclosure, and no cookieless or do-not-track respect.
What it records (every event type, with the fields it sends)
Each of these fires automatically โ no opt-in, no user-visible UI, no awareness on the visitor's part.
| Event | When | Fields sent |
|---|---|---|
image_load | Every <img> that finishes loading | filename, width, height, load duration in ms |
image_error | Every image that fails to load | filename, error type, duration |
image_cache | Every image served from the browser cache | filename, dimensions, estimated "render time" based on pixel count |
api_call / api_error | Every fetch() the page makes | URL pathname, HTTP method, status code, ok flag, duration |
xhr_call / xhr_error | Every XMLHttpRequest the page makes | URL pathname, method, status code, duration |
button_click | Every click on a button, anchor, role=button, [data-track], or input[type=submit/button] | tag, type, visible text (up to 100 chars), id, full className, href, current page URL, click x/y coordinates, form id, form action |
js_error | Every uncaught JavaScript error | message, source filename, line, column, full stack trace, page URL, user-agent string |
promise_rejection | Every unhandled async error | reason, stack, page URL |
console_error / console_warn | Every console.error() / console.warn() the page emits | full message (up to 500 chars), stack trace at call site, page URL |
lcp / fid / cls | Core Web Vitals as they fire | metric value, page URL, referrer |
page_load / dom_ready / first_byte / dom_parse | Once per navigation, 100ms after load | timing values, page URL, referrer |
Every payload above also carries: session (a UUID v4 generated once per page load, persisting across all events of that visit), timestamp (millisecond unix epoch), url (current page pathname), referrer (the page you came from).
How it sends the data
The transport is the interesting part. From line 434:
flush() { if (!this.metrics.length) return; const payload = this.metrics.splice(0); if (navigator.sendBeacon) { const success = navigator.sendBeacon(this.endpoint, JSON.stringify(payload)); // ... } else { fetch(this.endpoint, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(payload), keepalive: true }) } }
navigator.sendBeacon is the W3C API designed specifically for telemetry that has to survive page unload. It is fire-and-forget: there is no response, no way for the page to know whether the beacon arrived, no way for the user to cancel it. Browsers prioritize beacon delivery even during tab close, navigation, or browser exit. keepalive: true on the fallback fetch does the same thing.
This is the correct API for the use case, and it is also the API that gives the receiver maximum chance to receive the data regardless of what the user does. Pressing the back button does not stop a beacon. Closing the tab does not stop a beacon. The data leaves the browser.
Batch flushes happen on three triggers:
- When the metrics queue hits 20 entries (configurable via
data-batch-sizeon the script tag). - Every 30 seconds, if anything is queued.
- On
beforeunload, dumping whatever is left.
cb4da9fc1565โฆ)
What's "privacy-preserving" โ and what isn't
The script makes two visible nods to privacy:
cleanUrl()on line 461 strips?query=stringand#fragmentfrom URLs before they are recorded. So a search query passed via querystring would not be captured here. The pathname is kept in full.- Image filenames are truncated to just the trailing path component:
img.src.split('/').pop(). So/images/private/document.jpgbecomes justdocument.jpg.
That's the entire privacy story. Everything else is sent verbatim:
- Full
location.pathnameon every event. On TrumpRx,/drug/adderall-20mgwould go in cleartext. - Click
xandycoordinates as raw pixel values. - Click target's visible text content, up to 100 characters. The exact label of every button or link the user clicked.
- Click target's id and className โ useful for fingerprinting the user's UI state.
- Click target's
hrefwhen present. Where the link points. - Form id and form action URL when the click is inside a form.
- Full JavaScript stack traces from console errors. App internals leak.
- Full user-agent string when an error fires.
- Document referrer โ where the visitor came from.
There is no IP-address stripping. (sendBeacon and fetch both carry the visitor's IP to the server by definition โ and the server is the one who would strip it, if anyone did.) There is no consent banner, no opt-out, no respect for the browser's Do Not Track header, and no documentation of any of this anywhere on ndstudio.gov.
Cross-site analytics audit
The Drey article identifies PostHog on TrumpRx and AutoMonitor on ndstudio.gov. We probed every NDS-built or NDS-fronted site we could reach and grepped for tracker markers in the served HTML.
| Site | AutoMonitor | PostHog | Google Analytics | Notes |
|---|---|---|---|---|
ndstudio.gov | โ loaded | โ | โ | Studio's own homepage |
trumprx.gov | โ | โ loaded | โ | Federal drug-pricing site |
realfood.gov | โ | โ | โ | MAHA nutrition site (Next.js โ trackers may live in chunked JS bundles, not the served HTML) |
nasaforce.gov | โ | โ loaded | โ | NEW finding โ article didn't flag this |
americabydesign.gov | โ | โ | โ | Same caveat as realfood โ trackers may be in chunks |
genesis.energy.gov | โ loaded | โ | โ loaded | NEW finding โ DOE/Oracle AI initiative also runs Google Analytics on top of AutoMonitor |
retire.opm.gov | โ | โ | โ | OPM digital retirements |
What this means:
- AutoMonitor is loaded on at least two NDS-built federal sites, including DOE's flagship AI initiative.
- PostHog is loaded on at least two federal sites built by the same office.
- The DOE Genesis Mission site adds Google Analytics on top of AutoMonitor โ three separate telemetry channels reporting to three different parties on every visit.
- Three of the seven probed sites had no trackers detectable in the served HTML. That likely means trackers loaded later via Next.js chunked bundles; a deeper crawl would resolve it.
The receiving endpoint
We probed https://analytics.infra.ndstudio.gov/metrics directly from outside an embedded JavaScript context, with the appropriate Origin: https://ndstudio.gov header and a well-formed JSON payload. Four HTTP methods, four responses:
GET /metrics โ no response (000) POST /metrics โ no response (000) OPTIONS /metrics โ no response (000) HEAD /metrics โ no response (000)
"No response" here means the connection drops without the server sending HTTP status, headers, or body. A normally-configured endpoint that wanted to reject our probe would return 401, 403, 405, or 404. This one returns nothing at all.
That behavior is consistent with two things: aggressive IP-based or origin filtering at the network edge, or a WAF rule that drops requests not matching a session-cookie pattern. Either way, the endpoint is configured to take data only from the embedded script's context โ which is the configuration you'd choose if you wanted the only thing entering your collector to be the data your own JavaScript chose to send.
It's also the configuration that prevents outside researchers from understanding what the endpoint logs, what it retains, or how it's processed.
How this compares to commercial analytics tools
| AutoMonitor | PostHog (default) | Google Analytics 4 | Plausible / Fathom (privacy-first) | |
|---|---|---|---|---|
| Page-view tracking | yes | yes | yes | yes |
| Click tracking with element data | yes (full) | yes | yes | no |
| Click coordinates (x, y) | yes | yes (replay) | no | no |
| Full session replay (keystrokes, mouse) | no | yes (default on) | no | no |
| JS error capture with stack traces | yes | yes | no | no |
Monkey-patches fetch / XHR | yes (unconditional) | no (opt-in) | no | no |
Monkey-patches console | yes (unconditional) | no | no | no |
| Consent banner / opt-out UI | none | yes | yes | yes |
| Respects DNT header | no | yes (configurable) | yes (configurable) | yes |
| Source publicly auditable | script is downloadable; the receiving infrastructure is not | open source | no | open source |
| Published privacy policy for the collector | none | yes | yes | yes |
AutoMonitor sits in a peculiar spot. It is technically less invasive than PostHog session replay (no keystrokes, no mousemove, no input values). It is also less constrained than any commercial product on this list. There is no consent UI on any page that runs it. There is no published privacy policy or SORN for it. There is no public documentation of what the receiver does with the payloads. And the script monkey-patches three browser primitives unconditionally โ a level of instrumentation normally reserved for either application performance monitoring tools (which are usually opt-in and documented) or for tools whose explicit purpose is to capture user behavior comprehensively.
Who can ship code here โ and what's documented about them
The AutoMonitor script and the rest of the JavaScript shipped from cdn.infra.ndstudio.gov are produced by the National Design Studio's engineering staff. Most of that staff moved over from the Department of Government Efficiency. None of them require Senate confirmation, none appear on White House salary reports, and the Executive Office of the President has no inspector general.
One of those engineers has a documented history that bears on what a reader should weigh when reading the technical findings above. Edward Coristine is a former DOGE engineer (now full-time federal employee, GS-15) who moved from DOGE through SSA to NDS. Per the Borges SSA whistleblower disclosure footnote 3 โ which cites Krebs on Security (2025-02-28), Bloomberg (2025-02-07), and a series of Wired articles โ Coristine:
- was part of the cybercrime group "The Com," responsible for multiple privacy breaches and fraud;
- operated a Telegram handle that in 2022 hired a hacker to conduct a DDoS cyberattack;
- was fired from cybersecurity firm Path Networks for allegedly leaking secrets to a competitor;
- owns Tesla.Sexy LLC, which operates Russian-registered domains running an AI Discord bot in the Russian market.
None of this means Coristine wrote the AutoMonitor source we analyzed in the preceding sections โ we have no commit-level attribution. What it means is that the personnel pipeline that ships code to ndstudio.gov, which has no inspector general and no Senate confirmation, includes someone whose pre-government history is publicly documented in these terms. The Borges disclosure also separately documents that Coristine resigned from DOGE in June 2025 and reappeared at SSA "days later," and that on 2025-08-11 he was one of three DOGE-affiliated names the SSA Chief Data Officer asked for information about an unsecured cloud copy of 300+ million Americans' Social Security data โ and received no response.
Receipts: claim 66 ยท claim 80 ยท people roster โ Edward CoristineWhat we'd test next
- Render the NDS sites in a headless browser and capture the full set of network calls โ that resolves the "Next.js may load trackers in chunks" gap for realfood / americabydesign / retire.opm.
- Read the PostHog initialization on TrumpRx โ specifically whether session replay is enabled (default) or disabled. The presence of
posthog.init({ disable_session_recording: false })would settle the most invasive question outright. - Diff the AutoMonitor script week-over-week โ if new event types or fields appear, that's the most direct signal of scope creep.
- Test the analytics endpoint with a synthetic browser session โ if we can capture a real
sendBeaconpayload from inside Chrome DevTools, we can compare it to what the script claims it sends. (No tampering โ just observation.)
All scans, source code, and live URL probes referenced here were fetched and SHA-256 hashed before being cited; the hash appears next to the receipt at each callout. The full AutoMonitor source is sha256 cb4da9fc156568b09cdf9f01d4f343e204cbd4116db068e65099c74ba3ebb78e. Live URLs are linked at each callout.