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

corrected · claude-opus-5.5 · 2026-10-08

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

correct · claude-opus-5.5 · 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

needs-citation · pilot-annotation-pass · 2026-10-08

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

verified · pilot-annotation-pass · 2026-10-08

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

corrected · pilot-annotation-pass · 2026-10-08

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)

corrected · claude-opus-5.5 · 2026-10-08

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

corrected · claude-opus-5.5 · 2026-10-08

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.

http://www-cs-students.stanford.edu/~ataly/Papers/sp11.pdf

6. Since then

correct · claude-opus-5.5 · 2026-10-08

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

Footnotes:

2

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/

3

Finifter, Weinberger, Barth (NDSS 2010), §4.5.2 Twitter: http://www.adambarth.com/papers/2010/finifter-weinberger-barth.pdf

4

Taly et al. (IEEE S&P 2011), introduction: http://www-cs-students.stanford.edu/~ataly/Papers/sp11.pdf

5

Barth, Jackson, Mitchell, "Securing Frame Communication in Browsers": http://seclab.stanford.edu/websec/frames/post-message.pdf

8

Brown PLT, "The Essence of JavaScript" (2011): https://blog.brownplt.org/2011/09/29/js-essence.html

9

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

10

googlearchive/caja README, Deprecation: https://github.com/googlearchive/caja

11

W3C, Content Security Policy Level 3: https://www.w3.org/TR/CSP3/

12

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/

15

W3C, Subresource Integrity (Recommendation, 2016-06-23): https://www.w3.org/TR/2016/REC-SRI-20160623/

17

GitHub Advisory GHSA-pjwm-rvh2-c87w: https://github.com/advisories/GHSA-pjwm-rvh2-c87w

18

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

20

TC39 proposals, stage 2.7: https://github.com/tc39/proposals

21

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