Back to Blog
critical SEVERITY6 min read

How missing authorization enforcement happens in Node.js Express routers and how to fix it

A critical authorization bypass was discovered in lib/router.js where readOnly and noDelete configuration options were only enforced through UI controls, not server-side middleware. Any authenticated user could bypass these restrictions by sending direct HTTP requests to perform destructive operations like database deletion or document modification. The fix adds Express middleware that enforces these security modes at the server level, blocking POST, PUT, and DELETE requests when appropriate.

O
By Orbis AppSec
Published July 31, 2026Reviewed July 31, 2026

Answer Summary

This vulnerability is a missing authorization enforcement issue (CWE-862) in a Node.js Express router where security controls (readOnly and noDelete modes) were only enforced client-side through hidden UI elements. Attackers could bypass these by sending direct HTTP requests. The fix adds server-side middleware in lib/router.js that checks the request method against configuration flags and redirects unauthorized operations before they reach route handlers.

Vulnerability at a Glance

cweCWE-862 (Missing Authorization)
fixAdded Express middleware to enforce readOnly and noDelete modes server-side
riskAny authenticated user can delete databases, export sensitive data, or modify documents
languageNode.js / Express.js
root causeSecurity controls only enforced in UI templates, not in backend request handlers
vulnerabilityMissing Server-Side Authorization Enforcement

Introduction

In the lib/router.js file of a MongoDB administration interface, we discovered a critical authorization bypass that could allow any authenticated user to perform destructive database operations. The application had readOnly and noDelete configuration options designed to protect production databases, but these controls were only enforced through hidden UI elements—not at the server level.

This meant that while the web interface would hide delete buttons and edit forms when readOnly was enabled, an attacker could simply send a DELETE /db/production_db request directly and completely bypass these protections. The vulnerable pattern existed around line 331 in router.js, where route handlers processed requests without validating the application's security configuration.

The Vulnerability Explained

The core issue was a classic client-side security anti-pattern: trusting the UI to enforce authorization decisions. The application's config.options.readOnly and config.options.noDelete flags were used by templates to conditionally render controls, but the actual route handlers never checked these flags.

Here's what the request flow looked like before the fix:

HTTP Request  Express Router  Route Handler  Database Operation
                                  No authorization check for readOnly/noDelete

The templates would hide the "Delete Database" button when readOnly was true, but nothing stopped this direct request:

# Attacker bypasses UI restrictions entirely
curl -X DELETE https://admin.example.com/db/production_db \
  -H "Cookie: session=authenticated_user_session"

Real-World Attack Scenarios

Scenario 1: Production Database Deletion
An operator configures the admin interface with readOnly: true to let developers view production data safely. A malicious or compromised developer account sends DELETE /db/production_db, wiping the entire production database.

Scenario 2: Sensitive Data Exfiltration
With noDelete enabled but no read restrictions, an attacker sends GET /db/production_db/users/export to bulk-export sensitive user data, bypassing any UI-level export restrictions.

Scenario 3: Document Tampering
An authenticated user with view-only intentions sends PUT /db/production_db/users/doc/admin_user_id to modify administrative records, escalating their privileges or corrupting critical data.

The vulnerability is particularly severe because the application is explicitly designed for database administration—the very operations it exposes are inherently destructive.

The Fix

The fix introduces Express middleware that enforces readOnly and noDelete at the server level, before requests ever reach route handlers:

/*
 * Server-side enforcement of readOnly and noDelete. The templates hide the controls and
 * two collection handlers check the flags, but nothing stopped a direct HTTP request, so
 * a DELETE still removed the document with readOnly turned on.
 *
 * Redirects go to the configured base URL rather than res.locals.baseHref: the latter is
 * derived from req.originalUrl, and CodeQL flags feeding request-controlled data into
 * res.redirect. config.site.baseUrl is server configuration the caller cannot influence.
 */
appRouter.use(function (req, res, next) {
  const safeRedirect = () => res.redirect(config.site.baseUrl || '/');

  if (config.options.readOnly && ['POST', 'PUT', 'DELETE'].includes(req.method)) {
    req.session.error = 'Application is running in read-only mode!';
    return safeRedirect();
  }
  if (config.options.noDelete && req.method === 'DELETE') {
    req.session.error = 'Delete operations are not permitted!';
    return safeRedirect();
  }
  next();
});

Key Security Improvements

1. Server-Side Enforcement: The middleware runs before any route handler, ensuring that no code path can bypass the check.

2. Method-Based Blocking: When readOnly is enabled, all state-changing methods (POST, PUT, DELETE) are blocked. When only noDelete is set, just DELETE requests are blocked.

3. Safe Redirect Pattern: The redirect uses config.site.baseUrl (server configuration) rather than req.originalUrl (user-controlled input), preventing a secondary open redirect vulnerability that CodeQL would flag.

4. User Feedback: The middleware sets req.session.error to inform users why their action was blocked, rather than silently failing.

Request Flow After Fix

HTTP Request  Express Router  Authorization Middleware  Route Handler  Database Operation
                                                                      Checks readOnly/noDelete
                              Redirects if unauthorized

Key Takeaways

  • UI controls are not security controls: The readOnly template logic created a false sense of security while leaving the actual API unprotected
  • Middleware placement matters: The authorization middleware was added before route definitions, ensuring it runs first for all requests
  • Redirect URLs must use trusted sources: Using config.site.baseUrl instead of req.originalUrl prevents introducing an open redirect vulnerability in the fix
  • Test the bypass, not just the feature: Security tests should verify that direct HTTP requests are blocked, not just that UI elements are hidden
  • Configuration options need enforcement code: Having a readOnly config option is meaningless without code that actually enforces it

How Orbis AppSec Detected This

  • Source: Authenticated HTTP requests reaching route handlers in lib/router.js
  • Sink: Database operation endpoints (DELETE, PUT, POST handlers) that execute without checking config.options.readOnly or config.options.noDelete
  • Missing control: No server-side middleware enforcing the readOnly and noDelete configuration flags before route handlers execute
  • CWE: CWE-862 (Missing Authorization)
  • Fix: Added Express middleware at line 331 that checks request method against configuration flags and redirects unauthorized operations with appropriate error messages

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 a fundamental security principle: authorization must be enforced at the point of action, not just at the point of display. The readOnly and noDelete options gave operators confidence that their databases were protected, but that protection evaporated the moment someone used curl instead of a web browser.

The fix is elegant in its simplicity—a single middleware function that checks two configuration flags. But its placement in the request pipeline, before any route handler, is what makes it effective. When implementing similar protection in your own applications, remember: if an HTTP request can reach your server, assume someone will send it directly, regardless of what your UI allows.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1900

Related Articles

critical

deleteNestedProperty Prototype Pollution via Dot-Notation Path

The `deleteNestedProperty` function in propertyUtils.ts allowed attackers to manipulate JavaScript object prototypes by passing specially crafted dot-notation paths like `__proto__.polluted`. A fix now blocks dangerous keys before processing, preventing prototype pollution attacks that could affect all objects in the application.

high

How Denial of Service via Infinite Loop Happens in JavaScript Dependencies and How to Fix It

CVE-2026-67213 is a high-severity denial of service vulnerability in nanoid before version 5.1.6 that triggers an infinite loop during random ID generation when processing specially crafted input. We upgraded nanoid across the entire dependency tree to patch this flaw and prevent attackers from freezing application threads. This fix ensures that ID generation remains resilient even when handling adversarial input patterns.

high

How Sensitive Data Exposure happens in Zotero plugins and how to fix it

A high-severity data exposure vulnerability in `Zotero.ts` automatically transmitted complete document metadata—including private notes, attachment paths, and tags—to external LLM services without user consent. The fix replaces broad `item.toJSON()` serialization with explicit field selection, sending only essential bibliographic data.

high

How missing dependency update cooldowns happen in GitHub Dependabot configurations and how to fix it

A semgrep scan flagged `.github/dependabot.yml` for lacking a cooldown period, meaning Dependabot would immediately propose updates to brand-new package versions across npm, Bundler, and Docker ecosystems. The fix adds a `cooldown: default-days: 7` block to every `package-ecosystem` entry, forcing a one-week waiting period before newly published releases are considered — reducing exposure to malicious or unstable package drops.

high

How Path Traversal Happens in TensorFlow's Data Service and How to Fix It

TensorFlow's data service dispatcher validated dataset IDs against forward-slash traversal attacks but overlooked backslash characters on non-Windows platforms, allowing attackers to escape the root directory. A targeted fix adds explicit backslash validation across all platforms, closing a high-severity path traversal vulnerability in the snapshot management system.

critical

How Unbounded WebSocket Message Handling Causes Resource Exhaustion in Node.js and How to Fix It

The WebSocketCrossServerAdapter class in a popular Node.js WebSocket library lacked any rate limiting on inbound messages, allowing attackers to flood Redis nodes and WebSocket servers with high-volume traffic. The fix introduces a configurable `rateLimit` option that caps messages per connection per second, preventing resource exhaustion while preserving legitimate functionality.