Prototype pollution
JavaScript objects inherit from Object.prototype. Code like:
function merge(target, source) {
for (const k in source) {
if (typeof source[k] === 'object') merge(target[k] = target[k] || {}, source[k]);
else target[k] = source[k];
}
}
merge({}, JSON.parse(userInput));
If userInput is {"__proto__": {"isAdmin": true}}, the merge writes isAdmin: true onto Object.prototype. Every subsequent obj.isAdmin check anywhere in the process returns true.
Real-world impact: 2019 Kibana RCE via prototype pollution into a template renderer. 2022 lodash CVE. The bug class is endemic to libraries that recursively merge user-controlled objects.
Defenses: use Object.create(null) for parsed-input bags, validate keys against an allow-list before merging, prefer Map over plain objects for untrusted key/value data, and ban keys named __proto__, constructor, prototype.
CSP bypass classes
A strict CSP closes most XSS sinks. The bypass classes that survive:
- JSONP endpoints in allow-listed origins: if your CSP allows
https://cdn.example.comand that CDN hosts a JSONP endpoint, an attacker injects<script src="https://cdn.example.com/api?callback=alert">and gets script execution. The defense is to never list origins that host arbitrary-callback JSONP. - AngularJS template injection: if
https://ajax.googleapis.comis in script-src and an unescaped attacker string lands inside an Angular-bound DOM tree, the attacker can write Angular template expressions that execute. Defense: drop the Angular CDN from allow-list if you do not use it, or migrate to Angular post-template-injection-fix versions. - Dangling base-uri: if you forget
base-uri 'none', an attacker who can inject a<base>tag changes the relative-URL resolution for every subsequent<script src="...">, redirecting them to attacker-controlled paths. - Strict-dynamic plus a trusted-but-vulnerable script: strict-dynamic propagates trust to scripts loaded by other trusted scripts. If any allow-listed script is exploitable, the attacker chains through it.
HTTP/2 to HTTP/1.1 downgrade smuggling
Front proxies often speak HTTP/2 to clients and HTTP/1.1 to backend origins. HTTP/2 carries headers in binary frames with explicit lengths; HTTP/1.1 uses text framing. The translation between them is where smuggling lives:
- HTTP/2 forbids the Transfer-Encoding header; some translating proxies passthrough it anyway, recreating CL.TE conditions on the HTTP/1.1 backend.
- HTTP/2 allows pseudo-headers like
:path; bugs in translating these into HTTP/1.1 request-lines have allowed header injection. - Even a perfectly compliant translator can be downgraded by injecting CRLF in HTTP/2 header values; the backend sees a request with weird headers.
James Kettle's research (PortSwigger) catalogued real vulnerabilities in major CDNs and load balancers. Defenses: HTTP/2 end-to-end where possible, strict header validation at the translator, reject any HTTP/2 request that carries forbidden HTTP/1.1 headers.
iframe sandbox escapes
The <iframe sandbox> attribute creates a restricted execution context. Each capability re-enabled by a flag (allow-scripts, allow-same-origin, etc.) erodes the protection. The escapes:
- allow-scripts + allow-same-origin together: the sandbox can remove the sandbox attribute on its own iframe and reload, escaping entirely. Never combine these two flags on untrusted content.
- postMessage to parent: even fully sandboxed, an iframe can postMessage to its parent. If the parent's onmessage handler does not validate the origin or the data, the iframe can drive the parent.
- Top-level navigation: without
allow-top-navigationthe iframe cannot navigate the top frame, but in older browsers there were bugs aroundwindow.openwithnoopener.
Supply chain: the largest blast radius
Most modern web apps depend on thousands of npm/PyPI packages. Single-package compromises (event-stream 2018, ua-parser-js 2021, XZ utils 2024) reach huge user populations. The defense pattern in mature shops:
- Lockfiles with content hashes for every dependency.
- Sigstore / npm provenance: signed build metadata tying releases to source commits.
- Reproducible builds where the build process can be verified independently.
- Egress-restricted CI: the build cannot exfiltrate secrets to arbitrary destinations.
- SBOM (Software Bill of Materials) and continuous vulnerability scanning against it.
The pragmatic minimum for any production project: dependabot or renovate, lockfile-only updates, and a policy of declining new transitive dependencies without review.
The bug-class outlook
Memory-unsafe languages drove decades of buffer overflows; memory-safe languages eliminate that bug class (largely). Likewise, parameterized queries eliminate SQL injection, escape-by-default templates eliminate most XSS, and SameSite cookies eliminate most CSRF. The bug classes that remain in 2026:
- SSRF (cloud metadata).
- Prototype pollution and other language-quirk injection.
- Request smuggling.
- Deserialization gadgets.
- Supply chain.
The trend is for new bug classes to live higher up the stack (languages, build systems, dependencies) as the lower layers harden. The pattern repeats indefinitely.