---
title: "Eleven domains nobody approved"
description: "Ask an agent to add analytics and it adds a tag. Do that four times over six months and your page is executing code from hosts no one on the team chose, on your origin, with your users' data."
url: https://harpywatch.com/blog/eleven-domains-nobody-approved
kind: article
author: "The HarpyWatch team"
published: 2026-09-17T09:00:00+00:00
tags: ["AI Agents", "Monitoring", "Reliability"]
publisher: "HarpyWatch"
---

# Eleven domains nobody approved

Open the network tab on your marketing site and count the distinct hostnames it
contacts. Then try to say, for each one, who added it, when, and which of them
you could remove today.

Most people can account for three or four. Ordinary marketing pages routinely
contact more than twice that, and the gap between the list and the memory is
the subject of this piece.

## How the list grows

Nobody sits down and decides on eleven domains. They arrive one reasonable
request at a time.

"Add analytics" gets you a tag. "We need to know which ads convert" adds a
second, from a different vendor, which loads its own partner pixel at runtime.
"Add live chat" brings a widget that pulls its own font and its own error
reporter. Someone trials a heatmap tool for a fortnight and the trial
ends but the snippet does not. A tag manager is installed so that marketing can
add tags without engineering, which works exactly as designed: tags get added
without engineering.

None of this needed AI. What a coding agent changes is the friction — the hour
of someone's attention that used to sit between "we should track that" and a
script tag in production, and that was the only brake on the process. Asked to
add analytics, an agent finds the vendor's documented snippet and puts it in
the layout template, which is what the vendor's own quickstart tells it to do.
It will not ask whether the previous three analytics tools are still needed, or
notice that two of them now collect the same events, because it was asked to
add a thing and the thing was added.

You can build a test that fails when a page acquires a new outbound domain. It
takes a browser-based test that inspects network requests, or a
`Content-Security-Policy-Report-Only` header with somewhere to send the
reports. Neither is on by default in the frameworks we have used, and neither
appears in the change that added the tag.

Six months of that, and the honest answer to "why does the page contact this
host" is that a ticket, once, implied it.

## What it costs, in three separate ways

These are usually discussed as one problem. They are not — they have different
owners and different fixes.

**Performance.** A third-party script is a request to a host you do not
control, resolved through a DNS lookup and a TLS handshake you cannot warm, on
a connection whose latency and availability are somebody else's operational
concern. A synchronous tag in the head blocks parsing. A tag that injects
further tags at runtime is a request chain your build has never seen and your
performance budget cannot account for, because the second and third hops do not
exist until the first one runs. The heavy part is rarely the bytes; it is the
main-thread time spent parsing and executing them — on a low-end Android
handset, not on the machine you tested it on.

**Privacy and compliance.** Every one of those hosts receives, at minimum, the
visitor's IP address, user agent, and the full URL of the page — which on a
search results page or an account page can carry more than you meant. Under
GDPR that is processing personal data by a third party on your behalf, which
needs a lawful basis, a record, and usually a line in a privacy policy — and
whether the vendor is your processor or a controller in its own right is a
question with real consequences that most teams have never asked about half
their tags. Your consent banner blocks the tags it
knows about. It does not block the pixel that the tag it allowed loaded on its
own initiative. The organisation's stated position and the browser's actual
behaviour drift apart quietly, and it is often somebody outside the
organisation who notices first.

**Supply chain.** A `<script src>` from another origin is not a dependency in the sense
that an npm package is. It is not pinned, not reviewed, and not the same file
tomorrow. The vendor can change it at any moment, and so can anyone who
compromises the vendor, or buys them, or lets a domain lapse. That code then
runs with your origin's privileges: it can read the DOM, read cookies not
marked `HttpOnly`, and read anything a user types into your forms. The
canonical version of this attack is card skimming injected into a third-party
script on a checkout page — the Magecart pattern — and it is still in use
because it works. The site's own code is perfect throughout. This is the risk
that gets the least attention of the three, and it is the one that ends in a
breach notification.

## Why an audit does not fix it

The usual response is to do an inventory. Someone spends a day, produces a
spreadsheet, removes three tags, and everyone feels better.

That spreadsheet is accurate until the next release, and in a stronger sense it
was never accurate at all: it recorded what the page loaded on one machine, in one
country, in one consent state, on one day. Third-party behaviour is
conditional. Tags fire on some pages and not others, for some geographies and
not others, after some interactions and not others. A vendor that loads one
partner today loads four next quarter because they signed a deal, and nothing
in your repository changed.

The thing you actually want to know is not "what did we install". It is **what
does a browser contact when it loads this page, right now** — and you want to
know it as a continuous fact, so that a new hostname appearing is an event
somebody sees, rather than an archaeological finding six months later.

That is a monitoring question, not an audit question, and it has to be asked
from outside. A build-time inventory cannot see a script that injects another
script at runtime, because that only happens in a browser.

## Watching it as a fact

HarpyWatch is our own product, so read this knowing that. Its
`third-party-data-flow` check loads the page in a browser and records the
origins it contacts, so the set is observed rather than declared. `page-weight`
measures what actually arrived over the wire, which is where a script that has
quietly tripled shows up as a number rather than a feeling. `csp-policy`
reports what your Content Security Policy permits: the gap between the origins
you allow and the origins you use is the size of the hole left open. And
`cookie-flags` and `mixed-content` catch the two ways a third party most often
degrades the security of a page it was only visiting.

The limits are worth stating. An external check sees what a fetch of a public
URL sees; conditional tags that only fire on a logged-in account page, or only
for one country, are outside its view. And knowing that a domain is contacted
is not the same as knowing what was sent to it — that still needs someone to
open the request and look.

What you get is the list as it is today rather than as anyone remembers it,
and a signal when it changes. Deciding whether the change was approved is still
a person's job. The difference is that the question gets asked while somebody
can still remember the ticket.
