Back to Blog
critical SEVERITY5 min read

POST /api/generate Lacks Authentication, Allowing Unauthenticated

A resume generation endpoint in a Node.js backend accepted requests from any caller with network access, allowing attackers to consume OpenAI API quota without restriction. The vulnerability stemmed from missing authentication middleware on a cost-bearing endpoint. The fix adds mandatory API key validation via HTTP headers before processing any generation requests.

O
By Orbis AppSec
•Published September 25, 2026•Reviewed September 25, 2026

Answer Summary

The POST /api/generate endpoint in a Node.js resume service accepted requests without any authentication check, allowing any caller with network access to invoke resume generation and consume OpenAI API quota. An attacker could make unlimited requests to this endpoint to deplete credits or incur unexpected charges. The fix implements a `requireApiKey()` middleware that validates an `x-api-key` header against a server-side API_KEY environment variable before allowing the route to execute. This is a CWE-862 (Missing Authorization) vulnerability.

Vulnerability at a Glance

cweCWE-862 (Missing Authorization)
fixAdd API key header validation middleware to all resume routes
riskUnauthenticated attackers can consume API quota and incur costs
languageJavaScript (Node.js)
root causePOST /api/generate route lacks authentication middleware
vulnerabilityMissing authorization on cost-bearing endpoint

A critical gap in a cost-bearing endpoint

Imagine deploying a resume generation service backed by OpenAI's API—a feature that incurs real costs per request. Now imagine that any caller on your network can invoke it without proving who they are. That's the reality of this vulnerability.

The POST /api/generate endpoint in the resume service had no authentication middleware. It simply accepted requests and passed them to the resume controller, which then made expensive OpenAI API calls. There was no check of identity, no credential validation, no rate limiting by user. The endpoint was, in effect, public to anyone with network access.

Affected Versions

Affected not applicable (first-party code)
Fixed in not applicable (first-party code)
Ecosystem Node.js / npm
CVE / GHSA not assigned
CWE CWE-862 (Missing Authorization)

The Vulnerability Explained

Before the fix, the route definition looked like this:

const express = require('express');
const router = express.Router();
const resumeController = require('../controllers/resumeController');

// POST /api/generate
router.post('/generate', resumeController.generateResume);

Anyone could make a POST request to /api/generate and trigger resume generation. There was no middleware intercepting the request to verify the caller's identity. The endpoint went straight from the HTTP listener to the business logic.

Why is this dangerous?

Resume generation is not free. Each call to generateResume likely invokes OpenAI's API, consuming tokens and incurring charges. An attacker who discovers this endpoint can:

  • Make thousands of requests in rapid succession to exhaust the API quota
  • Force the service to incur unexpected bills
  • Trigger a denial of service by consuming all available API calls for legitimate users
  • Do all of this from inside the local network, across container boundaries, or via a compromised machine on the same subnet

The attack in practice:

An attacker on the same network (or with access to an internal service) writes a simple loop:

for i in {1..10000}; do
  curl -X POST http://localhost:5000/api/generate \
    -H "Content-Type: application/json" \
    -d '{"input": "My resume data"}'
done

Within minutes, thousands of resume generations execute, each hitting the OpenAI API. The service owner receives a bill for thousands of API calls they never authorized. Meanwhile, legitimate users attempting to generate resumes find the quota exhausted.

The vulnerability exists because the route has no authorization control—no way to verify that the caller is allowed to use this resource.

The Fix

The fix introduces a middleware function called requireApiKey() that runs before the route handler:

function requireApiKey(req, res, next) {
  const expectedKey = process.env.API_KEY;
  if (!expectedKey) {
    console.error('API_KEY is not configured on the server');
    return res.status(500).json({ success: false, message: 'Server misconfiguration' });
  }
  const providedKey = req.header('x-api-key');
  if (providedKey !== expectedKey) {
    return res.status(401).json({ success: false, message: 'Unauthorized' });
  }
  return next();
}

router.use(requireApiKey);

router.post('/generate', resumeController.generateResume);

What changed:

  1. Environment variable setup: The .env.example file now includes API_KEY=your_app_api_key_here, signaling to operators that they must generate and configure a secret key before deployment.

  2. Middleware validation: The requireApiKey() function checks that:
    - API_KEY is configured (if not, returns 500 to alert operators)
    - The x-api-key header in the incoming request matches the server's API_KEY
    - If the keys don't match, the request is rejected with a 401 Unauthorized response

  3. Route protection: router.use(requireApiKey) applies this middleware to all routes defined on the router, including POST /api/generate. The middleware runs before the route handler, so no request reaches resumeController.generateResume() unless the API key is valid.

Why this solves the problem:

  • Before: Any request could reach the controller.
  • After: Only requests carrying the correct API key in the x-api-key header proceed.

An attacker trying the previous attack now receives:

HTTP/1.1 401 Unauthorized
{"success": false, "message": "Unauthorized"}

The request never reaches the OpenAI API. The attacker has no way to generate resumes unless they obtain the secret API_KEY value.

How the attack changes with the fix

With the fix in place, an attacker's loop now fails immediately:

curl -X POST http://localhost:5000/api/generate
# HTTP/1.1 401 Unauthorized
# {"success": false, "message": "Unauthorized"}

To exploit the endpoint, the attacker must now:
1. Discover or guess the API_KEY value (a random secret)
2. Pass it in every request via the x-api-key header

This requirement transforms the attack from trivial to cryptographically hard. The service is no longer exposed to unauthenticated quota theft.

Key Takeaways

  • Cost-bearing endpoints need authentication, not just CORS headers. CORS (Cross-Origin Resource Sharing) controls which origins can call your API; it says nothing about who can call it. OpenAI API endpoints require explicit credential checks.

  • Environment variables for secrets belong in .env.example. By adding API_KEY=your_app_api_key_here to the example config, operators are reminded to generate and configure a unique key before deployment. Omitting this from the example invites forgotten configuration and accidental exposure.

  • Middleware applies in declaration order. By placing router.use(requireApiKey) before the route definition, every subsequent route on that router is protected. This is simpler and less error-prone than protecting individual routes.

  • Request headers are the standard transport for API keys in REST services. The fix uses the x-api-key header, which is widely recognized and doesn't conflict with HTTP authentication headers. This makes the API predictable for clients.

  • Authorization failures should return 401, not 403. A 401 (Unauthorized) tells the client "you need to authenticate"; a 403 (Forbidden) says "you are authenticated but not allowed." This distinction helps clients distinguish between missing credentials and insufficient permissions.

How Orbis AppSec Detected This

Source: The HTTP request body and headers to the POST /api/generate endpoint accept any incoming request without authentication.

Sink: The resumeController.generateResume() function invokes the OpenAI API based on unauthenticated input, incurring costs and consuming quota.

Missing control: No authentication or authorization middleware validates the caller's identity before the resume generation function executes.

CWE: CWE-862 (Missing Authorization)

Fix: Add a requireApiKey() middleware that validates the x-api-key HTTP header against a server-side API_KEY environment variable before allowing any resume route to execute.

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 authorization on cost-bearing endpoints is a practical, high-impact vulnerability. An attacker with network access doesn't need to be sophisticated—they just need to know the endpoint exists. This fix demonstrates that protecting such endpoints requires explicit, layered validation: the endpoint should demand proof of identity before executing expensive operations. The API key middleware pattern used here is standard in REST services and scales from single endpoints to entire route groups. Deploy it consistently across any endpoint that triggers real-world costs or resource consumption.

Prevention and further reading

Frequently Asked Questions

Why does the fix check for a missing API_KEY environment variable and return 500 instead of failing fast?

If the server misconfiguration goes unnoticed, an attacker could bypass the check by knowing the expected key was never set. Explicitly erroring allows operators to catch deployment mistakes before the endpoint becomes publicly exploitable.

Does CORS misconfiguration mentioned in the PR description compound this authorization bypass?

Yes—unrestricted CORS means a malicious webpage can make cross-origin requests to POST /api/generate from a victim's browser, and if that browser has network access to the backend, the attack succeeds without the victim's knowledge.

Could a loopback-only middleware have prevented this, as the description mentions for admin routes?

No; loopback restriction (127.0.0.1 only) stops remote attackers but not local privilege escalation or container-to-container attacks. API key authentication works across all network boundaries and is the correct control for a multi-user or internet-facing service.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #3

Related Articles

critical

SchedulePush.disableReminder Missing Authorization Check in push.js

The `disableReminder` method in the `SchedulePush` class allowed any user to disable push notification reminders for arbitrary user IDs by manipulating the `e.user_id` event parameter. The fix adds a `checkFriend()` authorization gate that verifies the requesting user has a valid friendship relationship with the bot before modifying subscription state.

critical

How Broken Object-Level Authorization happens in Express.js and how to fix it

A critical authorization flaw in `src/v1/routes/index.js` allowed any authenticated API key holder to access arbitrary budgets by manipulating the `budgetSyncId` URL parameter. The fix introduces an environment-based allowlist that validates budget access before processing requests.

high

How Missing Rate Limiting Enables Denial of Service Attacks in Node.js and How to Fix It

The k-skill-proxy server exposed multiple public API endpoints (`/health`, `/v1/vworld/search`, `/v1/fine-dust/report`, `/v1/assembly/bills`) without consistent rate limiting middleware, leaving them vulnerable to denial-of-service attacks. A `buildRateLimiter` function existed but wasn't applied to all endpoints. This fix ensures rate limiting is enforced on all public endpoints, preventing resource exhaustion attacks.

high

How Sandboxed Iframe Popup Restriction Bypass happens in Electron and how to fix it

A high-severity flaw in Electron (CVE-2026-70608) allowed sandboxed iframes to bypass the `allow-popups` sandbox restriction through the internal OpenURL navigation path, letting malicious or compromised embedded content spawn unauthorized popup windows. The fix upgrades Electron from 40.10.6 to 41.10.3 (also patched in 42.0.1 and 39.8.10), closing the navigation-layer gap without requiring any application code changes.

critical

How Missing Authentication on DELETE Endpoints Happens in Python aiohttp and How to Fix It

A critical missing authentication vulnerability in `pz_minimax.py` allowed any network-connected user to delete stored MiniMax prompts via the `DELETE /pz_easyuse/minimax-prompts/{index}` endpoint without any access control. An attacker could enumerate sequential indices to wipe all user-created prompts from the shared JSON file. The fix restricts the DELETE endpoint to localhost-only requests by checking `request.remote` against loopback addresses.

critical

Yandex Translate API Key Leaked via URL Query Parameter

The `translateYandex()` helper built its request URL by interpolating the caller-supplied API key directly into the query string, meaning every call leaked the credential into server access logs, proxy logs, and any Referer header sent by intermediaries. The fix switches the request from a GET with the key in the URL to a POST with the key in the request body via `URLSearchParams`, removing the credential from any URL-logging surface entirely.