JavaScript Security
Table of Contents
Working notes from 2012 on vetting third-party JavaScript (ads and widgets) before it lands on a host page. The 2012 material is kept as a snapshot: dead links point to archives, wrong claims are corrected in place, empty stubs are cut. Since then is the 2026 view.
1. Background
original said rogue HTML/XSS was "employed since before 1995"; JavaScript shipped in Netscape 2.0 in 1995 and CERT's malicious-HTML-tags advisory (CA-2000-02) is from 2000. History sniffing via :visited was closed in Firefox 4, Chrome 6, Safari 5, IE8 (MDN BCD css.selectors.visited.privacy_measures).
Security issues like rogue HTML, malicious JavaScript, and XSS are as old as JavaScript in the browser (Netscape 2.0, 1995). The CERT advisory on malicious HTML tags embedded in client web requests (CA-2000-02) put a name to the class in 2000.
There were a number of issues related to core JavaScript exploits that
came about with the increased use of JavaScript for Ajax in addition
to the release of MSIE6 (issues with arguments, eval, try/catch).
Issues relating to Ajax and history mining1 go back to 2006. The
CSS :visited half of history mining was closed by browsers in
2010–2011 (IE8, Safari 5, Chrome 6, Firefox 4): visited links may
differ only in colour, and getComputedStyle reports the unvisited
value2.
Much of the security work done on JavaScript was performed in 2007 and
2008 and the tools from that era reflect that work (limiting global
scope changes, binding of valueOf(), etc.). The primary concern here
is that the analysis performed for the implementations at that time
hasn't been updated to reflect current browsers.
The context for concerns about exploits has also changed: while in 2004 ads were directly injected into site contents, most major sites have moved toward iframe deployments. The counter to this is for third-party widgets: limited access should be provided.
Open questions for the review, as framed in 2012: confirming asset integrity, the worst case on compromise of the ad host or its DNS, and whether guest code can be kept from executing at all.
1.1. Exploits in the wild
"This example illustrates that library designers do not expect their functions to be called with untrusted arguments. This trust assumption is currently valid, as evidenced by the lack of actual exploits against Twitter, but becomes invalid when hosting ADsafe-verified advertisements." 3
The mechanism is small enough to run. The host page (here, Prototype.js
style) adds a method to a built-in prototype; guest code that passes a
blacklist verifier, because it never names eval, Function, or
constructor, calls that method and gets the Function constructor
back.
"use strict";
// Host page (Prototype.js style) extends a built-in prototype.
Function.prototype.evalScripts = function () { return this.constructor; };
// Guest code a blacklist verifier would pass.
const guest = () => [].map.evalScripts();
const Fn = guest();
console.log(Fn === Function, typeof Fn("return typeof globalThis")());
// The later answer: freeze the primordials before any guest code runs.
delete Function.prototype.evalScripts;
for (const ctor of [Object, Function, Array]) {
Object.freeze(ctor); Object.freeze(ctor.prototype);
}
try { Function.prototype.evalScripts = () => 1; } catch (e) { console.log("frozen:", e.constructor.name); }
console.log("evalScripts" in Function.prototype);
Run under Node 26.2.0 on 2026-10-08. The freeze at the end is what
SES lockdown() does across every intrinsic; see Since then.
1.2. Verifying correctness via ad hoc review
quote is verbatim from Taly, Erlingsson, Mitchell, Miller, Nagra, "Automated Analysis of Security-Critical JavaScript APIs", IEEE S&P 2011 (sp11.pdf, text extracted 2026-10-08).
"One reason why prior work has not focussed on verifying correctness of APIs is because present JavaScript, based on the 3rd edition of ECMA-262 standard, is not amenable to static analysis, for reasons discussed in section 2." 4
1.3. Communicate between frames / sandboxes
"Web browsers provide a platform for web applications. These applications rely on the browser to isolate frames from different security origins and to provide secure inter-frame communication." 5
2. Nomenclature
2.1. security-enforcing JavaScript
A subset or dialect of JavaScript that a verifier or rewriter can check: ADsafe, Caja, FBJS, Jacaranda. The list of languages that compile to JS was the 2012 starting point for finding them: https://github.com/jashkenas/coffee-script/wiki/List-of-languages-that-compile-to-JS
2.2. host
The page that embeds third-party (guest) JavaScript: the publisher, in the advertising case.
2.3. FBJS
A subset of JavaScript designed to run in a hosted container on Facebook.
2.4. secure mashup problem
"Safely displaying third-party advertisements is a special case of the more general secure mashup problem in which an integrator (e.g., a publisher) attempts to interact with a collection of untrusted gadgets (e.g., advertisements)." 6
2.5. dynamic enforcers
Unlike static verifiers, dynamic enforcers transform the source program by inserting a number of run-time security checks.
2.6. statically verify containment
Check, before the guest runs, that its source conforms to a safe subset, so that no run-time checks are needed. ADsafe, Dojo Secure and Jacaranda take this approach6.
2.7. unvetted objects
The unvetted objects are the JavaScript objects that are not accessible by the guest script on the empty page. For example, all the objects created by the host page are unvetted because those objects are not present on the empty page. As another example, the document object is unvetted because it is not accessible by the guest script on the empty page, even though document exists on the empty page.6
2.8. same-origin policy
A script may read only the properties of windows and documents with the same origin (protocol, host, port) as the document that contains the script. Recorded in 2012 as a quotation whose source the notes do not name:
"The same-origin policy is a sweeping security restriction on what web content JavaScript code can interact with. It typically comes into play when a web page includes <iframe> elements or opens other browser windows. In this case, the same-origin policy governs the interactions of JavaScript code in one window or frame with the content of other windows and frames. Specifically, a script can read only the properties of windows and documents that have the same origin as the document that contains the script…"
2.9. CORS
Cross-Origin Resource Sharing: the server opts in to cross-origin
reads with Access-Control-Allow-Origin and related response headers.
See CORS (2012 support).
2.10. JSONP
Cross-origin data fetched by injecting a <script> tag whose response
calls a page-supplied callback. It works under the same-origin policy
because script inclusion was never restricted, which is also the
problem: the provider can return arbitrary script, not just data.
3. Tools
| Tool | Feature | Notes | Status (2026) |
|---|---|---|---|
| ADsafe | static verifier | blacklist properties | adsafe.org does not resolve |
| Blancura | static verifier | whitelist via namespaces | http://webblaze.cs.berkeley.edu/2010/blancura/ |
| Caja | rewriter + runtime checks | object-capability model | archived 2021-01-31 |
| Closure Compiler | compiler, type checker | type annotations | maintained |
| Cuckoo | malware sandbox | n/a | https://cuckoosandbox.org/ |
| Dojo Secure | static verifier | blacklist | unmaintained (dojox, Dojo 1.x) |
| DoctorJS | indexing | gone | |
| Esmorph | instrumentation | archived (https://github.com/ariya/esmorph) | |
| Esprima | AST | inactive since 2023; Acorn, Babel parser | |
| FBJS | transpiler | retired with Facebook Platform's FBML | |
| Jacaranda | static verifier | gone | |
| JSLint | lint | https://www.jslint.com/ | |
| Cover | coverage | https://github.com/itay/node-cover | |
| parse-js | AST | UglifyJS v1 parser | superseded |
| Web Sandbox | dynamic enforcer | Microsoft Live Labs | websandbox-code.org does not resolve |
3.1. ADsafe  adsafe
all 11 methods checked against the archived ADSAFE object in adsafe.js (web.archive.org snapshot of http://www.adsafe.org/adsafe.js, ~2011) — create, get, go, has, id, isArray, later, lib, log, remove, set are all present with behavior matching the table.
ADsafe makes it safe to put guest code (such as third party scripted advertising or widgets) on a web page.
| Name | Description |
|---|---|
| create(object) | Create a new empty object that inherits from object. |
| get(object, name) | Get the value of the object's name property. |
| go(id, function) | Start the widget. The id string must match the id of the widget's div. |
| has(object, name) | true if the object has an own property with that name. |
| id(id) | Identify the widget. The id string must match the id of the widget's div. |
| isArray(value) | Returns true if the value is an array. |
| later(function, milliseconds) | Call a function in the future. |
| lib(name, function) | Register an ADsafe library. |
| log(string) | Post the string to the browser's log. |
| remove(object, name) | Remove a property from the object. |
| set(object, name, value) | Set a property's value on the object. |
Further analysis of the system is available7 as well as general research on JavaScript core semantics and a verifier for JSLint (+ADsafe)8.
3.2. Caja  caja
Attack vectors, from the Caja wiki (now on the Google Code archive): https://code.google.com/archive/p/google-caja/wikis/AttackVectors.wiki
3.2.1. GlobalObjectPoisoning
Should be possible, even with only manual review, to ensure we don't modify any of the host environment objects.
3.2.2. EvalArbitraryCodeExecution
Shouldn't be using eval or new Function() to bypass creation.
3.2.3. CrossScopeParameterModification
It's unlikely that we would need to be providing an assignment to the
arguments object. Also, most of the validation checks wouldn't allow
for this anyway.
3.2.4. ScriptInHtml
This is a fairly general problem if we use a sink system for attempting to clean the data. May need to work with an implementation team to determine what they consider appropriate to push into the DOM.
3.2.5. InnerHtmlYieldsCdata
Would be trivial to show a team that we don't use any of the
workarounds with the deprecated elements listing, plaintext, xmp.
3.2.6. DomAllowsXsrf
Without using Caja it's difficult to prove that a library doesn't perform these actions.
3.2.7. DomAllowsKeylogging
Would need to prove that we don't have key events handled by the system. Would need to do further review on the static implementation.
3.2.8. RedirectWithoutUserAction
Bypassing the same-origin policy; see same-origin policy.
3.2.9. GlobalScopeViaThis
This would need to be something checked via static analysis.
3.2.10. DeleteUnmasksGlobals
Audit for the use of delete.
3.3. GATEKEEPER9  gatekeeper
paper (Guarnieri & Livshits, USENIX Security 2009, sec. 2.2) disallows =with=, =eval=, =Function=, =setTimeout=, =setInterval= via lexer blacklist and instruments =document.write=/=.innerHTML=; it explicitly allows =arguments=, =Function.call=, =Function.apply= as safe reflective constructs — the opposite of what this heading listed as restricted.
GATEKEEPER disallows via lexer blacklist: with, eval, Function,
setTimeout, setInterval. document.write gets an
optimistic-then-verified static check and .innerHTML assignments get
a runtime rewrite. arguments, Function.call and Function.apply
are explicitly allowed, not restricted.
3.4. CORS (2012 support)
original was a pasted Wikipedia list (Chrome 3; Opera "expected in 12"). MDN BCD http.headers.Access-Control-Allow-Origin gives Chrome 4, Firefox 3.5, Safari 4, Opera 12 (2012-06-14), IE10 (2012-10-26).
In early 2012 CORS worked in Firefox 3.5+, Safari 4+ and Chrome 4+.
IE8 and IE9 offered only the partial XDomainRequest object. Presto
Opera added CORS in Opera 12 (June 2012) and IE10 (October 2012) closed
the gap, so by the end of 2012 every current browser supported it.
4. Tasks
4.1. Define a secure subset of JavaScript
This would need to be done with tools like Esprima or parse-js unless existing tools should be used.
4.2. Manually look for globals, constructor
A manual check should be performed for global pollution; see JavaScript Global Execution Context Pollution. This is based on "3.1 Containment Architecture"6.
4.3. Define the success criteria for external vendors
Ad code is often shifted out by the ad networks, which makes it a higher threat to a page.
4.4. Define tainted values in the system
A generic suite of test data should be provided to ensure that the system, when writing out data, doesn't allow for an exploit.
4.5. TODO Evaluate: ADsafe
This is largely just about the process of using JSLint as a validation step since this is all a validation process.
5. Reading
"Preventing Capability Leaks in Secure JavaScript Subsets" linked to sp11.pdf (Taly et al., S&P 2011); the abstract quoted is Finifter, Weinberger, Barth (NDSS 2010), finifter-weinberger-barth.pdf. Link fixed; Taly paper listed separately.
5.1. Language-Based Isolation of Untrusted JavaScript  adsafe
Maffeis, Mitchell, Taly, CSF 2009. http://seclab.stanford.edu/websec/jsPapers/csf09-camera-ready.pdf
5.2. Preventing Capability Leaks in Secure JavaScript Subsets
Finifter, Weinberger, Barth, NDSS 2010.
Publishers wish to sandbox third-party advertisements to protect themselves from malicious advertisements. One promising approach, used by ADsafe, Dojo Secure, and Jacaranda, sandboxes advertisements by statically verifying that their JavaScript conforms to a safe subset of the language.
http://www.adambarth.com/papers/2010/finifter-weinberger-barth.pdf
5.3. Automated Analysis of Security-Critical JavaScript APIs
Taly, Erlingsson, Mitchell, Miller, Nagra, IEEE S&P 2011. Defines SESlight, an analysable subset of ES5 strict, and the ancestor of the SES work below.
6. Since then
Caja archived 2021-01-31 (googlearchive/caja README); SameSite=Lax default Chrome 80 (2020-02-04), Firefox flag only, Safari no; Trusted Types Chrome 83, Firefox 148 (2026-02-24), Safari 26 (MDN BCD); ShadowRealm at TC39 stage 2.7; Shai-Hulud >500 packages (CISA, 2025-09-23).
The 2012 program, which was to make the guest safe by checking or rewriting its source, lost. Every source-level subset on the tools list is dead; Caja, the most serious of them, was archived on 2021-01-31 with Google's note that it had "not been actively maintained or developed to keep up with the latest research on web security"10. What won was the iframe the 2012 background already saw coming, plus browser-enforced policy on the host.
Policy on the host. Content Security Policy went from a 2012
draft to Level 2 (W3C Recommendation, 2016) to Level 3, still a
Working Draft in September 202611. Nonce- and hash-based policies
with 'strict-dynamic' replaced host allowlists, which were shown to
be bypassable in practice12. Trusted Types attacks the ScriptInHtml
and .innerHTML items above directly: with
require-trusted-types-for 'script', DOM sinks reject plain strings
and accept only values minted by a named policy. Chrome shipped it in
83 (2020); Firefox 148 (February 2026) and Safari 26 made it Baseline
in 202613. Caja's own deprecation notice pointed its users at
Closure, citing its sanitizers and Trusted Types support.
Cookies. Chrome 80 (February 2020) treats a cookie without a
SameSite attribute as Lax, which removes most of the
DomAllowsXsrf class for those cookies. Firefox has it only behind a
preference and Safari not at all, so explicit SameSite remains the
portable answer14.
Asset integrity. The 2012 question of confirming asset integrity got
Subresource Integrity: <script integrity"sha384-…">= fails closed
if the CDN serves different bytes. Level 1 became a W3C Recommendation
in 201615. SRI pins a file; it does nothing when the pinned
version is the malicious one.
Supply chain. That gap is where the attacks went. event-stream
3.3.6 (November 2018) pulled in flatmap-stream, which decrypted its
payload only inside the Copay wallet build16. ua-parser-js
0.7.29, 0.8.0 and 1.0.0 were published from a hijacked account in
October 202117. In September 2025 the Shai-Hulud worm used stolen
npm tokens to republish more than 500 packages with a credential
stealer that propagated itself18. The 2012 threat was hostile
code the page chose to host; the current one is hostile code the build
chose to install, and it runs before the browser enforces anything.
Language-level containment. The capability-leak result above has a
real descendant. SESlight became SES and Hardened JavaScript
(lockdown() freezes every intrinsic, Compartment gives each guest
its own globals), maintained in Agoric's Endo project19 and
used by MetaMask's LavaMoat to sandbox npm dependencies. The
standard-track piece, ShadowRealm, sits at TC39 stage 2.7 in October
202620. ES5 strict mode, which the 2011 quote above was waiting
for, made most of the GATEKEEPER list (with, arguments.callee,
this as the global object) statically checkable.
The secure-mashup problem, again. LLM agents that read web pages are the 2012 integrator with a new guest. Indirect prompt injection (Greshake et al., 202321) is untrusted content crossing into a context where it is executed as instructions, which is XSS without a parser to enforce the boundary. There is no ADsafe for natural language yet: the containment work that exists (CaMeL, taint tracking) moves enforcement outside the model, which is the same move the browser made when it stopped trusting source-level subsets. See Tainted Data in LLM Pipelines and How We Contain Claude.
What held up: the unvetted objects framing, the instinct to audit
delete, eval and global writes, and the iframe as the unit of
trust. What did not: the idea that a verifier over guest source could
be the boundary.
6.1. Related on this site
- JavaScript Global Execution Context Pollution — the 2012 companion on globals and the module pattern.
- Web Trackers Analysis (Alexa Top 800) — the same year's map of who was injecting third-party script.
- Browser adtech infrastructure — this site's own third-party surface in 2026, built to fit a
connect-src 'self'CSP. - Tainted Data in LLM Pipelines — the LLM-era version of the tainted-values task above.
- LibrePlanet 2017 — notes include JavaScript subresource integrity.
Footnotes:
Grossman, "I know where you've been" (2006): http://jeremiahgrossman.blogspot.com/2006/08/i-know-where-youve-been.html
MDN, ":visited" privacy restrictions: https://developer.mozilla.org/en-US/docs/Web/CSS/:visited ; Mozilla Security Blog, "Plugging the CSS history leak" (2010-03-31): https://blog.mozilla.org/security/2010/03/31/plugging-the-css-history-leak/
Finifter, Weinberger, Barth (NDSS 2010), §4.5.2 Twitter: http://www.adambarth.com/papers/2010/finifter-weinberger-barth.pdf
Taly et al. (IEEE S&P 2011), introduction: http://www-cs-students.stanford.edu/~ataly/Papers/sp11.pdf
Barth, Jackson, Mitchell, "Securing Frame Communication in Browsers": http://seclab.stanford.edu/websec/frames/post-message.pdf
Finifter, Weinberger, Barth (NDSS 2010): http://www.adambarth.com/papers/2010/finifter-weinberger-barth.pdf
Politz et al., "ADsafety": http://www.cs.brown.edu/research/plt/dl/adsafety/v1/adsafety.pdf
Brown PLT, "The Essence of JavaScript" (2011): https://blog.brownplt.org/2011/09/29/js-essence.html
Guarnieri, Livshits, "GATEKEEPER: Mostly Static Enforcement of Security and Reliability Policies for JavaScript Code", USENIX Security 2009: https://www.usenix.org/legacy/event/sec09/tech/full_papers/guarnieri.pdf
googlearchive/caja README, Deprecation: https://github.com/googlearchive/caja
W3C, Content Security Policy Level 3: https://www.w3.org/TR/CSP3/
Weichselbaum, Spagnuolo, Lekies, Janc, "CSP Is Dead, Long Live CSP! On the Insecurity of Whitelists and the Future of Content Security Policy", ACM CCS 2016: https://research.google/pubs/csp-is-dead-long-live-csp-on-the-insecurity-of-whitelists-and-the-future-of-content-security-policy/
MDN, Trusted Types API: https://developer.mozilla.org/en-US/docs/Web/API/Trusted_Types_API
MDN, Set-Cookie SameSite: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie
W3C, Subresource Integrity (Recommendation, 2016-06-23): https://www.w3.org/TR/2016/REC-SRI-20160623/
dominictarr/event-stream#116: https://github.com/dominictarr/event-stream/issues/116
GitHub Advisory GHSA-pjwm-rvh2-c87w: https://github.com/advisories/GHSA-pjwm-rvh2-c87w
CISA, "Widespread Supply Chain Compromise Impacting npm Ecosystem" (2025-09-23): https://www.cisa.gov/news-events/alerts/2025/09/23/widespread-supply-chain-compromise-impacting-npm-ecosystem
Endo / SES: https://github.com/endojs/endo
TC39 proposals, stage 2.7: https://github.com/tc39/proposals
Greshake et al., "Not what you've signed up for: Compromising Real-World LLM-Integrated Applications with Indirect Prompt Injection" (2023): https://arxiv.org/abs/2302.12173