How Exposed Debug Endpoints Happen in Express.js and How to Fix It
Introduction
In a production Express.js application, the routes.js file contained a /test endpoint designed for ISBN search testing—but it was accessible to anyone, anywhere, without any authentication or authorization checks. This wasn't a subtle bug; it was a critical security gap that could allow attackers to discover system capabilities, gather reconnaissance information, and potentially chain this information into more sophisticated attacks.
The vulnerability existed at line 56 in routes.js, where the route handler for /test was defined without any middleware to verify the requester's identity or authorization level. In a web service where remote attackers can reach every endpoint, this is a direct, exploitable vulnerability.
The Vulnerability Explained
What Went Wrong
Debug and test endpoints are common in development—they help developers verify functionality, test integrations, and troubleshoot issues. The problem occurs when these endpoints make it into production without proper access controls.
Here's the vulnerable code from routes.js:
/**
* Runs through tests for the ISBN search
* @param {Object} req The request to parse
* @param {Object} res The response to send
*/
router.get('/test', async (req, res) => {
res.send(await catalogues.testIsbnSearch())
})
The Problem: This endpoint accepts requests from anyone. There's no authentication middleware, no authorization check, no environment validation—nothing. An attacker can simply make a GET request to /test and receive whatever catalogues.testIsbnSearch() returns.
Why This Is Dangerous
Debug endpoints typically expose:
- System information: Node.js version, environment configuration, loaded modules
- Internal functionality: Test cases that reveal how the system works
- Catalog structure: In this case, the ISBN search implementation details
- Error messages: Stack traces and internal error handling that aid reconnaissance
An attacker could:
1. Reconnaissance: Call /test to understand the application's capabilities and structure
2. Vulnerability mapping: Learn which libraries and versions are in use
3. Logic discovery: Understand the ISBN search algorithm to find edge cases or bypasses
4. Chaining attacks: Use this information to craft more targeted attacks against authenticated endpoints
Attack Scenario
An attacker scanning your production domain discovers the /test endpoint through common enumeration techniques (directory fuzzing, GitHub repository analysis, etc.). They make a simple request:
curl https://production-api.example.com/test
The response exposes the ISBN search test results, potentially revealing:
- Database structure
- Search algorithm behavior
- Error handling patterns
- System configuration details
This information becomes the foundation for more sophisticated attacks.
The Fix
The fix is elegant and environment-aware. Here's the exact change made to routes.js:
/**
* Runs through tests for the ISBN search
+ * Restricted to non-production environments as it exposes internal
+ * debugging functionality with no authentication.
* @param {Object} req The request to parse
* @param {Object} res The response to send
*/
router.get('/test', async (req, res) => {
+ if (process.env.NODE_ENV === 'production') {
+ return res.status(404).end()
+ }
res.send(await catalogues.testIsbnSearch())
})
What Changed
Lines 52-53: Added a comment explicitly documenting that this endpoint is restricted to non-production environments and exposes internal functionality.
Lines 59-61: Added an environment check that:
- Checks if NODE_ENV is set to 'production'
- Returns a 404 Not Found response if it is
- Allows the endpoint to function normally in development/testing environments
Why This Works
- Environment-based gating: The endpoint is only accessible when
NODE_ENV !== 'production', which is the standard Node.js convention for environment detection - 404 response: Returning 404 (Not Found) instead of 403 (Forbidden) provides no information to attackers—the endpoint appears to not exist
- Preserves development: Developers can still test the ISBN search functionality locally and in staging environments
- Zero false positives: The fix doesn't break legitimate development workflows
Security Improvement
Before: Any remote attacker could access /test and retrieve debugging information
After: The endpoint is invisible in production (returns 404) and only accessible in development environments where you control access
Prevention & Best Practices
1. Never Ship Debug Endpoints Without Guards
Always ask: "Could this endpoint expose sensitive information or system details?" If yes, add authentication and/or environment checks.
2. Use Middleware for Cross-Cutting Concerns
Instead of adding environment checks to every debug endpoint, create reusable middleware:
// middleware/devOnly.js
function devOnly(req, res, next) {
if (process.env.NODE_ENV === 'production') {
return res.status(404).end();
}
next();
}
// routes.js
router.get('/test', devOnly, async (req, res) => {
res.send(await catalogues.testIsbnSearch())
});
router.get('/debug', devOnly, async (req, res) => {
res.send(await getDebugInfo())
});
3. Separate Development and Production Routes
Keep debug routes in a separate file or router and only mount them in development:
// routes/debug.js
const debugRouter = express.Router();
debugRouter.get('/test', async (req, res) => { /* ... */ });
module.exports = debugRouter;
// app.js
if (process.env.NODE_ENV !== 'production') {
app.use('/debug', require('./routes/debug'));
}
4. Use Static Analysis Tools
Tools like Semgrep can detect routes without authentication middleware:
# semgrep rule to find unprotected routes
- id: express-unprotected-route
pattern-either:
- pattern: router.get(...)
- pattern: router.post(...)
message: Route may lack authentication
5. Follow OWASP Guidelines
The OWASP Top 10 includes A01:2021 – Broken Access Control. Debug endpoints without access controls are a textbook example. Always verify:
- Is the endpoint supposed to be public?
- Does it require authentication?
- Does it require specific authorization levels?
6. Code Review Checklist
When reviewing routes, ask:
- [ ] Does this endpoint expose system information?
- [ ] Is it intended for development only?
- [ ] Are there authentication/authorization checks?
- [ ] Is the endpoint necessary in production?
- [ ] Could this aid in reconnaissance?
Key Takeaways
-
Debug endpoints are attack surfaces: The
/testendpoint inroutes.jsexposed internal functionality without any access controls, creating a reconnaissance vector for attackers. -
Environment-based gating is simple but effective: The fix uses
NODE_ENVto prevent the endpoint from functioning in production, a pattern that's easy to implement and understand. -
Return 404, not 403: Returning 404 provides no information to attackers, whereas 403 confirms the endpoint exists but is forbidden—use 404 for endpoints that shouldn't exist in certain contexts.
-
Reusable middleware prevents repetition: Creating a
devOnlymiddleware function ensures consistent protection across all development-only endpoints. -
Static analysis catches these issues: Security scanners can identify routes lacking authentication, preventing debug endpoints from reaching production in the first place.
How Orbis AppSec Detected This
Source: HTTP GET request to the /test endpoint in production environment
Sink: The route handler router.get('/test', ...) at line 56 in routes.js with no authentication or authorization middleware
Missing control: No environment-based access control, no authentication middleware, no authorization checks on the route handler
CWE: CWE-306 (Missing Authentication for Critical Function) and CWE-693 (Protection Mechanism Failure)
Fix: Added environment-based access control that returns 404 when NODE_ENV === 'production', restricting the debug endpoint to development environments only
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
The /test endpoint vulnerability in routes.js demonstrates why debug functionality must never reach production without explicit access controls. What seems like a harmless testing endpoint can become a reconnaissance tool for attackers mapping your system's capabilities.
The fix—a simple environment check—is elegant and effective. It preserves development workflows while eliminating the attack surface in production. By adopting the practices outlined above—middleware-based access control, environment-based gating, and security-focused code review—you can prevent similar vulnerabilities in your Express.js applications.
Remember: In web services, every endpoint is reachable by potential attackers. Treat debug endpoints as security-critical and protect them accordingly.
References
- CWE-306: Missing Authentication for Critical Function
- CWE-693: Protection Mechanism Failure
- OWASP Top 10 2021 - A01:2021 Broken Access Control
- OWASP Authentication Cheat Sheet
- Express.js Middleware Documentation
- Semgrep Rule: Missing Authentication in Express Routes
- fix: fix security issue in routes.js