Back to Blog
high SEVERITY6 min read

How express-check-csurf-middleware-usage happens in JavaScript/Express and how to fix it

A high-severity CSRF vulnerability was identified in `tower_game/index.js` where the Express application lacked any Cross-Site Request Forgery protection middleware. Without CSRF validation, an attacker could craft malicious pages that trick authenticated users into submitting unwanted requests to the game server. The fix adds `csurf` middleware with cookie-based token storage in just four lines of code.

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

Answer Summary

This vulnerability is a missing CSRF (Cross-Site Request Forgery) protection in an Express.js application (CWE-352). The `tower_game/index.js` file configured an Express server without any CSRF middleware, leaving all state-changing endpoints vulnerable to cross-origin forged requests. The fix adds `cookie-parser` and `csurf({ cookie: true })` as global middleware, ensuring every non-safe HTTP method requires a valid CSRF token.

Vulnerability at a Glance

cweCWE-352
fixAdded csurf middleware with cookie-based token storage via server.use(csrf({ cookie: true }))
riskAttackers can forge state-changing requests on behalf of authenticated users
languageJavaScript (Node.js/Express)
root causeExpress server in tower_game/index.js had no CSRF validation middleware
vulnerabilityMissing CSRF middleware in Express application

Introduction

In the tower_game/index.js file — a Node.js Express server that serves a browser-based tower game — we discovered a high-severity security gap: the application was configured with static file serving but completely lacked CSRF (Cross-Site Request Forgery) protection. Starting at line 5, the Express server was initialized and immediately began serving assets without any middleware to validate the origin of state-changing requests:

const server = express()
const host = 'http://localhost:8082'
server.use('/assets', express.static(path.resolve(__dirname, './assets')))
server.use('/dist', express.static(path.resolve(__dirname, './dist')))

This pattern — creating an Express app and adding routes without CSRF middleware — is flagged by Semgrep's express-check-csurf-middleware-usage rule because it leaves every POST, PUT, and DELETE endpoint vulnerable to cross-site forged requests.

The Vulnerability Explained

What's Actually Happening

Cross-Site Request Forgery exploits the trust that a web application has in the user's browser. When a user is authenticated with the tower game server (via session cookies, for example), their browser automatically attaches those cookies to every request sent to localhost:8082 — even if that request originates from a completely different website.

The vulnerable code in tower_game/index.js looked like this:

const express = require('express')
const path = require('path')
const opn = require('opn')

const server = express()
const host = 'http://localhost:8082'
server.use('/assets', express.static(path.resolve(__dirname, './assets')))
server.use('/dist', express.static(path.resolve(__dirname, './dist')))

There is zero validation that incoming requests actually originated from the tower game's own pages. No token checking, no origin validation, no CSRF middleware of any kind.

Attack Scenario Specific to This Application

Imagine the tower game has a score submission endpoint (as suggested by the game's nature). An attacker could create a malicious webpage like this:

<!-- attacker's page: evil-gaming-site.com -->
<form action="http://localhost:8082/api/score" method="POST" id="exploit">
  <input type="hidden" name="score" value="999999" />
</form>
<script>document.getElementById('exploit').submit();</script>

If a player who has the tower game open in another tab visits this page, the form auto-submits a fraudulent high score. The browser dutifully sends along any cookies associated with localhost:8082, and the server has no way to distinguish this forged request from a legitimate one.

While a local game server might seem low-risk, this pattern becomes critical when:
- The game is deployed to a public server with user accounts
- The server handles any authentication or user data
- The same codebase pattern is copied into production applications

Why Static Analysis Flagged This

Semgrep's rule express-check-csurf-middleware-usage performs a structural analysis of Express application setup. It looks for express() instantiation followed by route/middleware registration and checks whether any CSRF middleware (csurf, csrf, or equivalent) is registered. When none is found, it raises a HIGH severity finding because the absence of CSRF protection is a well-known, exploitable weakness.

The Fix

The fix adds four lines to tower_game/index.js that establish cookie-based CSRF protection:

Before (Vulnerable)

const express = require('express')
const path = require('path')
const opn = require('opn')

const server = express()
const host = 'http://localhost:8082'
server.use('/assets', express.static(path.resolve(__dirname, './assets')))

After (Secured)

const express = require('express')
const path = require('path')
const opn = require('opn')
const cookieParser = require('cookie-parser')
const csrf = require('csurf')

const server = express()
const host = 'http://localhost:8082'
server.use(cookieParser())
server.use(csrf({ cookie: true }))
server.use('/assets', express.static(path.resolve(__dirname, './assets')))

How Each Change Works

  1. const cookieParser = require('cookie-parser') — Imports the cookie-parser middleware, which is required by csurf when using cookie-based token storage. It parses the Cookie header and populates req.cookies.

  2. const csrf = require('csurf') — Imports the csurf middleware that implements the synchronizer token pattern for CSRF protection.

  3. server.use(cookieParser()) — Registers cookie parsing globally, ensuring csurf can read the CSRF secret from the _csrf cookie on incoming requests.

  4. server.use(csrf({ cookie: true })) — Registers CSRF protection globally with cookie-based storage. This means:
    - A _csrf cookie containing a secret is set on the client
    - Every state-changing request (POST, PUT, DELETE, PATCH) must include a valid CSRF token derived from that secret
    - The token can be sent via the _csrf body field, csrf-token header, or x-csrf-token header
    - Requests without a valid token receive a 403 Forbidden response

The middleware is registered before the static file routes, ensuring all subsequently defined routes inherit CSRF protection. Static file serving (GET requests) is unaffected since csurf only validates non-safe HTTP methods.

Prevention & Best Practices

1. Always Add CSRF Middleware Early in the Stack

Register CSRF protection immediately after session/cookie middleware and before any route handlers:

server.use(cookieParser())
server.use(session({ /* ... */ }))
server.use(csrf({ cookie: true }))
// Routes go here

2. Include CSRF Tokens in Your Frontend

For the tower game's frontend to work with CSRF protection, templates or API responses must provide the token:

server.get('/api/csrf-token', (req, res) => {
  res.json({ csrfToken: req.csrfToken() });
});

The frontend then includes this token in subsequent requests:

fetch('/api/score', {
  method: 'POST',
  headers: { 'X-CSRF-Token': csrfToken },
  body: JSON.stringify({ score })
});

3. Layer Your Defenses

  • Set SameSite=Strict or SameSite=Lax on session cookies
  • Validate the Origin and Referer headers as an additional check
  • Use helmet middleware for security headers

4. Use Static Analysis in CI/CD

Run Semgrep with the express-check-csurf-middleware-usage rule in your CI pipeline to catch missing CSRF protection before code reaches production.

5. Note on csurf Deprecation

The csurf package is deprecated. For new projects, consider alternatives like csrf-csrf or lusitania. The underlying pattern (synchronizer token) remains the same — only the package implementation differs.

Key Takeaways

  • The tower_game/index.js Express server had no CSRF middleware, meaning any cross-origin POST request would be accepted without validation — a textbook CWE-352 vulnerability.
  • Cookie-based CSRF protection requires both cookie-parser and csurf — forgetting cookieParser() before csrf({ cookie: true }) will cause runtime errors.
  • Middleware ordering matters: csrf() must be registered before route handlers but after cookie/session parsing to function correctly.
  • Static file routes (GET) are unaffected by csurf — the middleware only validates state-changing HTTP methods, so the game's asset serving continues to work without tokens.
  • Semgrep's structural analysis caught this at the application architecture level — not a bug in a specific route, but a missing security layer across the entire application.

How Orbis AppSec Detected This

  • Source: Any incoming HTTP request to the Express server at localhost:8082, particularly state-changing methods (POST, PUT, DELETE)
  • Sink: All route handlers registered on the server Express instance in tower_game/index.js:5 that process state-changing requests
  • Missing control: No CSRF middleware (csurf, csrf, or equivalent token validation) was registered on the Express application
  • CWE: CWE-352 (Cross-Site Request Forgery)
  • Fix: Added cookieParser() and csrf({ cookie: true }) as global middleware on the Express server, requiring valid CSRF tokens for all non-safe HTTP methods

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

Missing CSRF protection is one of those vulnerabilities that's easy to overlook — especially in game servers or internal tools where security isn't the primary focus. But as the OWASP Top 10 consistently reminds us, CSRF remains a real threat that can escalate from "harmless game hack" to "account takeover" when applications grow.

The fix for tower_game/index.js demonstrates how minimal the effort is: four lines of code — two imports and two middleware registrations — close an entire class of attacks. If you're building Express applications, make CSRF middleware as automatic as express.json() in your server setup.

References

Frequently Asked Questions

What is CSRF (Cross-Site Request Forgery)?

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 with cross-origin requests.

How do you prevent CSRF in Express.js?

Use the `csurf` middleware (or its successor `csrf-csrf`) with either cookie or session-based token storage, and ensure your frontend includes the CSRF token in state-changing requests via headers or hidden form fields.

What CWE is CSRF?

CWE-352 (Cross-Site Request Forgery) covers vulnerabilities where a web application does not sufficiently verify that a request was intentionally made by the authenticated user.

Is SameSite cookies enough to prevent CSRF?

SameSite cookies provide defense-in-depth but are not sufficient alone — older browsers may not support them, and `Lax` mode still allows top-level GET navigations. A dedicated CSRF token mechanism remains the recommended primary defense.

Can static analysis detect missing CSRF protection?

Yes, tools like Semgrep have rules (such as `express-check-csurf-middleware-usage`) that scan Express application setup code for the absence of CSRF middleware registration, catching this gap before deployment.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #24

Related Articles

high

How Denial of Service via Memory Exhaustion happens in Socket.IO Parser and how to fix it

CVE-2026-69185 is a high-severity Denial of Service vulnerability in the `socket.io-parser` package that allows attackers to exhaust server memory by sending specially crafted packets. The fix upgrades `socket.io-parser` from version 4.2.4 to 4.2.7 (and parallel branches to 3.4.5 and 3.3.6) in `client/package-lock.json`, closing the attack surface against malicious clients. This kind of memory-exhaustion flaw is particularly dangerous in real-time applications where the parser handles a continuo

high

How Quadratic CPU Consumption Happens in js-yaml and How to Fix It

A high-severity denial-of-service vulnerability in js-yaml versions prior to 4.3.1 allowed attackers to craft malicious YAML documents with !!omap tags that triggered quadratic CPU consumption during parsing. This fix upgrades js-yaml from 4.1.1 to 4.3.1 using npm overrides, protecting applications from algorithmic complexity attacks that could freeze or crash Node.js services.

critical

How Denial of Service via Gzip Bomb happens in Node.js and how to fix it

CVE-2026-59873 is a critical Denial of Service vulnerability in node-tar versions prior to 7.5.19, where a maliciously crafted gzip bomb can exhaust server resources when extracting archives. The fix upgrades the `tar` dependency from version 7.5.15 to 7.5.21 in `package-lock.json` and pins the version via an `overrides` block in `package.json`. Any application that processes user-supplied tar archives is at risk of resource exhaustion, making this an urgent upgrade.

high

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

An Express.js application in `src/server.js` was missing CSRF (Cross-Site Request Forgery) protection middleware, leaving all state-changing endpoints vulnerable to forged requests from malicious sites. The fix introduces the `csrf` package to generate and validate tokens on non-GET requests, while exempting API-key-authenticated clients. This defensive hardening raises the bar against automated exploit chaining.

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.

high

How Denial of Service via Unbounded Brace Expansion Happens in Node.js Dependencies and How to Fix It

A critical vulnerability in adm-zip (CVE-2026-39244) allowed attackers to craft malicious ZIP files that trigger unbounded brace expansion, causing excessive memory allocation and process crashes. The CortexKit project fixed this by upgrading adm-zip from 0.5.17 to 0.6.0, which implements bounds checking on expansion operations. This vulnerability demonstrates why dependency management and timely security updates are essential for production Node.js applications.