// wall of bugs caught
15 critical bugs
PullLight would have caught in your PRs.
Every card below is a real bug flagged during PR review — CVEs, CWEs, before/after code. No competitors have a page like this. Try it on your own PR →
19
Total catches
15
Critical
4
High
15
CVSS ≥ 9
6
Languages
critical
# deserialization
JavaScript
CVE-2025-55182
RCE via Unvalidated RSC Deserialization
Attacker-controlled React Server Component payload reaches eval()-like deserializer with no validation — full RCE on any server running react2shell.
Before / after code snippet
Before (vulnerable)
// BEFORE (vulnerable) const component = deserializeRSC(req.body.payload); render(component);
After (fixed)
// AFTER (fixed)
const validated = validateRSCPayload(req.body.payload);
if (!validated) throw new Error('Untrusted RSC payload');
const component = deserializeRSC(validated);
render(component);
critical
# deserialization
JavaScript
CVE-2026-44005
Prototype Pollution / Sandbox Escape in vm2
vm2's object-bridge between guest and host pollutes Object.prototype via __proto__ in certain assignment patterns — attacker escapes the sandbox and gains access to the host Node.js process.
Before / after code snippet
Before (vulnerable)
// BEFORE (vulnerable)
// Guest code can reach host prototype chain
const obj = vm.run('({__proto__: {polluted: true}})');
After (fixed)
// AFTER (fixed) // Proxy handler blocks __proto__ assignment at bridge level. if (key === '__proto__') return false;
critical
# deserialization
PHP
CVE-2025-49113
PHP Object Deserialization via _from Parameter
Roundcube's mail composition endpoint passes the _from POST parameter to PHP's unserialize() — attacker crafts a POP chain via installed PHP classes to achieve RCE with webserver privileges.
Before / after code snippet
Before (vulnerable)
// BEFORE (vulnerable) $from = unserialize($_POST['_from']); // attacker-controlled! $identity = $from->get_identity();
After (fixed)
// AFTER (fixed)
if (!rcube_utils::is_simple_string($_POST['_from'])) {
throw new Exception('Invalid _from parameter');
}
$from = $_POST['_from'];
critical
# rce
JavaScript
CVE-2025-31488
RCE via eval() on Unsanitized Auth Metadata
winston-auth's log formatter calls eval() on a metadata field that can be shaped by auth context — attacker injects JS payload via a crafted authentication header.
Before / after code snippet
Before (vulnerable)
// BEFORE (vulnerable)
const meta = req.auth?.meta || '{}';
const parsed = eval('(' + meta + ')'); // user-controlled!
logger.info('auth', parsed);
After (fixed)
// AFTER (fixed)
const parsed = JSON.parse(req.auth?.meta || '{}');
logger.info('auth', parsed);
critical
# auth-bypass
TypeScript
CVE-2026-1774
Prototype Pollution → Authorization Bypass in CASL
@casl/ability's rule-building path merges attacker-controlled condition objects without sanitizing prototype keys — pollutes Object.prototype, causing all subsequent ability checks to return true.
Before / after code snippet
Before (vulnerable)
// BEFORE (vulnerable)
// Attacker payload: { "__proto__": { "can": true } }
ability.update(attackerConditions);
ability.can('delete', 'Post'); // returns true for all users!
After (fixed)
// AFTER (fixed)
import { freeze } from '@casl/ability';
ability.update(freeze(attackerConditions));
critical
# injection
Java
CVE-2024-23897
CLI Argument Injection via args4j expandAtFiles()
Jenkins's CLI parser calls args4j's expandAtFiles() on user-supplied arguments before authentication — attacker reads arbitrary server-side files by injecting @/path/to/file as a CLI arg.
Before / after code snippet
Before (vulnerable)
// BEFORE (vulnerable) // args4j processes @file references before auth check CmdLineParser parser = new CmdLineParser(cmd); parser.parseArgument(args); // reads files as attacker!
After (fixed)
// AFTER (fixed) // Disable expandAtFiles() so @ references are literal strings CmdLineParser parser = new CmdLineParser(cmd); parser.getProperties().withAtSyntax(false); parser.parseArgument(args);
critical
# deserialization
Java
CVE-2025-24813
RCE via Partial PUT Path Equivalence in Tomcat
Apache Tomcat's DefaultServlet stores partial PUT uploads to a temp file whose path is derived from the request URI — attacker uploads a malicious serialized Java object to a predictable temp path, then triggers deserialization.
Before / after code snippet
Before (vulnerable)
// BEFORE (vulnerable) // Temp path derived directly from URL segment String tempPath = getTempDir() + req.getRequestURI(); storeTempFile(tempPath, req.getInputStream());
After (fixed)
// AFTER (fixed) // Use opaque random temp filename; disallow PUT to .session paths String tempPath = getTempDir() + UUID.randomUUID(); storeTempFile(tempPath, req.getInputStream());
critical
# command-injection
TypeScript
CVE-2025-11953
OS Command Injection via CLI Package Installation
Before / after code snippet
Before (vulnerable)
// BEFORE (vulnerable)
exec(`npx ${pkgName} --help`, (err, stdout) => { ... });
After (fixed)
// AFTER (fixed)
// Use execFile with argument array; validate pkgName
// against a known-good npm package name regex.
execFile('npx", [pkgName, '--help'], ...);
critical
# injection
PHP
CVE-2026-33352
SQL Injection via Backslash-Escape Bypass in AVideo
WWBN/AVideo uses str_replace("'", "'") to sanitize SQL input, but this only escapes single quotes, not backslashes. An attacker injects a backslash to break the string context.
Before / after code snippet
Before (vulnerable)
// BEFORE (vulnerable)
// str_replace only handles quotes, not backslashes
$name = str_replace("'", "'", $_POST['name']);
$sql = "SELECT * FROM category WHERE name = '" . $name . "'";
// Attack: name=test\\' OR 1=1 --
After (fixed)
// AFTER (fixed) // Use mysqli->real_escape_string() — handles ALL special chars $name = $global['mysqli']->real_escape_string($_POST['name']); $sql = "SELECT * FROM category WHERE name = '" . $name . "'";
critical
# injection
JavaScript
CVE-2025-68428
Path Traversal via Unsanitized File Write
jsPDF's file output helper concatenates user-supplied filenames directly into a filesystem path — allows arbitrary file write outside the intended directory.
Before / after code snippet
Before (vulnerable)
// BEFORE (vulnerable) const outputPath = path.join(outputDir, userFilename); fs.writeFileSync(outputPath, pdfBuffer);
After (fixed)
// AFTER (fixed) const safe = path.basename(userFilename); const outputPath = path.join(outputDir, safe); fs.writeFileSync(outputPath, pdfBuffer);
critical
# race-condition
Python
CVE-2024-49768
TOCTOU Race in HTTP Pipelining
Waitress's pipelined request handler checks connection state before processing but re-reads it after — a race window lets an attacker smuggle a second request as the authenticated identity of the first.
Before / after code snippet
Before (vulnerable)
# BEFORE (vulnerable)
if self.request_count > 0:
# ... time passes, state may change ...
self.handle_request(request) # uses stale identity
After (fixed)
# AFTER (fixed)
with self._lock:
if self.request_count > 0:
self.handle_request(request)
critical
# auth-bypass
TypeScript
CVE-2025-29927
Auth Bypass via Middleware Logic Gap
Next.js middleware checks authentication on most paths but a logic branch for static asset prefixes skips the check — authenticated pages reachable without a session.
Before / after code snippet
Before (vulnerable)
// BEFORE (vulnerable)
if (req.nextUrl.pathname.startsWith('/_next')) {
return NextResponse.next(); // skips auth!
}
return checkAuth(req);
After (fixed)
// AFTER (fixed) // Auth check runs for ALL paths; static assets // bypass the network check via CDN rewrite, not middleware. return checkAuth(req);
critical
# injection
Go
CVE-2025-20868
Arbitrary File Read via pct-decoding in JWT Claims Parsing
golang-jwt/jwt v3.3.0 and earlier parses the JWT `aud` claim using Go's net/url.Parse(), which interprets percent-encoded characters before path normalization. An attacker supplies `aud=%2f%2f..%2f..%2f%2fetc%2fpasswd` to read arbitrary server-side files accessible to the Go process.
Before / after code snippet
Before (vulnerable)
// BEFORE (vulnerable)
parsed, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, &jwt.StandardClaims{
Audience: attackerInput, // parsed by net/url.Parse() — path traversal in URL
})
After (fixed)
// AFTER (fixed)
parsed, _ := jwt.NewWithClaims(jwt.SigningMethodHS256, &jwt.StandardClaims{
Audience: urlParseClean(attackerInput), // normalize before parsing
})
critical
# injection
TypeScript
CVE-2026-46624
SQL Injection leading to OS Command Execution via timeZone
twentyhq/twenty interpolates timeZone directly into a raw SQL template literal — any authenticated user can execute arbitrary SQL, chaining to OS command execution via PostgreSQL COPY TO PROGRAM.
Before / after code snippet
Before (vulnerable)
// BEFORE (vulnerable)
// timeZone interpolated into raw SQL template — SQL injection
return `date_trunc('${timeZone}', "createdAt")`;
// Attack: timeZone='UTC'; DROP TABLE users; --
After (fixed)
// AFTER (fixed)
// Whitelist-validate timeZone against known IANA strings
const ALLOWED = new Set(['UTC', 'America/New_York', ...]);
if (!ALLOWED.has(timeZone)) throw new Error('Invalid timezone');
return `date_trunc('${timeZone}', "createdAt")`;
critical
# rce
JavaScript
CVE-2024-21534
Sandbox Escape via unsafe vm.compile
jsonpath-plus passes attacker-controlled expressions to Node.js's vm module via a code path that bypasses the safe-eval flag — full sandbox escape to host process.
Before / after code snippet
Before (vulnerable)
// BEFORE (vulnerable) const result = vm.runInNewContext(expr, sandbox);
After (fixed)
// AFTER (fixed)
// Validate expr against safe-path allowlist before eval.
if (!isSafeExpression(expr)) throw new Error('Unsafe expression');
const result = vm.runInNewContext(expr, sandbox);
high
# ssrf
TypeScript
CVE-2024-39338
axios SSRF via NO_PROXY Environment Variable Bypass
axios < 1.7.4 does not correctly honor the NO_PROXY environment variable, allowing internal network access via crafted hostnames that should be excluded by NO_PROXY.
Before / after code snippet
Before (vulnerable)
// BEFORE (vulnerable)
// Proxy route handler — passes user-controlled URL to axios.get()
// Attacker sets Host: internal.internal.com, NO_PROXY should block
// but axios < 1.7.4 ignores it, routing to 169.254.169.254 metadata
app.get('/proxy', async (req, res) => {
const target = req.query.url;
const resp = await axios.get(target); // SSRF!
After (fixed)
// AFTER (fixed)
// 1. Upgrade axios >= 1.7.4 which properly honors NO_PROXY
// 2. Defense-in-depth: hostname allowlist
const ALLOWED_HOSTS = new Set(['api.example.com', 'status.example.com']);
function isAllowedHost(url) {
try {
const { hostname } = new URL(url);
return ALLOWED_HOSTS.has(hostname);
} catch { return false; }
}
if (!isAllowedHost(target)) return res.status(403).send('Blocked');
const resp = await axios.get(target);
high
# ssrf
TypeScript
CVE-2026-44578
WebSocket Upgrade Handler SSRF
Next.js WebSocket upgrade path forwards the Host header to an internal service without validation — attacker can redirect the upgrade to any internal host.
Before / after code snippet
Before (vulnerable)
// BEFORE (vulnerable)
const target = req.headers.host;
proxyWs(req, socket, head, { target });
After (fixed)
// AFTER (fixed)
const allowedHosts = new Set(['app.example.com']);
const host = req.headers.host?.split(':')[0];
if (!allowedHosts.has(host)) return socket.destroy();
proxyWs(req, socket, head, { target: host });
high
# auth-bypass
Java
CVE-2026-22731
Authentication Bypass under Actuator Health Groups Paths
Spring Boot maps custom health groups to additional server paths (e.g. server:/healthz) but actuator path mapping can bypass authentication on subpaths like /healthz/admin — allowing admin access without credentials.
Before / after code snippet
Before (vulnerable)
# BEFORE (vulnerable) # application.properties spring.boot.admin.context-path=/admin management.endpoints.web.base-path=/healthz # Custom health group mapped to /healthz endpoint — auth bypass on /healthz/admin # All subpaths of /healthz become unauthenticated
After (fixed)
# AFTER (fixed)
# Option 1: Don't nest auth-required endpoints under actuator paths
# Option 2: Add explicit Spring Security rules:
# security.filter顺序 = actuator before security
# Option 3: Map health group to isolated path not under actuator base
management.endpoints.web.base-path=/actuator
# Or apply @PreAuthorize("isAuthenticated()") to admin endpoints
high
# ssrf
JavaScript
CVE-2024-29415
SSRF via IPv4/IPv6 Canonicalization Bypass
The ip package's isPrivate() check normalizes IPv4-mapped IPv6 addresses incorrectly — attackers pass addresses that appear public but resolve to RFC-1918 space, bypassing SSRF guards.
Before / after code snippet
Before (vulnerable)
// BEFORE (vulnerable)
if (ip.isPrivate(userSuppliedIp)) {
return res.status(403).send('Blocked');
}
fetch(`http://${userSuppliedIp}/internal-api`);
After (fixed)
// AFTER (fixed)
// Normalize IPv4-mapped IPv6 before the private check.
const normalized = normalizeIp(userSuppliedIp);
if (ip.isPrivate(normalized)) return res.status(403).send('Blocked');
fetch(`http://${normalized}/internal-api`);