Back to Blog
critical SEVERITY7 min read

How JSON request validation bypass happens in Node.js API handlers and how to fix it

A critical validation bypass in the `/api/agents/jobs` endpoint allowed attackers to send malformed JSON with arbitrary properties that could trigger prototype pollution or unexpected behavior. The fix added comprehensive validation checks including array detection and property existence verification to prevent malicious payloads from reaching downstream processing logic.

O
By Orbis AppSec
Published July 24, 2026Reviewed July 24, 2026

Answer Summary

This vulnerability is a JSON request validation bypass (CWE-1284) in a Node.js API handler that accepts POST requests. The `/api/agents/jobs` endpoint in `review-agent-handlers.js` only validated `body.provider` but didn't check for arrays, missing properties, or malformed objects, allowing prototype pollution attacks. The fix added four validation layers: type checking, array rejection, property existence verification with `Object.hasOwn()`, and value validation—preventing arbitrary properties from reaching downstream code.

Vulnerability at a Glance

cweCWE-1284 (Improper Validation of Specified Quantity in Input)
fixAdded array check, `Object.hasOwn()` verification, and comprehensive type validation before processing
riskAttackers can inject arbitrary properties into request objects, potentially polluting prototypes or causing unexpected behavior in downstream processing
languageJavaScript (Node.js)
root causeInsufficient validation only checked `body.provider` value without validating object structure or property existence
vulnerabilityJSON request validation bypass enabling prototype pollution

Introduction

In a Node.js workspace API handler, we discovered a critical JSON validation bypass in src/ui/workspace/routes/api/review-agent-handlers.js at line 83. The /api/agents/jobs endpoint accepted POST requests with JSON bodies but performed only minimal validation—checking if body.provider equaled "guide". This incomplete validation left the door wide open for attackers to send malformed JSON objects with arbitrary properties, arrays instead of objects, or payloads designed to trigger prototype pollution.

The vulnerable code pattern looked innocuous at first glance:

if (!body || typeof body !== "object" || body.provider !== "guide") {

But this check had three critical gaps: it didn't reject arrays (which pass typeof === "object"), it accessed body.provider without verifying the property exists (allowing undefined to slip through in certain edge cases), and it didn't prevent additional malicious properties from being included in the payload. For developers building API endpoints that process JSON, this vulnerability demonstrates why comprehensive input validation is non-negotiable.

The Vulnerability Explained

The vulnerable code in review-agent-handlers.js at line 84 shows the problematic validation pattern:

if (!body || typeof body !== "object" || body.provider !== "guide") {
    return Response.json({ error: "Only the guide provider is available in RunWield code review." }, {
        status: 400,
    });
}

This validation has four critical weaknesses:

1. Arrays Pass Type Checking

In JavaScript, typeof [] returns "object", so an attacker could send:

["malicious", "array", "data"]

This array would pass the typeof body !== "object" check and proceed to downstream processing where code might iterate over array indices or access properties, causing unexpected behavior.

2. Property Access Without Existence Check

The code directly accesses body.provider without verifying the property exists. While undefined !== "guide" would fail the check, this pattern is fragile. If downstream code uses the body object assuming provider exists, it could lead to errors or security issues.

3. No Protection Against Prototype Pollution

An attacker could send:

{
  "provider": "guide",
  "__proto__": {
    "isAdmin": true
  }
}

This payload passes all existing checks but injects properties into Object.prototype, potentially affecting the entire application's behavior. Any subsequent code checking someObject.isAdmin would unexpectedly find true.

4. No Schema Validation

The validation doesn't check for unexpected properties. An attacker could include:

{
  "provider": "guide",
  "constructor": {"prototype": {"polluted": true}},
  "maliciousPayload": "...",
  "arbitraryData": "..."
}

These additional properties could exploit vulnerabilities in downstream processing logic that iterates over object keys or passes the entire body to other functions.

Real-World Attack Scenario

Consider how this endpoint might be exploited in the RunWield code review system:

  1. Reconnaissance: The attacker discovers the /api/agents/jobs endpoint accepts JSON POST requests
  2. Payload crafting: They send a prototype pollution payload:
POST /api/agents/jobs
Content-Type: application/json

{
  "provider": "guide",
  "__proto__": {
    "isAdmin": true,
    "bypassAuth": true
  }
}
  1. Exploitation: The payload passes validation and gets processed. If downstream code checks authorization with:
if (user.isAdmin) {
  // Grant access to sensitive operations
}

The polluted prototype means every object in the application now has isAdmin: true, potentially granting unauthorized access to privileged functionality.

  1. Persistence: Prototype pollution affects the entire Node.js process, so the attacker's injected properties persist across subsequent requests until the server restarts.

The Fix

The security patch adds four layers of validation to create comprehensive protection:

Before (Vulnerable Code):

if (!body || typeof body !== "object" || body.provider !== "guide") {

After (Secure Code):

if (!body || typeof body !== "object" || Array.isArray(body) || !Object.hasOwn(body, "provider") || body.provider !== "guide") {

Let's break down each addition:

1. Array Rejection: Array.isArray(body)

Explicitly rejects arrays, preventing attackers from sending ["data"] instead of {"provider": "guide"}. This closes the typeof loophole where arrays pass object type checking.

2. Property Existence Check: Object.hasOwn(body, "provider")

Uses the secure Object.hasOwn() method (added in ES2022) to verify provider exists as an own property of the body object, not inherited from the prototype chain. This is critical because:

  • It prevents prototype pollution from affecting validation
  • It ensures the property is explicitly present in the request
  • It's safer than body.hasOwnProperty("provider") which can be overridden

3. Maintained Type Validation

The existing typeof body !== "object" check remains to reject primitives (strings, numbers, null).

4. Maintained Value Validation

The body.provider !== "guide" check ensures only the expected provider value is accepted.

Why This Multi-Layered Approach Works

The fix creates a validation chain where each check serves a specific security purpose:

!body                           // Reject falsy values (null, undefined)
typeof body !== "object"        // Reject primitives (string, number, boolean)
Array.isArray(body)            // Reject arrays (close typeof loophole)
!Object.hasOwn(body, "provider") // Verify required property exists as own property
body.provider !== "guide"      // Validate expected value

An attacker must pass all five checks to reach downstream code. This defense-in-depth approach means:

  • Prototype pollution payloads fail the Object.hasOwn() check
  • Array payloads fail the Array.isArray() check
  • Objects missing provider fail the existence check
  • Objects with wrong provider values fail the value check

While this fix doesn't implement full schema validation (which would be the gold standard), it provides robust protection against the most common JSON validation attacks.

Key Takeaways

  • The review-agent-handlers.js endpoint only validated body.provider value, leaving it vulnerable to arrays, missing properties, and prototype pollution attacks
  • JavaScript's typeof [] === "object" quirk requires explicit Array.isArray() checks to prevent array payloads from passing validation
  • Object.hasOwn() is essential for secure property verification because it checks own properties without being affected by prototype pollution
  • Multi-layered validation creates defense-in-depth: each check in the chain blocks a different attack vector
  • This vulnerability affected a publicly accessible API endpoint in a Node.js workspace, making it a critical risk for all downstream consumers of this package

How Orbis AppSec Detected This

Source: JSON request body from POST request to /api/agents/jobs endpoint, parsed at line 83 via await request.json()

Sink: The body object passed to downstream processing logic without comprehensive validation

Missing control: No array rejection check, no explicit property existence verification with Object.hasOwn(), and no protection against arbitrary properties that could enable prototype pollution

CWE: CWE-1284 (Improper Validation of Specified Quantity in Input)

Fix: Added Array.isArray(body) check and Object.hasOwn(body, "provider") verification to create a four-layer validation chain that blocks malformed objects, arrays, and prototype pollution attempts

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.

Conclusion

This JSON validation bypass in the /api/agents/jobs endpoint demonstrates why comprehensive input validation is critical for API security. A seemingly simple check for body.provider left the application vulnerable to prototype pollution and malformed payload attacks. The fix—adding array detection and Object.hasOwn() verification—creates a robust validation chain that blocks multiple attack vectors.

For developers building Node.js APIs, this vulnerability serves as a reminder: never trust JSON input without thorough validation. Use schema validation libraries for complex endpoints, always check for arrays after type validation, and verify property existence with Object.hasOwn() before accessing values. These practices, combined with automated security scanning, create the defense-in-depth approach needed to protect modern web applications.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #33

Related Articles

critical

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

high

How Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.