Writing
web security
12 articles on this subject.
The document that reads your filesystem: XXE in docx, xlsx and SVG parsers
A docx, xlsx or SVG is a bundle of XML, and an XML parser that resolves external entities will fetch files and URLs on the attacker's behalf. CVE-2019-12415 in Apache POI is the office-document case. How XXE reaches an upload endpoint, how to find it, and how to turn it off.
9 min read
Writing to __proto__: prototype pollution in Node.js and the lodash defaultsDeep bug
Prototype pollution lets an attacker set a property on every object in a Node process by writing through __proto__. CVE-2019-10744 in lodash is the canonical case. How the merge goes wrong, how to find it, and how it differs from Python class pollution.
9 min read
The cache said yes: web cache deception, cache poisoning, and CVE-2024-46982 in Next.js
Web cache deception stores a victim's private response for anyone to fetch; cache poisoning stores an attacker's response for everyone. Both come from the cache and the origin disagreeing about a URL. CVE-2024-46982 in Next.js is the current, well-documented case.
9 min read
One request, two lengths: HTTP request smuggling and the HAProxy Transfer-Encoding bug
CL.TE and TE.CL desync happen when two servers disagree about where a request ends. CVE-2019-18277 in HAProxy is the clean example: how the disagreement forms, how to find it safely, and how to shut it down.
8 min read
No 'Access-Control-Allow-Origin' header is present on the requested resource: the error is right, and both popular fixes are vulnerabilities
No 'Access-Control-Allow-Origin' header is present on the requested resource. The two fixes developers copy do not fix CORS, they delete it. One is account takeover.
13 min read
CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate is not corruption, and verify=False is not a fix
CERTIFICATE_VERIFY_FAILED: unable to get local issuer certificate means no path to a trusted root. The five real causes, and why verify=False is not a fix.
13 min read
Indirect prompt injection: EchoLeak, CVE-2025-32711, and the email nobody opened
A crafted email sitting unread in an inbox was enough to make Microsoft 365 Copilot exfiltrate internal data. The injectable surface is every document the model reads, and the control that bounds it is least privilege on tools.
7 min read
Race conditions are not hard any more: the single-packet attack and what it broke
Web race conditions were dismissed as theoretical because network jitter made the window unhittable. Then HTTP/2 removed the jitter. The technique, the sub-states nobody models, and how to build code that cannot race.
7 min read
Prototype pollution has a Python cousin: class pollution and the road to __globals__
Python has no prototypes, so it cannot have prototype pollution, except it can. A walkthrough of class pollution: the vulnerable merge function, the four attribute chains that matter, and how to spot it in a code review.
7 min read
Argument injection: how a soft hyphen became CVE-2024-4577 and put 9.8 on the board
You escaped the shell metacharacters and the command still ran. Argument injection is the bug that survives shell-safe APIs, here is the mechanism, the PHP-CGI case that got mass-exploited, and the one-character fix.
7 min read
Server-side template injection: why {{7*7}} is a bad test, and how CVE-2023-22527 got to CVSS 10.0
Most SSTI write-ups teach one Jinja2 payload and stop. Here is the actual bug class, the per-engine probe table, a lab you can build in ten minutes, and the code-review rule that removes it entirely.
9 min read
An LLM is not a security boundary: building a moderation layer, then walking through it
We built a kids' chat app where a language model decides which messages are safe, then attacked the decision. Prompt injection is OWASP's number one LLM risk for the second edition running, and the reason is structural.
8 min read