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

Prevention & Best Practices

1. Never Trust Client-Side Controls

Any security control that only exists in the UI can be bypassed. Always implement authorization checks server-side:

// BAD: Only hiding the button
if (!config.options.readOnly) {
  res.render('admin', { showDeleteButton: true });
}

// GOOD: Server-side enforcement
app.use((req, res, next) => {
  if (config.options.readOnly && isWriteMethod(req.method)) {
    return res.status(403).json({ error: 'Read-only mode' });
  }
  next();
});

2. Use Middleware for Cross-Cutting Concerns

Authorization that applies to multiple routes should be implemented as middleware, not duplicated in each handler:

// Centralized authorization middleware
const enforceReadOnly = (req, res, next) => {
  if (config.readOnly && ['POST', 'PUT', 'DELETE'].includes(req.method)) {
    return res.status(403).send('Forbidden');
  }
  next();
};

// Apply to all routes
app.use(enforceReadOnly);

3. Test Authorization Independently

The new test file test/lib/routers/readOnlySpec.js demonstrates testing authorization separately from UI behavior:

// Test that direct HTTP requests are blocked
it('should block DELETE when readOnly is enabled', async () => {
  const response = await supertest(app)
    .delete('/db/test_db')
    .set('Cookie', sessionCookie);

  expect(response.status).to.equal(302); // Redirected
  // Verify database still exists
});

4. Defense in Depth

Even with middleware enforcement, consider:
- Database-level permissions (MongoDB roles)
- Network segmentation (admin interface on internal network only)
- Audit logging for all destructive operations
- Rate limiting on sensitive endpoints

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.

References

Frequently Asked Questions

What is missing authorization enforcement?

Missing authorization enforcement occurs when security controls exist in configuration or UI but aren't validated on the server, allowing attackers to bypass restrictions via direct API requests.

How do you prevent missing authorization in Node.js Express?

Always implement authorization checks in server-side middleware that executes before route handlers, never rely solely on UI controls to hide or disable functionality.

What CWE is missing authorization enforcement?

CWE-862 (Missing Authorization) covers cases where software does not perform authorization checks when an actor attempts to access a resource or perform an action.

Is hiding UI controls enough to prevent unauthorized actions?

No, hiding UI controls is never sufficient security. Attackers can easily bypass client-side restrictions using tools like curl, Postman, or browser developer tools to send direct HTTP requests.

Can static analysis detect missing authorization?

Yes, static analysis tools can detect missing authorization by identifying routes that lack middleware checks or by comparing configuration-defined restrictions against actual enforcement points.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1900

Related Articles

medium

How Insufficient Password Hashing Cost Factor Happens in Node.js and How to Fix It

A bcrypt password hashing implementation in the User.js model was using a cost factor of 10, which falls below OWASP's 2024 recommendation of 12 for applications handling sensitive data. This fix upgrades the salt rounds from 10 to 12, increasing the computational work required to crack passwords by approximately 4x, significantly improving protection against brute-force attacks on this e-commerce platform.

high

How Arbitrary Code Execution via Template Imports Happens in JavaScript (lodash) and How to Fix It

A high-severity arbitrary code execution vulnerability (CVE-2026-4800) was discovered in lodash's template function, specifically in how it handles the `imports` option with untrusted input. The fix upgrades lodash from version 4.17.21 to 4.18.0 in the project's `package.json` and `yarn.lock`, eliminating the attack surface where crafted template imports could execute arbitrary code on the server.

critical

How Server-Side Request Forgery (SSRF) happens in Node.js IP address parsing and how to fix it

A critical SSRF vulnerability (CVE-2026-69192) was discovered in the ip-address npm package version 10.2.0, which could allow attackers to bypass IP address validation and access internal services. The fix upgrades the dependency to version 10.3.1, which properly handles edge cases in IP address parsing that previously allowed trust-boundary bypasses.

critical

How Sensitive Data Exposure happens in Python web applications and how to fix it

A critical sensitive data exposure vulnerability was discovered in `nodes/google_gemini.py` where the Google Gemini API key was returned in plaintext through a web endpoint. The fix masks the token in API responses, preventing credential theft from any client that queries the token endpoint. This protects downstream users of this Node.js library from unauthorized access to their Google Gemini services.

high

How Authentication Bypass happens in Next.js App Router with Turbopack and how to fix it

A critical authentication bypass vulnerability (CVE-2026-64642) was discovered in Next.js versions prior to 16.2.11, specifically affecting App Router applications using Turbopack with a single locale configuration. This vulnerability allowed attackers to bypass middleware and proxy protections, potentially gaining unauthorized access to protected routes and resources that should have been secured by authentication checks.

critical

How SQL Injection Happens in CSV-to-SQL Converters and How to Fix It

A critical SQL injection vulnerability was discovered in the `csv2sql()` function in `src/data/converter/csv.js`, where CSV data and table names were directly interpolated into SQL INSERT statements without sanitization. The fix implements input validation through identifier sanitization and proper value escaping, eliminating the attack surface while preserving legitimate functionality.