Back to Blog
high SEVERITY8 min read

How unauthenticated endpoint exposure happens in Node.js Express and how to fix it

A high-severity vulnerability in the AgenticATODetectionService allowed unauthenticated users to access sensitive agent status data, trigger detection scans, and view security alerts. The fix adds authentication middleware to four critical API endpoints, ensuring only authorized users can access these sensitive operations.

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

Answer Summary

This is an unauthenticated endpoint exposure vulnerability in Node.js Express (CWE-306: Missing Authentication for Critical Function). Four API endpoints in agenticATODetectionService.js lacked authentication middleware, allowing any user to access sensitive ATO detection data and trigger scans. The fix adds the authMiddleware parameter to each route handler, enforcing authentication checks before any request processing occurs.

Vulnerability at a Glance

cweCWE-306 (Missing Authentication for Critical Function)
fixAdd authMiddleware to four endpoint route definitions to enforce authentication
riskUnauthorized access to sensitive agent data, detection scans, and security alerts
languageNode.js/JavaScript
root causeAPI routes defined without authentication middleware, exposing critical functions to unauthenticated requests
vulnerabilityUnauthenticated Endpoint Exposure

How unauthenticated endpoint exposure happens in Node.js Express and how to fix it

Introduction

In the backend/services/agenticATODetectionService.js file, we discovered a high-severity vulnerability where four critical API endpoints were accessible to any user without authentication. The setupAPI() method at line 977 defined routes for agent status checks, detection scans, alert retrieval, and statistics—all without requiring users to authenticate first. This meant attackers could access sensitive agent data, trigger security detection scans, and view all security alerts for any agent ID, simply by making HTTP requests to the endpoints. For a service designed to detect account takeover attacks, exposing these capabilities to unauthenticated users directly undermined the security posture of systems relying on this library.

The Vulnerability Explained

What Was Exposed

The AgenticATODetectionService exposes four critical endpoints through its setupAPI() method:

  1. GET /api/ato/agents/:agentId/status - Returns real-time agent status information
  2. POST /api/ato/agents/:agentId/detect - Triggers detection scans for potential compromises
  3. GET /api/ato/alerts - Returns all security alerts across all agents
  4. GET /api/ato/statistics - Returns aggregated statistics about detection activity

The Vulnerable Code

Here's the problematic code from the original implementation (lines 977-1004):

setupAPI(app) {
    app.get('/api/ato/agents/:agentId/status', async (req, res) => {
        try {
            const status = await this.getAgentStatus(req.params.agentId);
            res.json({ success: true, data: status });
        } catch (error) {
            res.status(500).json({ success: false, error: error.message });
        }
    });

    app.post('/api/ato/agents/:agentId/detect', async (req, res) => {
        try {
            const detection = await this.detectCompromisedAgent(req.params.agentId, req.body);
            res.json({ success: true, data: detection });
        } catch (error) {
            res.status(500).json({ success: false, error: error.message });
        }
    });

    app.get('/api/ato/alerts', async (req, res) => {
        try {
            const alerts = await this.getAlerts();
            res.json({ success: true, data: alerts });
        } catch (error) {
            res.status(500).json({ success: false, error: error.message });
        }
    });

    app.get('/api/ato/statistics', async (req, res) => {
        try {
            const stats = await this.getStatistics();
            res.json({ success: true, data: stats });
        } catch (error) {
            res.status(500).json({ success: false, error: error.message });
        }
    });
}

The Problem

Each route is defined with only two parameters: the route path and the async handler function. There is no authentication middleware between them. In Express, middleware functions are passed as parameters before the final handler:

app.get('/path', middleware1, middleware2, handler);

By omitting middleware entirely, these routes accept requests from anyone—authenticated or not.

Attack Scenario

An attacker could exploit this vulnerability in several ways:

  1. Reconnaissance: GET /api/ato/agents/12345/status returns real-time status of agent 12345, revealing whether it's active, last seen time, and detection history—without needing any credentials.

  2. Denial of Service: POST /api/ato/agents/*/detect could be called repeatedly for multiple agent IDs, flooding the service with detection scan requests and consuming resources.

  3. Information Disclosure: GET /api/ato/alerts returns all security alerts across all agents, potentially exposing which systems have been flagged as compromised, enabling attackers to target those systems next.

  4. Brute Force Agent Enumeration: An attacker could iterate through agent IDs (1, 2, 3, ...) calling the status endpoint to discover all agents in the system and their configurations.

Why This Matters

For a library focused on detecting account takeover attacks, exposing detection capabilities to unauthenticated users is particularly dangerous. Attackers could:
- Understand which detection methods are in use
- Disable or interfere with scans
- Access alerts about compromised accounts before legitimate administrators
- Trigger false positives to mask real attacks

The Fix

What Changed

The fix adds the authMiddleware parameter to each of the four route definitions. First, the middleware is imported at the top of the file:

const authMiddleware = require('../middleware/authMiddleware');

Then, each route is updated to include authMiddleware as a parameter before the async handler:

// BEFORE (vulnerable)
app.get('/api/ato/agents/:agentId/status', async (req, res) => {

// AFTER (fixed)
app.get('/api/ato/agents/:agentId/status', authMiddleware, async (req, res) => {

Complete Before/After Comparison

setupAPI(app) {
-   app.get('/api/ato/agents/:agentId/status', async (req, res) => {
+   app.get('/api/ato/agents/:agentId/status', authMiddleware, async (req, res) => {
        try {
            const status = await this.getAgentStatus(req.params.agentId);
            res.json({ success: true, data: status });
        } catch (error) {
            res.status(500).json({ success: false, error: error.message });
        }
    });

-   app.post('/api/ato/agents/:agentId/detect', async (req, res) => {
+   app.post('/api/ato/agents/:agentId/detect', authMiddleware, async (req, res) => {
        try {
            const detection = await this.detectCompromisedAgent(req.params.agentId, req.body);
            res.json({ success: true, data: detection });
        } catch (error) {
            res.status(500).json({ success: false, error: error.message });
        }
    });

-   app.get('/api/ato/alerts', async (req, res) => {
+   app.get('/api/ato/alerts', authMiddleware, async (req, res) => {
        try {
            const alerts = await this.getAlerts();
            res.json({ success: true, data: alerts });
        } catch (error) {
            res.status(500).json({ success: false, error: error.message });
        }
    });

-   app.get('/api/ato/statistics', async (req, res) => {
+   app.get('/api/ato/statistics', authMiddleware, async (req, res) => {
        try {
            const stats = await this.getStatistics();
            res.json({ success: true, data: stats });
        } catch (error) {
            res.status(500).json({ success: false, error: error.message });
        }
    });
}

How This Solves the Problem

By adding authMiddleware to each route, Express now executes the middleware before the async handler function. The middleware:

  1. Intercepts the request before it reaches the handler
  2. Validates the user's credentials (typically checking JWT tokens, session cookies, or API keys)
  3. Allows the request to proceed only if authentication succeeds
  4. Rejects the request with a 401 Unauthorized response if authentication fails

Now, an unauthenticated request to GET /api/ato/agents/12345/status will be rejected by authMiddleware before getAgentStatus() is ever called.

Prevention & Best Practices

1. Apply Authentication by Default

Make it a habit to add authentication middleware to every endpoint that handles sensitive data or operations. In Express, this means:

// ✅ GOOD: Middleware applied
app.post('/api/sensitive-operation', authMiddleware, handler);

// ❌ BAD: No middleware
app.post('/api/sensitive-operation', handler);

2. Use Established Authentication Patterns

Don't reinvent authentication. Use proven libraries:

  • JWT (JSON Web Tokens) with libraries like jsonwebtoken or passport-jwt
  • OAuth 2.0 for third-party integrations
  • Session-based authentication with express-session
  • API key validation for service-to-service communication

3. Implement Role-Based Access Control (RBAC)

Beyond authentication, add authorization checks to ensure users can only access resources they're permitted to:

app.get('/api/ato/agents/:agentId/status', 
    authMiddleware, 
    roleMiddleware('admin', 'security_officer'),  // Only admins or security officers
    async (req, res) => {
        // handler
    }
);

4. Audit All Endpoints

Regularly review your routes to ensure:
- Public endpoints (login, health checks) are intentionally public
- Private endpoints have appropriate middleware
- Admin endpoints have stricter authentication/authorization

Use this checklist:

// Intentionally public ✓
app.get('/api/health', handler);
app.post('/api/auth/login', handler);

// Requires authentication ✓
app.get('/api/user/profile', authMiddleware, handler);

// Requires admin role ✓
app.delete('/api/agents/:id', authMiddleware, adminMiddleware, handler);

5. Use Static Analysis Tools

Tools like Orbis AppSec, ESLint security plugins, and Semgrep can automatically detect routes missing authentication middleware. This vulnerability was caught by the multi_agent_ai scanner rule before it reached production.

6. Test Unauthenticated Access

In your test suite, verify that endpoints reject unauthenticated requests:

describe('AgenticATODetectionService', () => {
    it('should reject unauthenticated requests to /api/ato/agents/:id/status', async () => {
        const response = await request(app)
            .get('/api/ato/agents/123/status')
            .expect(401);  // Unauthorized
    });

    it('should allow authenticated requests to /api/ato/agents/:id/status', async () => {
        const token = generateValidJWT();
        const response = await request(app)
            .get('/api/ato/agents/123/status')
            .set('Authorization', `Bearer ${token}`)
            .expect(200);  // Success
    });
});

Key Takeaways

  • Never expose sensitive endpoints without authentication middleware. The AgenticATODetectionService handles critical security detection—every endpoint needed authMiddleware applied.

  • Middleware order matters in Express. Authentication must be added as a parameter before the handler function, not after, or it won't be executed.

  • Four endpoints were fixed in one change. The GET /api/ato/agents/:agentId/status, POST /api/ato/agents/:agentId/detect, GET /api/ato/alerts, and GET /api/ato/statistics routes all received the same fix pattern.

  • Unauthenticated access to detection scans is critical. Allowing attackers to trigger detectCompromisedAgent() could be used to interfere with security monitoring or cause denial of service.

  • Static analysis can catch this pattern. The Orbis AppSec multi_agent_ai scanner identified all four missing middleware instances, enabling an automated fix before deployment.

How Orbis AppSec Detected This

Source: Express route definitions in setupAPI() method (lines 977-1004)

Sink: Four app.get() and app.post() calls lacking middleware parameters in backend/services/agenticATODetectionService.js

Missing control: No authentication middleware (authMiddleware) parameter passed to route handlers for sensitive operations

CWE: CWE-306 (Missing Authentication for Critical Function)

Fix: Added authMiddleware parameter to four route definitions, enforcing authentication before handler execution:
- Line 978: app.get('/api/ato/agents/:agentId/status', authMiddleware, ...)
- Line 986: app.post('/api/ato/agents/:agentId/detect', authMiddleware, ...)
- Line 994: app.get('/api/ato/alerts', authMiddleware, ...)
- Line 1003: app.get('/api/ato/statistics', authMiddleware, ...)

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

Unauthenticated endpoint exposure is a critical vulnerability that's surprisingly common—developers often forget to add authentication middleware when creating new routes, especially in rapidly evolving services. The AgenticATODetectionService fix demonstrates how easily this can happen: four sensitive endpoints were exposed in a production library simply because the developer didn't add one parameter to each route definition.

The fix is minimal but powerful: by adding authMiddleware to each route, the service now enforces authentication on all sensitive operations. This is a reminder that security is often about the small details—a single missing parameter can expose critical functionality to attackers.

For developers building Node.js services:
1. Make authentication middleware a required parameter for any endpoint handling sensitive data
2. Use static analysis tools to catch missing middleware before code review
3. Test that unauthenticated requests are properly rejected
4. Document which endpoints are intentionally public and why

By following these practices, you can prevent unauthenticated endpoint exposure in your own applications and protect your users from account takeover attacks and data breaches.

References

Frequently Asked Questions

What is unauthenticated endpoint exposure?

It's a security flaw where API endpoints that should require authentication are accessible to any user, including attackers, without credentials or authorization checks.

How do you prevent unauthenticated endpoint exposure in Node.js?

Always add authentication middleware to Express routes that handle sensitive operations. Use established patterns like passport.js, JWT validation, or custom auth middleware that checks credentials before allowing request processing.

What CWE is unauthenticated endpoint exposure?

CWE-306 (Missing Authentication for Critical Function) covers this vulnerability class, where critical functions lack proper authentication controls.

Is rate limiting enough to prevent unauthenticated endpoint exposure?

No. Rate limiting slows down attacks but doesn't prevent unauthorized access. You must implement actual authentication—rate limiting is a supplementary control, not a replacement.

Can static analysis detect unauthenticated endpoint exposure?

Yes. Static analysis tools can identify Express routes that lack middleware, especially when comparing against a list of sensitive functions that require authentication. Orbis AppSec detected this pattern in the production code.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #1284

Related Articles

high

How Path Traversal and Security Policy Bypass Happens in Node.js Dependencies and How to Fix It

A high-severity vulnerability in the fast-uri package (CVE-2026-6321) allowed attackers to bypass security policies through improper Unicode hostname canonicalization and path traversal. This issue affected the @apralabs/apra-fleet project through its dependency tree, and was resolved by upgrading fast-uri from version 3.1.0 to 4.1.2 using npm overrides.

high

How Command Injection Happens in Node.js child_process and How to Fix It

A high-severity command injection vulnerability was discovered in `scripts/common.js` where the `exec()` function used `execSync()` with unsanitized input, allowing potential command injection attacks. The fix replaces `execSync()` with `execFileSync()` and separates command arguments into an array, preventing shell metacharacter interpretation. This defensive hardening removes an exploit primitive that could be chained with other weaknesses by automated attack tools.

critical

How Prototype Pollution Denial of Service Happens in Node.js HTTP Libraries and How to Fix It

A critical prototype pollution vulnerability in axios versions 1.12.0 and earlier could allow attackers to trigger denial of service attacks by poisoning the configuration object through the `__proto__` key. The vulnerability was fixed by upgrading axios to 1.13.5 and updating related dependencies like follow-redirects to 1.16.0, which implements stricter input validation in the mergeConfig function.

high

How IP Address Parsing Inconsistency Happens in Node.js and How to Fix It

CVE-2026-69192 revealed a critical inconsistency in the `ip-address` npm package where the `Address4` class decoded leading-zero octets as decimal while standard DNS resolvers interpreted them as octal, creating a trust-boundary bypass and SSRF attack vector. The fix upgrades `ip-address` from version 10.2.0 to 10.3.1 in the CanvaLight plugin, correcting the parsing behavior to match resolver expectations.

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.

high

How Cache-Control Header Mishandling Happens in Node.js HTTP Clients and How to Fix It

CVE-2026-13697 is a high-severity vulnerability in undici, the popular Node.js HTTP client, where the cache interceptor fails to properly validate malformed `Cache-Control: private` directives. This could allow sensitive cached responses to be served to unauthorized users. The fix upgrades undici from 7.28.0 to 7.29.0 (and 6.27.0 to 6.28.0) across the dependency tree, including using npm overrides to patch transitive dependencies.