Old bugs.
Still worth knowing.
We run Lachesis against vulnerable historical code and show the evidence path it reconstructs. No claim of novel discovery. Just a clear record of what the tool found.
rsync: Off-by-one out-of-bounds heap write
The argv pointer array is allocated with `maxargs` elements. argc is grown through the loop -- by direct argv[argc++] stores and by the interprocedural glob_expand paths -- while the only in-loop realloc guard fires on `argc == maxargs-1`. After the loop, `argv[argc] = NULL;` writes the trailing terminator; when argc has reached maxargs, that store lands one 8-byte pointer past the allocation, corrupting adjacent heap.
wolfssl: Stack-based buffer overflow
An attacker-sized ASN.1 OID length is copied straight into a small fixed stack buffer with no size check, overflowing the stack.
mikro-orm: Query injection in mikro-orm QueryBuilderHelper
QueryBuilderHelper builds WHERE/identifier clauses from operator/field input; a crafted key or operator escapes the intended parameterization and is concatenated into the SQL, letting request-derived content alter the query -- SQL injection. The blind enumerator built the scope cleanly (25397 nodes / 76329 edges, 3978 sinks); class-matched candidates fired 3 time(s) (census {'injection.query.escaping': 3}) but all in sibling files, 0 in QueryBuilderHelper.ts. Sites: injection.query.escaping SchemaHelper.ts:219; injection.query.escaping MySqlSchemaHelper.ts:262; injection.query.escaping SchemaHelper.ts:217 The real defect in QueryBuilderHelper (condition/identifier processing) (QueryBuilderHelper.ts) was not surfaced. Upstream fix: the fixed release validates/parameterizes the identifier and operator handling so a crafted key cannot inject SQL.
i18next-http-middleware: Prototype pollution via an unguarded dynamic property write
`setPath` is a generic `object[path] = value` walker: it splits the path into a key stack (lib/utils.js:3-4) and, for each key, does `if (!object[key]) object[key] = {}` then descends (9-10), finishing with `object[key] = newValue` (15). No step checks the key against `__proto__`/`constructor`/`prototype`, so an attacker-chosen segment writes straight through Object.prototype. The blind enumerator surfaced the prototype-key family precisely on that final dynamic write; 3.9.3 fixes it by blocking those keys in setPath and validating the lng/namespace identifiers.
mupdf: Integer overflow in pdf_load_image_imp stride/size computati
pdf_load_image_imp validates the image width/height/bits-per-component against SIZE_MAX, but the row stride and total buffer size are computed with int (not size_t) arithmetic, so a crafted image whose dimensions pass the guard still overflows the stride/size computation; the undersized buffer is then filled by fz_unpack_stream, which writes past its end -- a heap out-of-bounds write. Lachesis, scoped to source/pdf + source/fitz + include and enumerating blind, surfaced the class-matched obligation inside the vulnerable file (1 candidate(s) at pdf-image.c:724); the missing bound is exactly what the fix restores.
outline: Arbitrary file read via path traversal in JSON import
The attachment key from the imported JSON flows straight into path.join(rootPath, key) and is read by fs.readFile with no containment check, so ../ reads arbitrary files that are then imported.
js-yaml: the merge key that reached the prototype
A YAML merge key could choose the property name written by the loader. The existing check did not stop __proto__, so the write reached the shared prototype chain.
xmlBuildQName: the length math that wrapped
Two attacker-sized lengths were added in a 32-bit integer. When the total wrapped, libxml2 allocated a small buffer and copied the full data into it.
langchain-core: Unsafe deserialization
The dumps() side did not escape user-controlled mapping keys beginning with 'lc', letting an attacker smuggle a crafted {"lc":1,"type":"constructor","id":[...],"kwargs":{...}} node that load()/loads() then revives -- importing a module by the attacker-influenced namespace path and instantiating the named class with attacker kwargs. Fixed 0.3.81/1.2.5 by escaping 'lc' keys on serialization and hardening the load defaults (secrets_from_env=False plus an explicit allowed-objects allowlist).
trpc: Prototype pollution via an unguarded recursive computed-prop
`formDataToObject` builds a nested object from submitted FormData by calling `set(obj, key, value)` per entry. `set` splits the field name on `.`/`[`/`]` into a path (line 8) and, while more than one segment remains, does `obj[p] = obj[p] || {}` and recurses (lines 12-15), finishing with an `obj[p] = value` leaf write (line 20). No step checks the segment against `__proto__`/`constructor`/`prototype`, so an attacker-chosen field name writes straight through Object.prototype. The blind enumerator surfaced the prototype-key family precisely on those dynamic writes; 10.45.3 fixes it by rejecting the dangerous keys in `set`.
fastmcp: OS command injection via server name in subprocess-backed in
The attacker-controlled server name is placed unvalidated into the subprocess command list and executed; on Windows the .cmd wrapper runs via cmd.exe, which interprets the metacharacters and executes injected commands.
django: Partial directory traversal in archive.extract
A malicious tar/zip entry name resolves to an absolute path that shares a string prefix with the extraction target but lives outside it; target_filename's startswith check passes, and extract() writes the file into the sibling directory.
django: SQL injection via column alias
A crafted column-alias key passed via **kwargs to annotate()/alias()/aggregate()/extra() flows through add_annotation into the SELECT clause; check_alias runs but its regex omits '#', which on MySQL/MariaDB opens an inline comment, injecting SQL.
sqlite: Integer overflow
The separator length flows from the SQL argument into a 32-bit size multiplication that overflows, undersizing the result buffer; the copy loop then writes the true byte count and runs off the heap allocation.
pytorch: Unsafe deserialization
torch.load's modern loader builds UnpicklerWrapper(data_file) -- a pickle_module.Unpickler subclass -- and calls `result = unpickler.load()` (serialization.py:1826/1848). weights_only=True was supposed to route through a restricted unpickler, but <2.6.0 allowed a bypass, so a crafted checkpoint runs arbitrary code during unpickling (GHSA-53q9-r3pm-6pq6). The blind enumerator ran the whole taxonomy over the serialization.py import closure and object-integrity.deserialize.trust DID fire -- correct family -- but its one candidate is a sibling: torch.load(io.BytesIO(b), weights_only=False) in _load_from_bytes (storage.py:520). The advisory sink, unpickler.load() on a pickle.Unpickler subclass, is not name-matched by the catalog (module-function keys only), so it went unsurfaced. 2.6.0 fixes the CVE by defaulting weights_only=True and hardening the weights_only unpickler.
endpoint.js: Regular-expression denial of service
The caller-supplied options.headers is copied into the local headers object at parse.ts:18, and on a preview /graphql request headers.accept is fed as the SUBJECT to a static regex .match at parse.ts:62. The tool's reverse value-flow cone confirms the match subject is headers.accept. The regex /[\w-]+(?=-preview)/g is unanchored on the left, so a long non-preview subject drives catastrophic backtracking -- the DoS.
libxml2: Stack buffer overflow via cumulative strcat with stale lengt
During validity-error reporting, attacker-controlled element/namespace names from the parsed document are strcat'd into a fixed 5000-byte stack buffer; the per-component bounds checks use a stale length that ignores the already-appended prefix, so prefix + ':' + name overflows the stack.
Keras: Arbitrary file write via archive extraction
Keras extracts downloaded archives in extract_archive() via tarfile.extractall(), trusting a path-safety filter to keep members inside the destination. A crafted archive with a long symlink chain defeats that filter and writes outside the destination -- an arbitrary file write. Lachesis, scoping the build to keras/src/utils and enumerating blind, surfaced the path-containment obligations inside the vulnerable file; the missing robust containment is exactly what 3.12.0 restores with filter="data".
transformers: Regular-expression denial of service
All six tokenizer regexes are compiled in SubWordJapaneseTokenizer.__init__; the enumerator's top lead is content_repatter6 at line 233, `re.compile(r"((0|[1-9]\d*|[1-9]\d{0,2}(,\d{3})+)*億)*...")`. The tool's value-flow cone confirms the compiled Argument[0] is that literal. The three nested `(...)*` groups over overlapping alternations are the ReDoS: on a long comma/digit run with no matching currency suffix the matcher backtracks catastrophically. The official fix rewrote exactly this pattern.
mongoose: NoSQL query-operator injection via unfiltered populate
getModelsMapForPopulate assembles the populate sub-query, reading the caller's `match` from options (line 181) and storing it verbatim as data.match (line 187; virtual branch at 447). In 8.8.1 nothing filters that object, so a `{ $where: '<js>' }` match executes attacker JavaScript in the MongoDB query. The blind enumerator module-scoped the build and ran the whole taxonomy, but injection.query.escaping models string escaping into a query call, not an unfiltered operator object, so it surfaced only schema-path traversal look-alikes in getSchemaTypes.js (8 candidates, e.g. line 203) and missed the real sink. 8.8.3 fixes it by throwing when match.$where is present (GHSA-m7xq-9374-9rvx).
gstreamer: Integer underflow
qtdemux_parse_theora_extension subtracts 8 from an attacker-controlled size without first checking size >= 8, so the unsigned size underflows to a huge value used as a length, causing a heap OOB write. The blind enumerator built the scope cleanly (10912 nodes / 32883 edges, 115 sinks); class-matched candidates fired 8 time(s) (census {'memory.copy.capacity': 8, 'memory.alloc.size': 0, 'memory.index.capacity': 0}) but all in sibling files, 0 in qtdemux.c. Sites: memory.copy.capacity properties.c:63; memory.copy.capacity atoms.c:3683; memory.copy.capacity atoms.c:3611; memory.copy.capacity atoms.c:210; memory.copy.capacity qtdemux_dump.c:339; memory.copy.capacity qtdemux_lang.c:204; memory.copy.capacity atoms.c:5748; memory.copy.capacity descriptors.c:66 The real defect in qtdemux_parse_theora_extension (qtdemux.c) was not surfaced. Upstream fix: later GStreamer validates size >= 8 before the subtraction.
django: SQL injection
Composed SQL is executed at sql/query.py:152 via cursor.execute(self.sql, params); the tool confirms self.sql flows straight into execute() with no escaping. The CVE's specific taint enters upstream where a JSONField key is used as an unescaped column alias, which then becomes part of self.sql.
mlflow: Deserialization of untrusted data
MLflow deserializes scikit-learn model artifacts with pickle/cloudpickle and no trust boundary, so loading an attacker-supplied model executes arbitrary code. The SAST surfaced the correct unsafe-deserialization family across mlflow/models, but the exact sklearn pickle sink was outside the built scope; the anchored sink is a same-family open()/yaml.safe_load look-alike, and the real load path is adjudicated from the advisory.
strapi: Open redirect in strapi admin auth redirect handling
Strapi's admin authentication flow uses a request-supplied URL as a redirect target without validating it against an allowlist, so an attacker can craft a link that sends an authenticated user to an external site -- an open redirect usable for phishing / token leakage. Lachesis, scoped to the strapi package's source subtree and enumerating blind, surfaced the class-matched obligation inside the vulnerable file (1 candidate(s) at auth.js:441); the missing bound is exactly what the fix restores.
aiohttp: Path traversal
The URL path segment flows straight into a pathlib Path that is joined onto the served directory and opened, and the one containment check is skipped whenever follow_symlinks is enabled -- so ../ reads arbitrary files.
transformers: Deserialization of untrusted data
LegacyIndex._load_passages resolves passages_path from self.index_path (133), opens it (134), and calls pickle.load(passages_file) (135); the tool ties the untrusted file to that call. In v4.35.0 nothing gates the deserialization, so a malicious passages pickle runs arbitrary code on load. 4.36.0 fixes it by raising unless TRUST_REMOTE_CODE is set, right before the open/pickle.load.
sequelize-typescript: Prototype pollution
assign() copies `_source[key]` onto `_target[key]` and, for nested objects, recurses through deepAssign. The final `_target[key] = targetValue` at object.ts:54 is a plain computed write; the tool confirms `key` comes verbatim from the parameter. A `__proto__` key therefore writes through to the prototype.
gradio: Path traversal
The /file route hands the request path to utils.abspath, which builds a filesystem Path at utils.py:909 and returns an absolute/resolved path. Containment is supposed to be enforced by is_in_or_equal, but the 4.10.0 check is flawed, so the resolved path can escape the allowed roots.
Pillow: Code injection via eval
ImageMath.eval compiles `expression` at line 246, runs a name-allowlist scan(), then eval's the compiled code at line 259. The tool anchors the eval sink and ties it to the `expression` parameter. The scan() guard is insufficient because the caller also controls the namespace it checks against.
langchain: Server-side request forgery via unrestricted recursive URL c
RecursiveUrlLoader._get_child_links_recursive fetches `url` with requests.get (line 133), extracts the page's links, and recurses into each one (line 161). The tool ties the fetched url to the crawl parameter. In v0.0.316 there is no filter keeping those links within the start domain, so a link to an internal address is followed -- SSRF. 0.0.317 fixes it by adding prevent_outside=True and base_url filtering in extract_sub_links.
curl: Heap buffer overflow
curl's SOCKS5 handshake copies the target hostname into a fixed connection buffer to ask the proxy to resolve it. An over-long (>255-byte) name is supposed to force local resolution instead, but that decision lives in an earlier handshake state and is kept in a local variable that does not survive the non-blocking re-entry into do_SOCKS5; the memcpy that builds the remote-resolve request (socks.c:907) has no length check of its own. Lachesis surfaced that unbounded buffer-write obligation blind; the missing precondition hostname_len <= capacity is exactly what 8.4.0 enforces by erroring out.
vm2: Sandbox escape to host RCE per GHSA-g644-9gfx-q4q4
The SAST reader fired the expected injection.exec (code-injection) family and anchored on new Script(...) in NodeVM.run -- vm2 compiling untrusted code, which is by design. The real CVE is one layer out: objects crossing from the sandbox back to the host inspect/error path are not fully decontextualized, so an attacker-supplied custom-inspect method executes in the host realm and reaches the host Function constructor. The tool located the code-exec primitive near the sandbox; it did not model the host<->sandbox realm boundary where the escape actually happens.
protobuf.js: Prototype pollution
Untrusted key segments from a parsed protobuf message flow into a property-writing helper that assigns them onto a target object without rejecting __proto__/constructor/prototype -- so an attacker-chosen key mutates Object.prototype (prototype pollution). Lachesis surfaced the object-integrity.prototype family blind (49 candidates) and, on the top candidate, witnessed the untrusted-object value flow and the absence of any prototype-key guard; the exact util.setProperty vector is adjudicated by hand.
langchain: Arbitrary code execution / code injection
The SAST reader fired the expected injection.exec (code-injection) family blind and even enumerated the real exec()/eval() sinks -- including PythonREPL.run's exec at utilities/python.py:19, the exact sink PALChain reaches. But its top-ranked, selected candidate is a string.Template constant in the Nebula graph adapter (a hardcoded nGQL query skeleton with an empty provenance cone), not the PALChain code-execution path. The real CVE is one construct over: PALChain._call takes Python source directly from the LLM (base.py:63) and runs it through repl.run -> exec (base.py:67 -> python.py:19) with no PALValidation, so a prompt-injected model output executes arbitrary Python on the host. The 0.0.236 fix adds an AST allow/deny-list and a process timeout.
json5: Prototype pollution
The parsed property name flows straight from the lexer into a plain computed assignment parent[key] = value with no __proto__ guard -- so a __proto__ key mutates Object.prototype (prototype pollution).
zlib: Heap buffer overflow
zlib's inflate() copies a gzip header's variable-length extra field into a caller-owned buffer (state->head->extra, capacity extra_max). The copy is clamped by `extra_max - len`, but len -- the running write offset -- is never required to stay below extra_max, and it accumulates across the multiple inflate() calls a chunked stream produces. Once len >= extra_max the unsigned subtraction wraps and the clamp becomes enormous, so the zmemcpy overruns the buffer. Lachesis surfaced the write obligation at inflate.c:769 blind; the missing precondition len < extra_max is exactly what 1.2.13 adds.
OpenSSL: Off-by-one stack buffer overflow in X.509 name-constraint pu
OpenSSL decodes punycode IDN labels while checking X.509 name constraints, writing the decoded label into a fixed-size buffer. The capacity bound on that write is off by one, so a crafted label overflows the buffer by one element during certificate verification. Lachesis, scoping the build to punycode.c and enumerating blind, surfaced the buffer-write obligations inside the vulnerable file (3 memory.copy.capacity candidates at 187/276/297); the missing sound capacity bound is exactly what 3.0.7 restores.
got: SSRF via redirect to a UNIX-domain socket
got rewrites an initial `unix:` URL into an http-over-socket form and, crucially, only does its scheme handling on the first request (source/core/index.ts:1673-1676). On a redirect it reads `Location` (2096), builds `redirectUrl` (2099) and sets it as `options.url` (2127) with no scheme re-check; the followed request lands in `_createCacheableRequest`, whose `url.hostname === 'unix'` branch connects to `socketPath` (2347). The blind enumerator surfaced the redirect-destination sink precisely on that socket assignment; 11.8.5 fixes it by rejecting redirects to UNIX-socket URLs.
ejs: Server-side template injection / code injection , ref.
An attacker-controlled template OPTION NAME is concatenated verbatim into the JavaScript source that ejs generates for the template, and that source is compiled and executed via the Function constructor -- so a non-identifier option name injects arbitrary JS that runs on the server at compile time. 3.1.6 validates none of these names; 3.1.7 requires each to be a bare JS identifier.
node-semver: Regular-expression denial of service
Every semver token regex is compiled at re.js:16 via `new RegExp(value, ...)` inside createToken; `value` is the token's source string. The tool's value-flow cone confirms the compiled first argument is that source. The vulnerable token sources embed unbounded overlapping quantifiers, which is the ReDoS.
qs: Prototype pollution
qs parses `a[__proto__][polluted]=1` into a key chain ['a','[__proto__]','[polluted]'], and parseObject() reverses over it building nested objects: for each segment it computes cleanRoot (stripping the surrounding brackets) and writes `obj[cleanRoot] = leaf`. With cleanRoot = '__proto__', the write lands on Object.prototype. The tool witnesses the `chain -> chain[i]/root -> cleanRoot -> obj[cleanRoot] = leaf` flow. In v6.10.2 no __proto__ filter guards this; 6.10.3 adds one.
joblib: Code injection via eval
Parallel.__call__ computes the dispatch batch size by calling `eval(pre_dispatch)`; the tool confirms the eval argument is the pre_dispatch attribute carried from the constructor. eval on a caller-supplied string is direct code injection.
scrapy: Exposure of sensitive information across domains on redirect
The SAST reader fired the navigation (request forgery & redirection) family blind and anchored on open(self.statefn) in the SpiderState extension -- a local pickle state-file read the Atropos URL-opener model mislabels as an SSRF sink. The real CVE is in a different component: RedirectMiddleware rebuilds the follow-up request with request.replace(...) that copies cookies and the Cookie header without comparing the source and redirect domains, so a cross-domain redirect leaks the victim's cookies to the new domain. The tool located an adjacent navigation-domain sink; it did not model the redirect request reconstruction or the cross-domain trust boundary where the leak actually happens, and the CVE's class (cross-domain sensitive-header exposure / incorrect authorization) has no sink family in the catalog.
node-fetch: Sensitive-header leak / SSRF on cross-origin redirect
On a redirect, fetch() takes `location = headers.get('Location')` (line 109), resolves `locationURL` (line 112), builds `requestOpts` with `headers: new Headers(request.headers)` -- every original header, including Authorization and Cookie (line 148) -- and re-issues `fetch(new Request(locationURL, requestOpts))` (line 175). With no cross-origin check, a redirect to a foreign host leaks those credentials. The tool anchors the destination sink (fetch to an attacker-chosen URL); 2.6.7 fixes it by stripping sensitive headers on cross-origin redirects.
ansi-regex: Regular-expression denial of service
The constant regex pattern is assembled at index.js:4 and handed to `new RegExp(pattern,'g')` at index.js:9. The tool's reverse value-flow cone confirms the sink argument is exactly that pattern. The pattern's `(?:;[a-zA-Z\d]*)*` group is the ReDoS: on a long ';'-run with no closing byte the matcher backtracks catastrophically.
libssh: SSH rekey digest size mismatch
libssh derives the session id / secret hash from the negotiated key-exchange digest. On a REKEY that negotiates a KEX with a different digest size than the original exchange, the code reuses length assumptions from the first exchange, so secret_hash and session_id lengths mismatch the actual digest -- copies keyed on the wrong length read or write past the allocated hash buffer. Lachesis, scoped to src + include and enumerating blind, surfaced the class-matched obligation inside the vulnerable file (2 candidate(s) at kex.c:732, kex.c:1198); the missing bound is exactly what the fix restores.
celery: Stored OS command / code injection via untrusted backend res
The tool's blind hunt fired the expected injection.exec family but anchored its top lead on the worker self-reload os.execv, which is not attacker-influenced (empty provenance cone). The real CVE is stored command injection via exception-class reconstruction in celery/backends/base.py: untrusted result-backend metadata is deserialized and the named exception class is imported and instantiated, so a poisoned backend yields code execution when a client reads the result. Fixed in 5.2.2 by restricting reconstruction to genuine Exception subclasses.
object-path: Prototype pollution
objectPath.set(obj, path, value) normalizes a string `path` via path.split('.').map(getKey), then recurses: currentPath = path[0], and on the final component writes `obj[currentPath] = value`. In v0.11.0 there is no guard rejecting __proto__/constructor/prototype, so a path of '__proto__.polluted' resolves currentPath to '__proto__', walks into Object.prototype, and assigns a shared property. The tool witnesses the `path -> path[0] -> currentPath -> obj[currentPath] = value` flow. 0.11.6 adds the guard.
handlebars.js: Code injection / template-compilation RCE
The tool localised the injection family to the handlebars compiler and its lower-ranked leads sit on the exact compile pipeline, but the top anchor is a benign regex .exec in the whitespace pass, not the code-generation line. My reading of the source and the 4.7.7 fix places the real RCE in depthedLookup, where an unescaped property name is concatenated into a JS string literal in the generated template function.
systeminformation: Command injection
inetChecksite sanitizes `url` into `urlSanitized` via a per-character allowlist (util.sanitizeShellString), then builds `args = ' -I --connect-timeout 5 -m 5 ' + urlSanitized + ' 2>/dev/null | head -n 1 | cut -d " " -f2'`, `cmd = 'curl'`, and calls `exec(cmd + args)`. child_process.exec runs the string through a shell, so any shell token surviving the sanitizer is interpreted. The tool witnesses the full `url -> sanitizeShellString -> urlSanitized -> args -> cmd + args -> exec` value-flow. In 5.3.0 the allowlist admitted spaces; 5.3.1 removes them.
salt: OS command injection
The request-body low-state flows straight into the salt-api client dispatch self.api.run(chunk) with no escaping, and downstream the salt-ssh client interpolates request-derived fields into a shell command run with shell=True -- so shell metacharacters in the request execute arbitrary OS commands. Lachesis anchored the injection.exec sink at the netapi dispatch (self.api.run) and witnessed the source (cherrypy.request.lowstate) and the missing escaping guard; the downstream shell=True Popen is in a module outside the built scope and is adjudicated.
freetype: Integer truncation in Load_SBit_Png
Load_SBit_Png stores a PNG's 32-bit width/height into narrower fields used to compute the glyph bitmap size, so dimensions above 0xFFFF truncate and undersize the buffer; libpng then writes the full-size image into it -- a heap OOB write. The blind enumerator built the scope cleanly (1957 nodes / 15737 edges, 8 sinks); class-matched candidates fired 8 time(s) (census {'memory.copy.capacity': 8, 'memory.alloc.size': 0, 'memory.index.capacity': 0}) but all in sibling files, 0 in pngshim.c. Sites: memory.copy.capacity sfdriver.c:967; memory.copy.capacity sfdriver.c:935; memory.copy.capacity ttcolr.c:346; memory.copy.capacity ttcolr.c:303; memory.copy.capacity sfwoff.c:357; memory.copy.capacity sfwoff2.c:242; memory.copy.capacity ttbdf.c:69; memory.copy.capacity ttbdf.c:132 The real defect in Load_SBit_Png (pngshim.c) was not surfaced. Upstream fix: 2.10.4 checks the PNG dimensions against the expected/target metrics before allocation and rejects images whose width/height do not fit, closing the truncation.
pyyaml: Deserialization of untrusted data
full_load delegates to `load(stream, FullLoader)`; the tool ties the untrusted `stream` argument to that call. FullLoader was assumed to strip unsafe tags, but in 5.3.1 it still constructs arbitrary objects via the python/object/* tags, so the deserialization is a code-execution sink.
salt: Path traversal
A caller-controlled wheel argument (file_name) is concatenated onto a base directory and written with no canonicalisation or containment check, so ../ segments write arbitrary files on the salt-master. The SAST tool surfaced the same absent-containment defect blind on the os.makedirs destination in this exact function, in the exact file the CVE fix patched.
sqlalchemy: SQL injection
The tool fired the SQL-injection family (injection.query.escaping, CWE-89) blind and reported an unguarded execute() sink, matching the CVE class. The precise CVE, however, is implicit string->text() coercion in the order_by/group_by compilation path: a bare string argument is emitted verbatim into the SQL and never validated as a real column label, so untrusted ordering text injects SQL. The fix (1.3.0b3, commit 30307c4) makes unresolved string coercion raise instead of rendering raw SQL.
sequelize: SQL injection via unescaped JSON-path-key interpolation in t
jsonPathExtractionQuery turns a JSON column + a path (derived from the where-clause JSON key) into a dialect SQL fragment. In v5.8.10 the MySQL branch interpolates the joined path inside single quotes (`(col->>'${pathStr}')`, line 1057) and the MariaDB branch does the same with no quoting (`json_unquote(json_extract(col,'${pathStr}'))`, line 1061); neither SQL-escapes the key. An attacker-chosen JSON key thus injects into the SQL. The blind enumerator module-scoped the build and ran the whole taxonomy, but injection.query.escaping models query-execution call sites, not a helper that returns a SQL substring, so it surfaced only an unrelated Array.find look-alike (line 1884) and missed the real sink. The fix (#11089) wraps the path in this.escape on both branches.
lodash: Prototype pollution
baseMergeDeep copies each source key onto the target and recurses for nested objects, reading the current value at each key through `safeGet`. In 4.17.11 safeGet rejects only the literal `__proto__`, so `safeGet(obj,'constructor')` returns the constructor function and the merge recurses into its `.prototype`; the terminal `object[key] = value` at baseAssignValue:2559 then writes onto Object.prototype. The tool confirms `key` at the write comes verbatim from the parameter (VALUE_FLOWS_TO 2550 -> 2559). 4.17.12 adds the missing `constructor` guard to safeGet.
serialize: Code injection via eval / unsafe deserialization
serialize.unserialize(str) does `obj = JSON.parse(str)` then walks the object; for each string value it checks `obj[key].indexOf('_$$ND_FUNC$$_') === 0` and, if so, runs `eval('(' + obj[key].substring(marker.length) + ')')`. A payload such as `{"rce":"_$$ND_FUNC$$_function(){require('child_process').exec('id')}()"}` therefore executes on unserialize. The tool witnesses the `obj[key].substring -> '('+...+')' -> eval` argument construction. No version fixes this.
sudo: Format-string vulnerability in sudo_debug
sudo_debug() builds a diagnostic message and passes it, together with the program name derived from argv[0], to a printf-family call in a way that uses attacker-influenced text as the format string. Because sudo is setuid root and argv[0] is fully attacker-controlled, format specifiers in the program name are interpreted, corrupting memory or crashing the process. Lachesis, scoped to src + lib + include and enumerating blind, surfaced the class-matched obligation inside the vulnerable file (2 candidate(s) at sudo.c:1219, sudo.c:401); the missing bound is exactly what the fix restores.
Every published case includes the final Lachesis evidence and its historical sources.