Back to Blog
high SEVERITY6 min read

How javascript.express.security.audit.express-check-csurf-middleware-usage.express-check-csurf-middleware-usage happens in Express.js and how to fix it

A publicly accessible Express.js API endpoint in `app/api/cameras.js` was missing CSRF protection, leaving state-changing requests (POST, PUT, DELETE, PATCH) vulnerable to cross-site request forgery attacks. The fix introduces Origin/Referer header validation middleware in `app/index.js` and removes a redundant Express instance from `cameras.js` that bypassed the application's middleware chain.

O
By Orbis AppSec
Published August 23, 2026Reviewed August 23, 2026

Answer Summary

Cross-Site Request Forgery (CSRF) vulnerability (CWE-352) was detected in an Express.js application where `app/api/cameras.js` instantiated its own `express()` app without CSRF middleware, bypassing any protections on the main app. The fix removes the isolated Express instance in `cameras.js` and adds Origin/Referer header validation middleware to the main `app/index.js`, ensuring all state-changing HTTP methods are protected against cross-origin forged requests.

Vulnerability at a Glance

cweCWE-352
fixRemoved redundant Express app and added Origin/Referer validation middleware to main app
riskAttackers can forge state-changing requests to the cameras API from malicious websites
languageJavaScript (Node.js/Express)
root causeSeparate Express instance in cameras.js bypassed application-level middleware
vulnerabilityMissing CSRF Middleware

Introduction

The app/api/cameras.js file in this application handles camera-related API operations, but a critical architectural flaw created a CSRF vulnerability: at line 4, the file instantiated its own independent Express application with const app = express(). This separate Express instance operated outside the main application's middleware chain defined in app/index.js, meaning any CSRF protections applied to the main app would never reach requests handled by this module.

This is a subtle but dangerous pattern. Even if a developer added CSRF middleware to the main application, the cameras API would remain completely unprotected because it was running on its own isolated Express instance without any such middleware configured.

The Vulnerability Explained

The Problematic Code

In the original app/api/cameras.js, the file created its own Express application:

const express = require('express');
const httpRequest = require('../lib/http');
const bodyParser = require('body-parser');
const app = express();
var SelfReloadJSON = require('self-reload-json');
const appRoot = require('app-root-path');
var settings = new SelfReloadJSON(appRoot + '/data/settings.json');

The key issue is lines 1, 3, and 4: importing express and body-parser, then creating a standalone const app = express(). This module-level Express instance would handle routes independently, completely bypassing any middleware (including CSRF protection) configured on the main application in app/index.js.

How Could This Be Exploited?

Consider this attack scenario:

  1. A user is authenticated to the camera management system and has an active session.
  2. The user visits a malicious website while still logged in.
  3. The malicious site contains a hidden form or JavaScript that submits a POST request to the cameras API endpoint:
<form action="https://target-app.com/api/cameras" method="POST">
    <input type="hidden" name="url" value="http://attacker-controlled-feed.com/spy" />
    <input type="hidden" name="name" value="Lobby Camera" />
</form>
<script>document.forms[0].submit();</script>
  1. The browser automatically includes the user's session cookies with the request.
  2. Because there's no CSRF validation, the cameras API processes the request as legitimate, potentially allowing an attacker to:
    - Add malicious camera feeds pointing to attacker-controlled streams
    - Delete existing camera configurations
    - Modify camera settings to disable monitoring

Since the PR description confirms "This API endpoint appears to be publicly accessible" and "This is a web service - vulnerabilities in request handlers are directly exploitable by remote attackers," the attack surface is real and immediately exploitable.

The Fix

The fix addresses this vulnerability through changes in two files, each serving a distinct purpose:

Change 1: Remove the Redundant Express Instance (app/api/cameras.js)

Before:

const express = require('express');
const httpRequest = require('../lib/http');
const bodyParser = require('body-parser');
const app = express();
var SelfReloadJSON = require('self-reload-json');

After:

const httpRequest = require('../lib/http');
var SelfReloadJSON = require('self-reload-json');

The imports for express and body-parser are removed, along with the standalone const app = express() declaration. This ensures that the cameras module no longer operates as an isolated Express application. Instead, it will use routes registered on the main application, inheriting all middleware in the chain.

Change 2: Add CSRF Protection Middleware (app/index.js)

Before: No CSRF protection existed in the middleware chain.

After:

// CSRF protection: verify Origin/Referer header matches Host for state-changing requests
app.use(function csrfProtection(req, res, next) {
    if (['POST', 'PUT', 'DELETE', 'PATCH'].indexOf(req.method) !== -1) {
        var origin = req.headers.origin || req.headers.referer;
        var host = req.headers.host;
        if (origin && host && origin.indexOf(host) === -1) {
            return res.status(403).json({ error: 'CSRF check failed' });
        }
    }
    next();
});

This middleware:
1. Targets only state-changing methods — GET and HEAD requests pass through unaffected since they should be idempotent.
2. Validates the Origin or Referer header — It checks whether the request's origin matches the application's host. Cross-origin forged requests will have a mismatched origin.
3. Returns 403 on mismatch — Requests failing validation receive a clear { error: 'CSRF check failed' } response.
4. Allows requests without Origin/Referer — This handles same-origin requests where browsers may not send these headers (e.g., direct API calls), avoiding false positives.

Why Both Changes Are Necessary

Removing the redundant Express instance ensures that cameras.js routes are served through the main app, which now has CSRF middleware. Without removing the standalone app = express(), the cameras module would continue to bypass any middleware added to app/index.js.

Prevention & Best Practices

Architectural Best Practices

  1. Never create multiple Express instances unless you're explicitly running separate servers. Use express.Router() for modular route definitions:
    javascript const router = require('express').Router(); // Define routes on the router, not a new app module.exports = router;

  2. Apply security middleware at the application level in your main entry point, before any route handlers are registered.

  3. Use defense-in-depth for CSRF protection:
    - Origin/Referer validation (as implemented here)
    - SameSite cookie attributes (SameSite=Strict or SameSite=Lax)
    - Token-based validation with libraries like csurf
    - Custom headers (e.g., X-Requested-With) that trigger CORS preflight

Detection Tools

  • Semgrep with rule javascript.express.security.audit.express-check-csurf-middleware-usage detects missing CSRF middleware
  • ESLint security plugins can flag patterns like multiple Express instantiation
  • OWASP ZAP can test for CSRF vulnerabilities in running applications

Relevant Standards

  • OWASP Top 10: This falls under "A01:2021 – Broken Access Control"
  • CWE-352: Cross-Site Request Forgery
  • OWASP CSRF Prevention Cheat Sheet: Recommends token-based and header-based validation

Key Takeaways

  • A standalone const app = express() in cameras.js created an isolated middleware chain, meaning CSRF protections on the main app were completely bypassed for camera API routes.
  • Origin/Referer header validation is an effective CSRF defense that doesn't require token management or client-side changes, making it ideal for API-first applications.
  • The fix targets only state-changing methods (POST, PUT, DELETE, PATCH), preserving backward compatibility for safe read operations.
  • Module-level Express instances are a common architectural anti-pattern that fragments security controls — always use express.Router() for sub-modules.
  • Two-file fixes are sometimes necessary: removing the bypass path (cameras.js) and adding the protection (index.js) together close the vulnerability completely.

How Orbis AppSec Detected This

  • Source: Incoming HTTP requests to the cameras API endpoints (state-changing methods like POST/PUT/DELETE)
  • Sink: Route handlers in app/api/cameras.js that process requests without CSRF validation
  • Missing control: No CSRF middleware (like csurf) was detected in the Express middleware chain, and the standalone express() instance in cameras.js:4 bypassed any application-level protections
  • CWE: CWE-352 (Cross-Site Request Forgery)
  • Fix: Removed the isolated Express instance from cameras.js and added Origin/Referer header validation middleware to the main application in app/index.js

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 vulnerability demonstrates how architectural decisions — specifically creating a standalone Express instance in a sub-module — can silently undermine security controls. The redundant const app = express() in cameras.js meant that even if CSRF middleware existed on the main application, the cameras API remained completely exposed. The fix elegantly addresses both the symptom (missing CSRF protection) and the root cause (isolated Express instance) by consolidating the middleware chain and adding Origin/Referer validation for all state-changing requests.

When building Express.js applications, always use express.Router() for modular routes and apply security middleware at the application level to ensure consistent protection across all endpoints.

References

Frequently Asked Questions

What is Cross-Site Request Forgery (CSRF)?

CSRF is an attack where a malicious website tricks a user's browser into making unwanted requests to a different site where the user is authenticated, exploiting the browser's automatic inclusion of cookies and credentials.

How do you prevent CSRF in Express.js?

Use CSRF middleware like `csurf`, implement Origin/Referer header validation for state-changing methods, use SameSite cookie attributes, or employ custom request headers that cannot be set cross-origin.

What CWE is CSRF?

CWE-352 (Cross-Site Request Forgery) covers vulnerabilities where a web application does not sufficiently verify whether a well-formed, valid request was intentionally provided by the user who submitted it.

Is Origin header checking enough to prevent CSRF?

Origin/Referer validation provides strong CSRF protection for most scenarios, but it should be combined with other defenses like SameSite cookies and token-based validation for defense-in-depth, especially since some browsers may strip the Referer header.

Can static analysis detect missing CSRF protection?

Yes, tools like Semgrep can detect the absence of CSRF middleware in Express applications by analyzing the middleware chain and flagging routes that handle state-changing methods without CSRF validation.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #33

Related Articles

high

How Client-Side Denial of Service happens in Node.js FTP clients and how to fix it

CVE-2026-44240 is a client-side Denial of Service vulnerability in the `basic-ftp` Node.js package (versions prior to 5.3.1) caused by improper handling of unterminated multiline FTP server responses. An attacker controlling an FTP server—or capable of intercepting FTP traffic—could send a malformed response that causes the client to hang indefinitely. Upgrading `basic-ftp` to 5.3.1 and adding a package override in `package.json` closes the attack surface entirely.

critical

How Command Injection via Unescaped Line Terminators Happens in Node.js and How to Fix It

A critical command injection vulnerability (CVE-2026-9277) was discovered in the shell-quote npm package version 1.8.3, where unescaped line terminators could allow attackers to execute arbitrary code. This fix upgrades shell-quote to version 1.9.0 using npm overrides to ensure all instances in the dependency tree are patched, eliminating the attack vector across the entire application.

critical

How Distributed Lock Takeover Happens in Node.js and How to Fix It

A critical vulnerability in `redis-lock/server.mjs` allowed any authenticated client to release another client's lock by guessing predictable holder identifiers like process IDs or hostnames. The fix implements cryptographically random `lockId` values that are minted on lock acquisition and validated on release, eliminating the exploit primitive entirely.

high

How Denial of Service via Infinite Loop happens in JavaScript (nanoid) and how to fix it

A high-severity denial of service vulnerability (CVE-2026-67213) was discovered in nanoid versions before 5.1.6 and 3.3.18, where the `customAlphabet` function could enter an infinite loop during random ID generation. The fix upgrades the transitive nanoid dependency from 3.3.16 to 3.3.18 using pnpm overrides, ensuring the vulnerable code path is eliminated from the entire dependency tree including PostCSS.

high

How Information Disclosure via Unstripped Credential Headers Happens in Electron Apps and How to Fix It

A high-severity vulnerability (CVE-2026-54673) in the builder-util-runtime package allowed sensitive credential headers to leak during HTTP redirects in Electron applications. The fix upgrades builder-util-runtime from version 9.5.1 to 9.7.0, which properly strips authentication headers before following redirects to prevent information disclosure.

high

How Information Disclosure and DoS via malformed Cache-Control directives happens in Node.js undici and how to fix it

A high-severity vulnerability (CVE-2026-13697) in the undici HTTP client library allowed attackers to trigger information disclosure and denial of service through malformed Cache-Control directives. The @jackwener/opencli project upgraded undici from version 7.24.6 to 7.29.0, eliminating the vulnerability in their dependency chain and protecting downstream consumers from exploitation.