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:
- 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
- Enables server-side validation: The
isTrusted()function can now reliably distinguish legitimate library requests from forged ones - 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.
Prevention & Best Practices
To prevent CSRF vulnerabilities in API client code:
- Always include anti-CSRF headers: For XMLHttpRequest/fetch calls, include
X-Requested-With: XMLHttpRequestor custom CSRF token headers - Implement defense in depth: Don't rely solely on
Sec-Fetch-Site; combine multiple validation techniques - Use CSRF tokens for state-changing operations: For maximum protection, require cryptographically random tokens in request headers or bodies
- Validate Origin and Referer headers: Server-side checks should verify these headers against an allowlist
- Set SameSite cookie attributes: Use
SameSite=StrictorSameSite=Laxfor session cookies to prevent cross-origin cookie transmission
Detection tools:
- Semgrep rules for CSRF detection: https://semgrep.dev/r?q=csrf
- OWASP CSRF Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
- CWE-352 guidance: https://cwe.mitre.org/data/definitions/352.html
Key Takeaways
- The
fetch()call inlib/client.jsat line 294 now includesX-Requested-With: XMLHttpRequestto enable CSRF validation - Relying on
Sec-Fetch-Sitealone 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.
References
- CWE-352: Cross-Site Request Forgery (CSRF): https://cwe.mitre.org/data/definitions/352.html
- OWASP CSRF Prevention Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
- MDN: Fetch API - Headers: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#headers
- Semgrep CSRF rules: https://semgrep.dev/r?q=csrf
- GitHub PR: fix: add CSRF protection in client.js