Safari 27 now blocks a category of third-party domain outright, at the network level, on by default. Whatever you think of that, it leaves every team with the same question: do we have one of those on our pages?
Most teams cannot answer it, and not because they are careless. These things arrive through a tag manager or a marketing vendor, which means the thing that added them is configuration rather than code. Reading your repository will not tell you, and the answer can change without a deploy.
So here is how to actually get the list, and the four things that quietly stop you.
The cheap version, and what it misses
The network panel answers it for one page load, on your machine, on the path you happened to take, with your extensions and your cookie state. That is genuinely useful and it is not an audit. The request that only fires for logged-in users on the checkout page is not in it.
A better local version is Resource Timing, because it enumerates what the page actually fetched rather than what you watched it fetch:
performance.getEntriesByType('resource')
.map(e => new URL(e.name).origin)
.filter((o, i, a) => a.indexOf(o) === i && o !== location.origin)
.sort()
MDN is explicit that this covers XMLHttpRequest, images and scripts alike, and that cross-origin resources do appear in the list. Many of their timing fields are restricted to 0 without a Timing-Allow-Origin header, but you are reading name, not timings, so that restriction does not cost you anything here.
Trap one: the buffer is small. MDN: "By default only 250 resource timing entries are buffered." A heavy page passes that without trying, and the entries you lose are silent. If you want all of them, observe instead of sampling:
new PerformanceObserver(list => {
for (const e of list.getEntries()) console.log(new URL(e.name).origin);
}).observe({ type: 'resource', buffered: true });
The version that covers pages you never visit
Everything above runs where you are standing, which is the thing that makes the original problem hard. The answer that covers every page and every real visitor is a response header, and it blocks nothing.
Content-Security-Policy-Report-Only "helps to monitor Content Security Policy (CSP) violations and their effects without enforcing the security policies", in MDN's words. So you can declare a policy you know is wrong, deliberately, and collect what breaks it without breaking anything.
Content-Security-Policy-Report-Only: connect-src 'self'; report-to csp-endpoint
That says connections should only go to your own origin, which is false, and that is the point. Every outbound connection to anywhere else is reported to you, from production, from real browsers, on pages you have never opened.
Trap two, and it is the one that wastes an afternoon. From the same page: "The CSP report-to directive must be specified for reports to be sent: if not, the operation won't have any effect." A report-only header with no reporting destination is not a quiet success. It does nothing at all, and it looks exactly like a policy nothing is violating.
Trap three: report-to is the modern directive and is not universally supported. MDN declares both in its own example, noting that report-to is preferred over the deprecated report-uri "but we declare both because report-to does not yet have full cross-browser support". If you only set the modern one you will collect reports from some browsers and conclude the others are clean.
Trap four: you cannot do this from a <meta> tag. MDN states that this header "is not supported inside a <meta> element". If your deployment makes response headers awkward, the workaround you were about to reach for is silently ignored.
Why the list is worth having before you need it
Any one of these origins can stop answering for reasons that never reach you: a browser default, an extension, an endpoint security product, a corporate proxy, a network that resolves it differently.
When that happens, the request is refused in somebody else's browser. Your server sees nothing, because there is nothing to see. Your logs are complete and useless. What arrives is a person saying that signing up did not work, with no mention of a third party, because they cannot see one either.
Having the list already is the difference between that being a mystery and being a short conversation. It takes an afternoon once, and the alternative is learning which third parties you depend on at the moment one of them disappears.
Top comments (0)