Back to Blog
critical SEVERITY5 min read

How CSRF vulnerabilities happen in Node.js API clients and how to fix them

A critical CSRF vulnerability in `lib/client.js` allowed attackers to forge authenticated POST requests to `/remote-ssh/api/*` endpoints. The fix adds the `X-Requested-With: XMLHttpRequest` header to enable proper CSRF token validation, blocking malicious cross-site requests with a minimal one-line change.

O
By Orbis AppSec
•Technically reviewed by Anupam Mediratta•Published September 8, 2026•Reviewed September 8, 2026

Answer Summary

This is a Cross-Site Request Forgery (CSRF) vulnerability (CWE-352) in a Node.js library's API client (`lib/client.js:294`). The `fetch()` call to `/remote-ssh/api/*` endpoints lacked CSRF protection, relying only on weak `Sec-Fetch-Site` header checks. The fix adds `X-Requested-With: XMLHttpRequest` header to signal legitimate XMLHttpRequest origins, enabling server-side CSRF validation to reject forged cross-origin requests.

Vulnerability at a Glance

cweCWE-352
fixAdded `X-Requested-With: XMLHttpRequest` header to fetch() call at line 294
riskAttackers could forge authenticated API requests to remote SSH endpoints from malicious websites
languageJavaScript/Node.js
root causeMissing `X-Requested-With` header allowed cross-origin POST requests to bypass CSRF checks
vulnerabilityCross-Site Request Forgery (CSRF)

Introduction

The lib/client.js file in this Node.js library handles authenticated communication with remote SSH API endpoints, but a flaw in the fetch() call at line 294 created a critical security risk. The code made POST requests to /remote-ssh/api/* endpoints without including the X-Requested-With header, leaving downstream consumers vulnerable to Cross-Site Request Forgery (CSRF) attacks. While the server-side isTrusted() function attempted validation using Sec-Fetch-Site headers and localhost origin checks, this defense proved inadequate against same-site attacks and legacy browser scenarios.

This vulnerability is particularly dangerous because it affects a library—meaning every application using this package inherited the same exploitable pattern. Developers working on similar API client code should pay close attention to how their requests signal legitimate origin to server-side CSRF protections.

The Vulnerability Explained

The vulnerable code in lib/client.js at line 294 performed a fetch() call without proper CSRF signaling:

// VULNERABLE CODE (before fix)
var r = await fetch("/remote-ssh/api/" + method, {
  method: "POST",
  headers: { "content-type": "application/json" },  // Missing CSRF protection
  body: JSON.stringify(args || {})
});

The critical problem is the missing X-Requested-With header. The server-side isTrusted() function relied on this header (along with Sec-Fetch-Site) to distinguish legitimate XMLHttpRequest calls from forged cross-origin requests. Without it, the server had no reliable way to verify that POST requests to sensitive /remote-ssh/api/* endpoints actually originated from the application's own JavaScript code.

How the attack works: An attacker creates a malicious website that lures authenticated users. When the victim visits the attacker's page, JavaScript triggers a cross-origin POST request to https://victim-app.com/remote-ssh/api/execute-command (or similar endpoints). Because the victim has an active session, their browser automatically includes authentication cookies. The content-type: application/json header bypasses simple preflight requirements, and without X-Requested-With, the server-side isTrusted() check sees only that Sec-Fetch-Site might indicate "cross-site"—but this header is missing in older browsers and can be ambiguous in same-site scenarios. The attacker successfully forges an authenticated API request.

Real-world impact: For applications using this library, attackers could execute arbitrary SSH commands, modify remote configurations, or exfiltrate sensitive data—all without the victim's knowledge or consent.

The Fix

The remediation was elegantly simple: add the X-Requested-With: XMLHttpRequest header to enable proper server-side CSRF validation.

Before:

// lib/client.js:294 (vulnerable)
var r = await fetch("/remote-ssh/api/" + method, {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify(args || {})
});

After:

// lib/client.js:294 (fixed)
var r = await fetch("/remote-ssh/api/" + method, {
  method: "POST",
  headers: { "content-type": "application/json", "x-requested-with": "XMLHttpRequest" },
  body: JSON.stringify(args || {})
});

This single-header change transforms the security posture. The X-Requested-With: XMLHttpRequest header is a defense-in-depth mechanism that:

  1. Cannot be set by cross-origin attackers: Browsers restrict JavaScript on attacker-controlled origins from setting this header on cross-origin requests due to CORS preflight requirements
  2. Enables server-side validation: The isTrusted() function can now reliably distinguish legitimate library requests from forged ones
  3. Maintains backward compatibility: The header is standard and safe for all target endpoints

The fix is scoped precisely to the vulnerable path—only the lib/client.js file was modified, ensuring no unintended behavioral changes elsewhere.

Key Takeaways

  • The fetch() call in lib/client.js at line 294 now includes X-Requested-With: XMLHttpRequest to enable CSRF validation
  • Relying on Sec-Fetch-Site alone is insufficient—legacy browsers and same-site scenarios bypass this protection
  • Library code requires extra scrutiny: vulnerabilities propagate to all downstream consumers automatically
  • Single-header security improvements can eliminate entire vulnerability classes without breaking existing functionality
  • Server-side isTrusted() implementations must validate multiple request indicators, not just origin headers

How Orbis AppSec Detected This

Source: HTTP request parameters and body data in lib/client.js fetch calls to /remote-ssh/api/* endpoints

Sink: The fetch() call at lib/client.js:294 performing POST requests without CSRF protection headers

Missing control: No X-Requested-With header or CSRF token validation; server-side isTrusted() relied solely on Sec-Fetch-Site and localhost origin checks

CWE: CWE-352: Cross-Site Request Forgery (CSRF)

Fix: Added x-requested-with: XMLHttpRequest header to the fetch request headers object, enabling server-side CSRF validation to reject cross-origin forged requests.

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

CSRF vulnerabilities in API client libraries pose systemic risks because they affect every application using the package. The fix in lib/client.js demonstrates that effective security improvements need not be complex—a single well-chosen header can close a critical attack vector. When building library code, always consider how your HTTP requests will be validated by server-side protections, and include appropriate security signaling by default. The X-Requested-With: XMLHttpRequest pattern remains a valuable defense-in-depth technique, especially when combined with comprehensive CSRF token validation on the server.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #4

Related Articles

high

undici 8.10.0 Cache Poisoning: CVE-2026-85152 Auth Bypass

undici, the HTTP client used by Node.js's `fetch()` implementation, shipped a caching layer that did not properly isolate cached responses by origin, letting a response poisoned on one origin be served to requests for another. This created a path to cross-origin authentication bypass, tracked as CVE-2026-85152 and fixed in undici 8.10.2.

critical

Hybridauth Telegram OAuth Timing Attack in `authenticateCheckError()`

The Telegram OAuth provider in Hybridauth relied on `strcmp()` to validate HMAC-SHA256 signatures, exposing a timing side-channel that could allow attackers to forge authentication tokens character by character. The fix replaces this with PHP's timing-safe `hash_equals()` function, eliminating the information leak.

critical

Node.js Auth Query SQL Injection via Group Code Interpolation

A sign-in authorization check built its SQL query by mapping an `authorizedGroups` array into quoted string literals and joining them directly into a template literal, creating a classic SQL injection point in a critical authentication path. The fix replaces every interpolated value — including the previously "typed" parameter — with `?` placeholders bound through a value-builder helper, closing off the injection vector entirely.

high

PHP session_start() Missing Secure Cookie Flags in lasturl.php

The `session_start()` call in the last URL tracking component failed to set secure session cookie parameters, allowing session hijacking via man-in-the-middle attacks on HTTP connections or XSS exploitation. The fix configures `session_set_cookie_params()` with `httponly`, `secure`, and `samesite` attributes before starting the session.

critical

Tung Tung Tracker Admin API: Missing Authentication in `/api/sync`

The Tung Tung Tracker administrative API endpoints (`/api/sync`, `/api/backfill`, `/api/show`, `/api/overlay/hide`) relied solely on localhost origin and a static `X-Tracker` header for protection, allowing any local process to execute privileged operations. The fix introduces proper session-based authentication and a random IPC secret for desktop-to-server communication.

critical

POST /api/generate Lacks Authentication, Allowing Unauthenticated

A resume generation endpoint in a Node.js backend accepted requests from any caller with network access, allowing attackers to consume OpenAI API quota without restriction. The vulnerability stemmed from missing authentication middleware on a cost-bearing endpoint. The fix adds mandatory API key validation via HTTP headers before processing any generation requests.