Blog
Security research notes and bug-hunting writeups — mostly things I broke on purpose.
-
The Last Function Wins
authorizationauthz-bypassnodejsfastifycwe-285cwe-697ghsacveresponsible-disclosure
A plugin for composing auth rules with AND and OR. The expression it evaluated wasn't always the one you wrote.
-
The Option That Was Really Code
rcenodejstemplate-injectioncwe-94full-disclosure
eta compiles templates with new Function. Three of its config options get spliced into that generated source raw — and two sibling engines already got a CVE for exactly this.
-
The Alias That Wasn't
sql-injectionormtypescriptpostgrescwe-89full-disclosure
A type-safe query builder that escapes every value you give it — and then hands your column aliases to Postgres raw, quotes and all.
-
A Bracket Too Far
sql-injectionsql-servernodejscwe-89ghsaresponsible-disclosure
The 'safe' bulk-insert helper built a CREATE TABLE from your column names. What if a column name contained a ] ?
-
The Spreadsheet That Ran Code
rcenodejsaisupply-chaincwe-94full-disclosure
promptfoo lets an assertion say javascript: and runs it. That's documented for your own config. It also does it for rows fetched from a shared Google Sheet.
-
The Dot That Opened a Door
sql-injectionnodejspostgrescwe-89full-disclosure
Massive-js binds every value you filter on. But put a dot in the key and it stops binding — and starts building a string literal by hand.
-
The Brackets That Hid an Address
ssrfnodejslangchainaicwe-918full-disclosure
LangChain added an SSRF guard after the last CVE. It checks for private IPs — but only after asking 'is this an IP?', and one way of writing 169.254.169.254 makes it answer no.
-
The Validator That Widened Access
path-traversalnodejsaicwe-22full-disclosure
transformers.js checks whether your modelId is a valid Hugging Face repo id. When the answer is no, it doesn't reject you — it reads the path off your disk instead.
-
The Quote That Forgot to Double
sql-injectiongolangormcwe-89full-disclosure
A Go query builder ships a helper literally called Quote(). Its whole job is to make an identifier safe. It wraps it in quotes and calls it a day.
-
The Base Class That Fetched Anything
ssrftypescriptllamaindexaicwe-918full-disclosure
In LlamaIndex.TS, if the path you hand a reader starts with http, it fetches it. Every reader. Because the fetch lives in the class they all inherit from.
-
The Path Inside the Braces
sql-injectionnodejsormpostgrescwe-89full-disclosure
Objection binds the column safely with ??. Then it takes the JSON path you asked for and staples it into a string literal by hand.
-
Designed for Untrusted Input, Except This
xxejavassrfcwe-611full-disclosure
Apache Commons Configuration's security page promises it's built to process untrusted config files. Its XML reader resolves external entities by default. Feed it a DOCTYPE and i...
-
A Function Called getSafe
ssrfnodejsembedjsaicwe-918full-disclosure
It fetches a URL and returns the body. It follows redirects. It validates nothing. The only safe thing about getSafe is the name.
-
Two ORMs, One Bug
sql-injectionpythonormcwe-89full-disclosure
The original package is unmaintained. The maintained fork copied it byte-for-byte — including the identifier quoting that never escaped anything.
-
Deny Always Wins, Except When It Doesn't
access-controlauthorizationnodejscwe-863full-disclosure
The README says it twice, in bold: deny-overrides, deny always wins. Grant three fields, then deny all fields with a wildcard, and all three come right back.
-
The Missing Dollar Sign
access-controlauthorizationnodejscwe-863regexfull-disclosure
A read-only role granted *:read also gets *:read:write, *:read:admin, and *:readsecrets. The whole bug is one absent regex anchor — and the function right above it has it.
-
A Tale of Two Dashes
argument-injectionnodejscwe-88responsible-disclosure
How a missing `--` turned a video-downloader wrapper into an argument-injection bug — and how the maintainer fixed it in an afternoon.
-
When a Resource Name Is a Regex
access-controlauthorizationnodejscwe-863regexfull-disclosure
acl2 denies access to a resource with no grants — by compiling every granted resource name into a regex and testing it against the one you asked for. Name a resource '.*' and yo...
-
The Deny That Only Counted Once
access-controlauthorizationnodejscwe-863full-disclosure
Two policies match your request. One hides the SSN field, one doesn't. abacl intersects the denies instead of unioning them — so the field one policy tried to hide comes back.
-
Return False, Still Lets You In
authenticationapi-securitynodejsopenapicwe-305full-disclosure
The obvious way to write an auth check is to return true or false. In oas-tools, only throwing denies the request. Returning false does nothing at all.