EresusSecurity
Back to Research
Supply Chain Security

Third-Party Scripts Run As You, Inside Your Own Origin

Yiğit İbrahim SağlamOffensive Security Specialist
November 4, 2026
14 min read
Assessment PlaybookApplication Security

Third-Party Scripts Run As You, Inside Your Own Origin

Every commercial web page today is a federation. The application the team wrote is often a minority of the JavaScript that actually executes on it. The rest arrives at runtime from analytics vendors, tag managers, chat and support widgets, consent management platforms, session replay tools, ad pixels, fraud scoring SDKs, customer-support portals, and payment iframes. Some of it is loaded by your <script> tags; much of it is loaded by other scripts, which means your source code does not contain a complete list of what runs in your customers' browsers.

Core assessment: a third-party script is not a cosmetic dependency. Once it executes on your page, it runs with your origin's privileges and your user's authenticated session. It can read the DOM, attach listeners to your password and card fields, rewrite your forms, change what your page tells the user is true, and send anything it likes to any host the network permits. This is the same trust relationship as a compromised server-side dependency, except it is delivered to authenticated end users, it is visible to nobody in your CI pipeline, and the artifact frequently changes several times per day without a deploy. The reason it gets missed is not ignorance — it is that four different controls each cover the risk from one angle and none of them cover it from the angle that matters: your own CSP allows the origin, your WAF never sees the code, your SAST never fetched it, and your software bill of materials describes the server.

Why This Is a Privileged Code Path

The mental model that breaks is "third party" implying "outside". On the client, third-party content that executes is inside.

Capability a page-origin script has What that enables
Read and mutate the full DOM Harvest form values before submit, including fields the app never sends to the backend
document.cookie on a non-HttpOnly cookie Steal or replay the session token, not just credentials
fetch/XMLHttpRequest/WebSocket from the user's browser Exfiltrate over the same HTTPS origin the page already talks to, or to any host not blocked by CSP
Event listeners on submit, input, click Silent duplicate-and-send of anything typed, and clickjacking of your own UI
Rewrite page text and prices Show the user one number and submit another
Inject new <script> nodes Pull in arbitrary second-stage code, bypassing the original tag's integrity pin
Access window.name, localStorage, sessionStorage, IndexedDB Read anything the app cached client-side, including tokens and PII
PostMessage to and from iframes Bridge into payment and support contexts that are treated as separate trust zones

Note the last two especially. A surprising number of applications put JWTs, refresh tokens, decoded user profiles or full address records into client-side storage for convenience. Any script on the page can read them. That converts a vendor breach into a token-theft event at scale, without a single request touching your infrastructure.

The Realistic Delivery Paths

There are four ways hostile code ends up inside a tag your page loads, and only one of them involves your team making a mistake.

1. Upstream compromise of the vendor. The vendor's build system, package account, CDN bucket, or admin panel is breached. Your tag URL is unchanged, the response is still served from the correct domain with a valid TLS certificate, and the content is now hostile. This is the hardest case, because nothing about your configuration was wrong.

2. Self-service mutation. Many marketing and analytics platforms let a customer or a vendor-side operator push a rule, snippet or "custom HTML tag" to the delivered container with no code review and near-instant propagation. A single compromised account, or an insider, can change what thousands of sites render. The delivery mechanism is designed for speed, not for review.

3. Chain loading. You load one script; it loads six. The tag you pinned is not the code that runs. Load-order randomness, A/B branches, and geo or user-agent conditionals mean different visitors get different code. This is why "we checked it once" is not a control.

4. Local transport tampering. Where scripts arrive over plain HTTP, or from an origin reachable inside the visitor's network, they can be replaced in flight. Rare on modern public surfaces, still routine on captive portals, hotel and conference networks, and in regions where an intermediary is trusted by the client's root store.

There is also a fifth, non-malicious case worth budgeting for: the vendor is fine and the script is buggy. An infinite loop, an unbounded setInterval, a listener that holds a closure over sensitive data and never releases it, or a retry storm that turns a page into a cost incident. Availability and cost failures from tags are more frequent than hostile ones and are handled by the same inventory.

Why Standard Controls Miss It

This is where most discussions go wrong, so be precise about each control's blind spot.

  • Content Security Policy. CSP constrains which origins may execute. If the vendor's origin is allowlisted — and it must be, or the feature breaks — then any code that origin serves is trusted. Nonces and hashes do not help either: they are generated at render time for scripts your server knows about, not for code fetched later by a tag manager. strict-dynamic makes this deliberately worse for auditability, because it legitimately permits runtime-injected scripts. Reporting (report-uri) is still valuable — it tells you what would have been blocked, which is how you discover a script nobody inventoried.
  • WAF / RASP / network edge. The hostile code is a legitimate response from a legitimate domain, requested by the browser, executed locally, with exfiltration disguised as analytics beacons. The WAF never sees the payload and cannot see what the script does after it lands.
  • SAST / DAST. SAST scans your repository, which does not contain the tag's contents. A DAST crawler that does not authenticate, scroll, or execute long enough will never trigger deferred tag loads at all. Even a thorough crawler only sees one branch of an A/B split.
  • SBOM / dependency scanning. These describe build-time components. A script element pointing at a vendor origin is not in the artifact, has no version, and changes on the vendor's schedule.
  • Subresource Integrity. The only control here that actually works — and the one almost nobody applies, for a reason discussed below.
  • Vendor security questionnaires. These answer "do you have a programme" and never "what did your tag serve between 03:00 and 03:20 UTC on Tuesday".

How to Establish the Facts

The order matters. First measure, then constrain.

Step 1 — Build a runtime inventory, not a source inventory. Load the real pages, authenticated, and enumerate what actually executed: every <script> element with a resolved URL, plus performance.getEntriesByType("resource") filtered to script initiators, plus dynamically inserted nodes captured by a MutationObserver on documentElement. Do this per page template, not once, and repeat it over days so A/B branches surface. Expect the list to be longer than anyone's estimate and to contain at least one entry nobody owns.

Step 2 — Attribute every entry to an owner and a purpose. The three useful questions are: which internal team requested this, is it required for a revenue or legal function, and what happens if it fails to load. A large share of tags will fail all three. That list is your removal candidate, and removal is a cheaper control than monitoring anything.

Step 3 — Map data exposure per script. For each tag, record what is readable at the moment it executes: is the user authenticated, is a form in the DOM, do payment or identity fields exist on the page, does anything sensitive live in client storage. Then split the inventory by risk band. A marketing pixel on an authenticated checkout page and the same pixel on a public blog post are not the same risk, and CSP granularity should reflect that.

Step 4 — Test the egress assumption. Whether the tag can send data anywhere, or only to its own origin, is a policy question you can turn into enforcement. Compare observed beacons to declared ones. An undocumented POST to a host that no one listed is the highest-signal finding in this whole exercise.

Step 5 — Detect changes, not content. Continuous monitoring of delivered bytes is the realistic answer: hash what each tag serves on each fetch, alert on change, and require the change to be justified before it reaches production users. Two enforcement mechanisms exist and they are complementary:

  • Pin, and fail closed. Subresource Integrity on the tag means the browser refuses to run anything that does not match. This is genuinely strong and has a real cost: vendor deploys break your site. Mitigate it by pinning only the high-risk subset (checkout, auth, account pages), using fetchpriority plus a fallback, and negotiating a vendor change-notification channel.
  • Hash, record, alert. Where pinning is impractical, keep the hash history server-side, send it with your own page-load telemetry, and treat unexplained changes as incidents. You lose prevention and keep detection.

Hardening, in the Order That Actually Pays

  1. Delete first. Every removed tag is a permanently closed path. Most sites can remove a third or more of their scripts with no measurable business loss once anyone actually looks.
  2. Never let the app store secrets client-side. Tokens in localStorage are the amplifier that turns tag tampering into account takeover. HttpOnly, SameSite, scoped and short-lived cookies plus rotating refresh remove the prize.
  3. Serve tags yourself. Proxy vendor scripts through a first-party origin you control, fetched server-side or through an edge worker you own. This gives you a place to hash, cache, block, log and version what would otherwise be opaque, and it collapses the CSP allowlist dramatically. It also breaks vendor analytics quietly, so budget for reconciliation.
  4. Isolate where it must run untrusted. Consent tools, chat widgets and support portals rarely need DOM access to your forms. Put them in iframe or shadow DOM boundaries, give them their own CSP via iframe-src, and hand them a postMessage contract that only carries the minimum fields.
  5. Use separate policies per template. Login, checkout and account pages get a short allowlist, no unsafe-inline, and pinned tags. Marketing pages get the loose policy. The blast radius difference is the whole point.
  6. Move to the Reporting API. Report-To plus CSP Reporting gives structured, sampled violation data at production scale. Use it to discover injected scripts before you use it to tune the policy.
  7. Add a browser-side tripwire. A small first-party script that loads before vendors, snapshots the form fields it considers sensitive, and watches for unexpected submit handlers, cloned nodes, or sendBeacon calls from non-allowlisted origins. It is bypassable — a competent attacker removes the watcher — which is exactly why it belongs alongside pinning and egress policy, not instead of them.
  8. Put vendor change terms in the contract. Change notification windows, a documented build provenance, an SBOM per release, and a right-to-audit clause. The commercial lever is the only lever that reaches failure mode one.

What This Means for Testing Scope

This is the part that regularly causes arguments during scoping calls, so state it plainly.

Third-party script behaviour is testable inside an authorised engagement, and it is frequently out of scope by accident. You can, on your own pages: enumerate what executes, attribute it, check what it can read, verify whether it is pinned, confirm CSP and cookie flags, and attempt a benign demonstration that a DOM value the app believes is private is reachable from a tag. What you cannot do is attack the vendor: probing their infrastructure, their CDN or their admin panel requires that vendor's written authorisation, which is a separate agreement.

That distinction has a practical consequence for how a finding should be reported. "A vendor could be breached" is not a finding; it is unfalsifiable and belongs in a risk register. "The script served on the checkout page today can read the card holder name field and posts to an origin that appears nowhere in your configuration, and nothing pins or monitors it" is a finding with a reproducible proof, a business impact and a fix.

A useful rule of thumb: if the engagement's scope document mentions URLs and authentication levels but not "third-party code executed on those URLs", the scope is incomplete. Ask for it to be added before the test begins — it changes what evidence you need to collect, not just what you get to test.

Quick Decision Table

Situation Realistic answer
Public marketing pages, no auth, no forms Inventory, prune, report-only CSP. Low effort, low urgency
Authenticated app pages, sensitive data in client storage Fix the storage first; it dominates the tag risk
Checkout or payment page Pinned tags, shortest possible allowlist, iframe isolation for everything else
Vendor refuses change notification Proxy the tag through your own origin so you can version and gate it yourself
Tag manager already in place and cannot be removed Own the container: lock who can publish, log every container revision, alert on propagation
"Legal/finance requires this pixel" Reduce its privileges and origin reach; you rarely get to remove it, but you almost always can shrink it
Compliance regime demands a control here Evidence the runtime inventory plus the change-detection pipeline; a questionnaire is not a control

Frequently Asked Questions

Does a strict Content Security Policy stop third-party script compromise?

No, not by itself. CSP restricts which origins may execute code, so a hostile payload served from an origin you already trust is executed with your approval. Nonce and hash based policies do not cover scripts fetched later at runtime. CSP still earns its place — tighten the allowlist to as few script sources as possible, add a report endpoint, and use per-template policies so authenticated pages get stricter treatment than public pages.

Will Subresource Integrity break my site when the vendor deploys?

Almost certainly, which is why it is applied selectively rather than everywhere. Pinning means the browser refuses to run a tag whose bytes differ from the recorded hash, so an unannounced vendor deploy turns into a broken feature on your side. The practical pattern is to pin the tags on authenticated and transactional pages, hold a fallback path, and negotiate a change-notification channel with the vendor so pin updates are planned rather than discovered at 4 a.m.

Can a vulnerability scanner find this class of issue?

Not reliably. Scanners inspect what your server returns for the requests they make, and third-party tags load conditionally, late, and often only for authenticated or geographically-scoped visitors. A scan without an authenticated session and a long-lived browser will miss deferred loads entirely, and will only ever observe one branch of any A/B split. Establishing the facts needs a real browser, a real session, and repetition over time, which is an assessment activity rather than a scan.

How is this different from a compromised npm dependency?

The failure domain. An npm dependency is fetched at build time, versioned, visible in your lockfile, and covered by your SBOM, so the controls that find it are supply-chain scanning and provenance. A browser-side tag is fetched at page-view time by the visitor, unversioned, absent from your repository, and changed on the vendor's schedule, so no build-time control reaches it. Both are privileged code paths; only one of them is discoverable by inspecting your artifact.

Should I test third-party scripts in a penetration test?

Yes, and get it into the scope document in writing before testing starts. Testing what executes on your own pages is within authorisation. Testing the vendor's infrastructure is not, unless that vendor separately authorises it, so plan the engagement around attribution, data reachability, pinning and monitoring gaps rather than around attacking upstream. A finding that names a specific script, the exact field it can read, and the destination it posts to is actionable; a finding that says vendors can be breached is not.

Next Step

If you want the facts rather than the policy argument, the smallest useful engagement is a runtime script inventory across your authenticated and transactional templates, with owner attribution, data-reachability evidence and a change-detection recommendation per tag. Eresus Security runs this as part of web application penetration testing and as a standalone scope for teams who need the answer before they argue about CSP. See Eresus Guard for how we handle evidence, and contact us to scope it.

AI Security Starter Training

Request a practical checklist for prompt injection, RAG data leakage, MCP risks, and model-file security before launch.

Prompt injection and guardrail bypass checks.
RAG data leakage and permission-boundary review.
MCP identity, transport, and command-risk controls.

No spam. Used only to send the resource and related security notes.

Related Research

Related Services

Scope Estimator

Get a rough engagement size before the scoping call.

Estimated engagement

5–7 days

Request exact scope