Third-Party Scripts and the Supply Chain
A script tag grants full authority over your page. There is no partial trust, and the dependency you never chose is running in the same context as your login form.
The intent, the obvious build, and why it breaks
Every lesson starts where the work starts: someone wanted an outcome, and the first implementation that comes to mind has a problem.
What am I actually granting when I add someone else's code to my page?
Marketing wants analytics, support wants a chat widget, and finance wants a payment script. All three are reasonable asks that arrive as a one-line snippet.
Paste the snippet in the head. It is one line and the vendor is a well-known company, so the risk is theirs.
A script tag runs with your page's full authority. It can read every cookie your JavaScript can read, every field in every form, and everything in browser storage. There is no scope on that grant (The Browser Security Model).
- A
scripttag runs with your page's full authority. It can read every cookie your JavaScript can read, every field in every form, and everything in browser storage. There is no scope on that grant (The Browser Security Model). - You did not audit the code, and it is fetched fresh on every load — so what you reviewed and what runs are different things, indefinitely.
- Most third-party scripts load further scripts from origins you were never told about, so the trust boundary you extended is not the one you thought.
- A compromise anywhere in that chain is a compromise of your login form, and it is invisible in your own repository and your own review process (Cross-Site Scripting).
- It executes on your main thread, on a schedule you do not control and cannot profile from outside (Long Tasks).
What is actually happening
In the browser, not in the framework.
- The same-origin policy protects you from *other pages*. It does nothing about code you deliberately execute inside your own origin — a third-party script is same-origin the moment you include it (The Same-Origin Policy).
- The browser offers exactly two coarse grants: inline in your page (full authority) or inside a cross-origin iframe (its own origin, its own storage, no access to your DOM). There is no middle setting on a plain script tag.
- Subresource Integrity lets you pin a hash so the browser refuses a file whose bytes changed. It protects against a modified file at a known URL; it does not protect against a vendor deliberately shipping a new, malicious version at a new URL, and it cannot be used for a script whose content changes by design.
- CSP constrains which origins may execute at all, which turns "anyone who compromises any of our vendors" into "anyone who compromises the vendors we listed" (Content Security Policy).
- The supply chain extends to build time as well: a dependency pulled in by your bundler runs with the same authority, and arrives without even a snippet to review (The Module Graph).
What this makes the browser do
And which of it is avoidable.
- An extra origin means a DNS lookup, a connection and a TLS handshake before a single byte of the script arrives — often on the critical path (Reading a Network Waterfall).
- Parsing, compiling and executing code you did not write, on the thread that owes the user a frame (The Real Cost of JavaScript).
- Whatever the script then does: DOM mutation, layout reads, its own network requests, its own timers, and often its own further script loads.
- Memory retained by a widget for the life of the page, outside anything your profiling attributes to your own components (Memory Leaks).
There is no partial trust
The platform gives you two settings, and the gap between them is enormous. A script in your page is you: same origin, same cookies, same storage, same DOM, same API access. A cross-origin iframe is someone else: separate origin, separate storage, no reach into your document at all.
Almost every third-party integration decision is really a decision about which of those two you are choosing, and it is usually made by pasting whichever snippet the vendor supplied. Making it deliberately is most of the work.
- The middle column is the whole lesson:
asyncanddeferchange *when* code runs, never *what it may do* (`defer`, `async` and `type="module"`). - A vendor that "cannot work in an iframe" is telling you it needs access to your page. That may be legitimate; it is always worth hearing out loud.
- A build-time dependency is a third-party script with worse ergonomics for review — it never even appears as a tag someone could notice.
| Grant | Can read your DOM and forms | Can read cookies and storage | Can call your API as the user | Contained if compromised |
|---|---|---|---|---|
Inline script tag | Yes, entirely | Yes, everything script-readable | Yes — credentials attach automatically | No |
Async / deferred script | Yes, entirely | Yes | Yes | No — timing changes, authority does not |
Cross-origin iframe | No | No — separate origin | No | Yes |
iframe with sandbox | No | No | No | Yes, plus restricted capabilities |
| Build-time dependency | Yes, entirely | Yes | Yes | No — and no snippet to review |
Reducing the grant
Because the platform's grants are coarse, the practical work is choosing the smallest one that still delivers the feature, and then constraining what is left. These three controls compose, and each covers something the others do not.
What does this vendor genuinely need access to?
when Chat widgets, embedded media, ads, payment fields.
cost Cross-origin iframe. Real containment, and integration becomes explicit message passing with no shared styling (Clickjacking and Framing).
when Any field whose value you would rather never possess.
cost A provider-hosted iframe means the data never enters your DOM, which changes your risk and compliance position more than any policy can.
when Analytics, error tracking, performance monitoring.
cost It runs in your origin, so constrain it: CSP allowlist, SRI where the file is stable, loaded off the critical path, and an owner who can answer for it (Content Security Policy).
when Session replay, personalisation, A/B tools that rewrite content.
cost Full authority over the page, including every form. Treat it as a code dependency with a named owner, an inventory entry and a review — and consider excluding it from pages handling credentials or payment (Session Replay and the Privacy It Costs).
when Anything your bundler pulls in.
cost Same authority, less visibility. Lockfiles, provenance and a real update policy are the equivalent controls (The Module Graph).
1<!-- full authority over the page: DOM, forms, cookies, your API -->2<script src="https://widget.example.com/chat.js"></script>3 4<!-- contained: its own origin, its own storage, no reach into your DOM -->5<iframe6 src="https://widget.example.com/chat-frame"7 sandbox="allow-scripts allow-forms allow-same-origin"8 title="Customer support chat"9 loading="lazy"10></iframe>The title is not decoration — an untitled iframe is an unlabelled region for a screen reader, and third-party embeds are where that omission is most common.
What actually goes wrong
These are the recurring incidents, and what is striking about them is how few involve anyone attacking you directly. Most are a vendor's change, a vendor's outage, or a vendor's dependency — arriving in your page through a door you left open years earlier and stopped thinking about.
| Trigger | Symptom | Cause | Response |
|---|---|---|---|
| Vendor ships a new version | Page breaks, or starts doing something new, with no deploy on your side | A live CDN reference means their release schedule is your release schedule | Pin a version, add SRI where the file is stable, or vendor the dependency into your own build. |
| Vendor origin is slow or down | Your page stalls before rendering | A synchronous script in the head blocks parsing on a host you do not control | Never load third-party code synchronously on the critical path (Why a Script Tag Stops the Parser). |
| Tag manager used by a non-engineer | Unknown scripts in production; CSP violations from origins nobody recognises | The tag manager is a mechanism for adding arbitrary code without review | Treat tag-manager changes as deploys: review, allowlist, and inventory them (Deploying a Frontend). |
| Upstream package compromised | Malicious code shipped to every user, signed by your build | A transitive dependency updated inside a permitted range | Lockfiles, deliberate updates, and a CSP that limits where exfiltration could send anything. |
| Chat widget injected at end of body | Keyboard users tab through the whole page to reach a floating button; focus is trapped once they do | DOM order, not visual order, determines tab order — and the vendor tested with a mouse | Constrain it in an iframe, give it a title, and test the flow by keyboard (Keyboard Operability). |
| Session replay on a checkout page | Card and personal data captured into a vendor's store | Replay captures what is on screen; masking is opt-out-shaped and fails open | Exclude sensitive routes entirely; never rely on field-level masking as the only control (Session Replay and the Privacy It Costs). |
How to build it
Most important first.
- Ask what authority the thing actually needs. Analytics needs to observe and report; it does not need to read your form fields. If a vendor cannot work inside an iframe, that is information about what it is doing.
- Sandbox what can be sandboxed. A cross-origin iframe with an explicit
sandboxattribute is the only real containment the platform offers, and a chat widget or an ad is usually a candidate (Clickjacking and Framing). - Constrain the rest with CSP. An allowlist converts an unbounded risk into an enumerated one, and the violation reports tell you when something unexpected tried to run (Content Security Policy).
- Use Subresource Integrity for any third-party file whose contents are supposed to be stable, and understand precisely what it does and does not cover.
- Load it off the critical path unless it genuinely must run first.
deferor dynamic injection after interactivity removes the failure mode where a slow vendor delays your own page (`defer`, `async` and `type="module"`). - Keep an inventory. Teams routinely cannot answer "what third-party code runs on our checkout page", and that question is the whole of this lesson.
- Prefer a vendored, versioned dependency over a live CDN reference where you can: it moves the change into your review process instead of leaving it in someone else's deploy pipeline.
Keyboard, focus, semantics, announcement
A required field on every lesson in this domain, not a section added when there is room.
- Third-party widgets are a common source of accessibility regressions you did not write and cannot easily fix: chat bubbles that trap focus, cookie banners that are not dismissible by keyboard, and overlays that cover content without being announced (Focus Management).
- A widget that injects itself at the end of the body lands last in the tab order regardless of where it appears visually, so keyboard users reach a floating button after the entire page.
- Ironically, "accessibility overlay" scripts are themselves a well-documented cause of harm — they override real semantics with guesses, and assistive-technology users frequently report them as making sites less usable, not more. Fix the markup instead (Semantics Before ARIA).
- A vendor script that blocks the main thread stops accessibility-tree updates for as long as it runs, so a screen reader reads stale content (The Multi-Process Browser).
- You own the accessibility of your page including the parts you did not write. "It is the vendor's widget" is an explanation, not a defence.
What can go wrong
- A tag manager that lets non-engineers add arbitrary scripts to production, bypassing review entirely — the most common way a page acquires code nobody knows about.
- A vendor outage taking your page down because their script was synchronous and render-blocking (Render-Blocking Resources).
- SRI applied to a URL whose content is intended to change, so the page breaks the next time the vendor ships.
- A CSP written permissively enough to satisfy every vendor, which is a CSP that permits everything.
- A widget that adds unremovable listeners and grows memory for the life of the session, attributed in profiles to "the page" rather than to the vendor.
- A build-time dependency compromised upstream, which ships to every user with your own signature on it.
- A third-party script that mutates the DOM can race your own rendering, producing hydration mismatches and elements that appear and vanish (Hydration Mismatch).
- Asynchronously loaded vendor scripts execute in arrival order, so behaviour differs between fast and slow networks in ways that are hard to reproduce (`defer`, `async` and `type="module"`).
- A third-party script can read anything your own script can read:
document.cookiefor non-HttpOnlycookies, every value in Web Storage, and the contents of every input on the page (Storage Security and Durability). - It can add its own listeners to your forms, so a keylogger is a few lines and is indistinguishable from analytics in a network trace.
- It can perform authenticated requests to your own API, because it runs in your origin and the browser attaches credentials automatically (Cross-Site Request Forgery).
HttpOnlycookies are meaningfully better here: they are unreadable by any script, including one you did not intend to run (Cookies vs Script-Readable Tokens).- Payment and credential fields deserve a stronger boundary than a policy. If a form takes card details, an iframe from the payment provider means the data never enters your DOM at all, and that is a materially different risk position.
- "It is from a reputable company, so it is safe." The question is not whether they are trustworthy but whether their entire delivery chain is, forever, and whether you would notice if it stopped being.
- "CSP means we are covered." CSP limits which origins may execute. An allowed origin that is compromised is still fully allowed (Content Security Policy).
- "SRI makes third-party scripts safe." It makes a specific file immutable. It says nothing about what that file does.
- "It is only analytics." Analytics scripts have full DOM access by construction, which is why they are a recurring vector.
- "The vendor's widget is the vendor's accessibility problem." It is on your page, so it is your users' problem, which makes it yours.
Measuring it, and what changes in the field
- A CSP in report-only mode is the cheapest inventory tool available: it tells you every origin your pages actually load, including the ones nobody documented (Content Security Policy).
- The Network panel grouped by domain answers "who is on this page" in about ten seconds (Debugging the Network).
- Third-party main-thread time is broken out in performance tooling; it is usually a larger share than teams expect (Measure Before Optimising).
- Field data segmented by whether a vendor script loaded tells you what it actually costs real users (Real User Monitoring).
- On a slow device, third-party execution competes directly with your own and the effect is multiplied (The Real Cost of JavaScript).
- On a slow or filtered network, a vendor that fails to load must degrade gracefully — and a synchronous script that fails to load can stall the page entirely.
- On a page handling credentials or payment, the same script that is an acceptable risk elsewhere is not.
- An iframe gives real isolation and costs integration: no shared styling, no direct DOM interaction, and communication only by explicit message passing.
- A strict CSP is genuinely effective and genuinely ongoing work — every new vendor is a policy change, which is a feature disguised as friction.
- SRI protects a fixed file and forbids the vendor from shipping fixes silently, which is exactly the trade you are making and should make deliberately.
- Removing a vendor is usually a product conversation, not a technical one. The engineering contribution is making the cost visible.
Where this applies
Frontend advice ages badly and fragments across engines. These labels say what each claim is specific to, and where a different browser, device or framework would differ.
- GENERALThat an inline script tag carries the page's full authority follows from the origin model itself, so it holds in every browser and cannot be softened by configuration.
- BROWSER-SPECIFICTracking protection, extension behaviour and third-party storage partitioning differ substantially — Safari and Firefox block or partition far more by default than Chromium does — so a widget that works in your browser may be silently degraded for a large share of real users (Storage Security and Durability).
- SPEC-EVOLVINGThird-party cookie and storage partitioning is actively being tightened across engines, so any vendor whose functionality depends on cross-site state should be treated as being on a deprecation path rather than as a stable dependency.
Where the depth lives
This domain teaches the browser-side mechanism and hands the rest off.
- — Testing & Reliability Engineering — a synthetic check that fails when an unexpected origin appears is the cheapest continuous inventory a team can have.
- — Software Design — a vendor integration is a dependency with an owner and a lifecycle, and treating it as a one-line snippet is what lets it outlive everyone who understood it.