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
•Technically reviewed by Anupam Mediratta•Published August 24, 2026•Reviewed 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.

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.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #24

Related Articles

critical

Lampa Desktop Auto-Update Heuristic Bypass: Execution of Unverified

Lampa Desktop's auto-update mechanism downloaded JavaScript and CSS from `raw.githubusercontent.com` using only heuristic validation—file size thresholds and string pattern matching—that attackers could trivially satisfy. The fix introduces cryptographic integrity verification by cross-referencing Git blob hashes from the GitHub Contents API, ensuring downloaded code matches the repository's authoritative state before execution.

high

adm-zip 0.6.0 Preserves SUID Bits From ZIPs: CVE-2026-102282

The `adm-zip` dependency resolved to 0.6.0 in this project's dependency tree, a version affected by CVE-2026-102282: during extraction it applies the Unix permission bits stored in each ZIP entry's external file attributes verbatim, including the setuid (`04000`), setgid (`02000`), and sticky bits. An attacker who controls an archive passed to `extractAllTo()` or `extractEntryTo()` can therefore have the extractor create a setuid binary owned by whatever user the extraction process runs as. The

high

requestInput() Type Confusion: NaN and Object Bypass in JavaScript

The `requestInput()` utility function lacked validation on its `type` parameter and failed to handle `NaN` results from float conversions, creating a type confusion weakness. An attacker could supply malformed inputs that propagate unhandled `NaN` values or unexpected object types through the type system. The fix adds explicit guards against `NaN` type parameters and rejects non-primitive type values.

critical

No Rate Limit on /api/uploads/presign Enables DoS

The `/api/uploads/presign` endpoint accepted unlimited concurrent requests to generate storage presigned URLs, giving an attacker a free lever to exhaust storage-provider quotas and server resources. The fix adds an `express-rate-limit` middleware capping each client to 30 requests per minute on that route.

high

CVE-2026-54673: builder-util-runtime Leaks Auth Headers on Redirect

electron-updater and electron-builder rely on builder-util-runtime to fetch update manifests and artifacts over HTTP. A flaw in that shared HTTP executor allowed credential headers attached to the original update-feed request to be re-sent after a redirect, exposing them to any host the redirect pointed to. The project fixes this by upgrading builder-util-runtime to 9.7.0 and collapsing a duplicate, older copy of the package that electron-updater had pinned on its own.

high

image-size 1.2.1 DoS: Zero-Valued Dimensions in Image Buffer Parser

A high-severity denial-of-service vulnerability in image-size 1.2.1 allows attackers to crash Node.js services using malicious image buffers with zero-valued dimensions. The fix removes the vulnerable `queue` dependency and tightens dimension validation in version 2.0.3.