Back to Blog
critical SEVERITY8 min read

How unauthenticated API access happens in Node.js Express endpoints and how to fix it

The `/api/translate` endpoint in `api/translate.js` was publicly accessible without any authentication, allowing anonymous users to freely invoke paid Anthropic Claude or Google Translate API calls. This critical vulnerability exposed the application to API cost abuse, quota exhaustion, and potential data exfiltration. A targeted fix adds a shared-secret header check before any translation logic executes.

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

Answer Summary

This is a missing authentication vulnerability (CWE-306) in a Node.js API handler (`api/translate.js`). The `/api/translate` endpoint accepted POST requests from any anonymous caller, enabling unrestricted use of paid backend translation services (Anthropic Claude / Google Translate). The fix adds a pre-flight check for a `x-translate-secret` header matched against the `TRANSLATE_API_SECRET` environment variable, rejecting unauthenticated requests with HTTP 401 before any downstream API call is made.

Vulnerability at a Glance

cweCWE-306
fixAdded a shared-secret header check (`x-translate-secret` vs `TRANSLATE_API_SECRET` env var) at the top of the handler, returning 401 on mismatch
riskUnlimited free use of paid translation APIs, financial cost abuse, quota exhaustion
languageJavaScript (Node.js)
root causeThe exported `handler` function in `api/translate.js` performed no identity or secret verification before processing requests
vulnerabilityMissing Authentication on Public API Endpoint

How Unauthenticated API Access Happens in Node.js Express Endpoints and How to Fix It

The Problem in Plain Sight

The api/translate.js file is responsible for one job: accept a block of text, call either Anthropic Claude or Google Translate, and return a translation. Every call to those backends costs real money. Yet until this fix landed, any person on the internet — no account, no token, no relationship with the application — could POST to /api/translate and run up the bill.

This is not a subtle logic flaw. It is a missing front door lock on a room that contains a cash register.


The Vulnerability Explained

What the Code Did (and Didn't Do)

Before the fix, the exported handler function in api/translate.js looked roughly like this around line 95:

// api/translate.js (before fix) — line ~106 onward
export default async function handler(req, res) {
  // ... method check (OPTIONS/POST) ...

  // ❌ No authentication check here — jumped straight to input parsing
  const { text, targetLang, sourceLang = 'en', context } = req.body || {};

  if (!text || typeof text !== 'string' || !targetLang) {
    res.status(400).json({ error: 'Missing required fields.' });
    return;
  }

  // ... cache lookup, then API call to Anthropic or Google Translate ...
}

The handler validated what was being asked (is text a string? is targetLang present?) but never validated who was asking. The only friction between the open internet and a paid API call was:

  1. A basic POST method check.
  2. Input validation on text and targetLang.
  3. An in-memory cache — which resets on every cold start and provides zero protection against distributed abuse.

How an Attacker Would Exploit This

The attack requires zero sophistication:

# Anyone on the internet can do this — no credentials required
curl -X POST https://your-app.com/api/translate \
  -H "Content-Type: application/json" \
  -d '{"text": "Hello world", "targetLang": "es"}'

A motivated attacker could:

  1. Run a cost-exhaustion campaign — script thousands of unique translation requests per minute (bypassing the in-memory cache with varied inputs), draining the application's Anthropic or Google Translate API budget.
  2. Exfiltrate translated content — if the application's own users submit sensitive text for translation, an attacker who knows the endpoint can replay or mirror those requests.
  3. Probe the AI backend — use the unauthenticated endpoint to probe Anthropic Claude's behavior with adversarial prompts, potentially revealing system prompt structure or triggering unintended model outputs at the app owner's expense.

The PR description characterizes this as a 2-step chain: the attacker (1) discovers the public endpoint and (2) sends crafted POST requests. There is no step 3 — exploitation is immediate.

Why the In-Memory Cache Doesn't Save You

The handler includes a cache to avoid redundant API calls for identical inputs. This sounds like a partial mitigation, but it is not:

  • The cache lives in process memory and resets on every cold start (relevant for serverless/Vercel deployments where api/translate.js is likely deployed).
  • An attacker generating unique text values (even trivially, by appending a counter) will always miss the cache.
  • The cache is a performance optimization, not a security control.

The Fix

What Changed

The fix inserts a shared-secret authentication check immediately after the method/OPTIONS handling and before any body parsing or API calls:

// api/translate.js (after fix) — inserted at line ~109
const apiSecret = process.env.TRANSLATE_API_SECRET;
if (apiSecret) {
  const provided = req.headers['x-translate-secret'];
  if (!provided || provided !== apiSecret) {
    res.status(401).json({ error: 'Unauthorized.' });
    return;
  }
}

Before vs. After

Before — handler flow:

POST /api/translate
  → method check
  → [NO AUTH CHECK]
  → parse body
  → validate text/targetLang
  → check cache
  → call Anthropic/Google Translate  ← anyone reaches here

After — handler flow:

POST /api/translate
   method check
   check x-translate-secret header vs TRANSLATE_API_SECRET env var
       mismatch?  401 Unauthorized, stop
   parse body
   validate text/targetLang
   check cache
   call Anthropic/Google Translate   only authorized callers reach here

Why This Specific Fix Works

  • Fail-closed when configured: If TRANSLATE_API_SECRET is set in the environment, every request without the correct x-translate-secret header is rejected with a 401 before any downstream logic runs.
  • Opt-in for development: If TRANSLATE_API_SECRET is not set (e.g., in a local dev environment where the variable isn't configured), the check is skipped. This prevents the fix from breaking local development workflows while ensuring production deployments — where the env var should always be set — are protected.
  • No business logic changes: The fix is scoped entirely to the authentication gate. Translation behavior, caching, and error handling for valid authenticated requests are completely unchanged.
  • Cheap to evaluate: A string comparison on a request header is essentially free. There is no performance cost to this check.

Deploying the Fix

To activate the protection, set the environment variable in your deployment:

# Generate a strong random secret
openssl rand -hex 32
# → e.g., 4a7f2c9b1d3e5f8a0b2c4d6e8f1a3b5c...

# Set it in your environment (Vercel, .env, etc.)
TRANSLATE_API_SECRET=4a7f2c9b1d3e5f8a0b2c4d6e8f1a3b5c...

Callers (your own frontend, internal services) must then include the header:

// Authorized client call
fetch('/api/translate', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-translate-secret': process.env.TRANSLATE_API_SECRET
  },
  body: JSON.stringify({ text: 'Hello', targetLang: 'es' })
});

Prevention & Best Practices

1. Apply Authentication at the Earliest Possible Point

The fix places the auth check before body parsing. This is the right pattern — don't do expensive or sensitive work before verifying the caller. In Express-style middleware:

// Middleware pattern — even better for multi-endpoint apps
function requireTranslateSecret(req, res, next) {
  const secret = process.env.TRANSLATE_API_SECRET;
  if (secret && req.headers['x-translate-secret'] !== secret) {
    return res.status(401).json({ error: 'Unauthorized.' });
  }
  next();
}

app.post('/api/translate', requireTranslateSecret, handler);

2. Never Expose Paid API Proxies Without Authentication

Any endpoint that proxies a paid third-party service (OpenAI, Anthropic, Google APIs, Twilio, SendGrid, etc.) is a direct financial liability if left unauthenticated. Treat these endpoints with the same care as payment endpoints.

3. Use Secrets Management, Not Hardcoded Values

The fix correctly reads the secret from process.env.TRANSLATE_API_SECRET. Never hardcode secrets in source. Use:
- Vercel: Project environment variables
- AWS: Secrets Manager or Parameter Store
- Docker: --env-file or mounted secrets

4. Supplement with Rate Limiting (Defense in Depth)

Authentication is the primary control. Rate limiting is a secondary layer. Even authenticated users could abuse the endpoint if no per-user limits exist:

import rateLimit from 'express-rate-limit';

const translateLimiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 20,             // 20 requests per minute per IP
  standardHeaders: true,
  legacyHeaders: false,
});

5. Monitor API Costs as a Security Signal

Set up billing alerts on Anthropic and Google Cloud. A sudden spike in API spend is often the first observable indicator of endpoint abuse. Treat cost anomalies as security incidents.

6. Relevant Standards

  • OWASP API Security Top 10 — API2:2023: Broken Authentication
  • CWE-306: Missing Authentication for Critical Function
  • OWASP Authentication Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html

Key Takeaways

  • The /api/translate handler in api/translate.js had zero authentication before line 109 — any anonymous HTTP client could invoke Anthropic Claude or Google Translate at the application owner's expense.
  • In-memory caches are not security controls — they reset on cold starts and are trivially bypassed with unique inputs, providing no meaningful protection against cost-abuse attacks.
  • The fix is a single, targeted block — a TRANSLATE_API_SECRET environment variable check against the x-translate-secret request header, inserted before any body parsing or API calls.
  • Paid API proxy endpoints deserve authentication by default — if your Node.js handler calls a metered third-party service, assume it will be found and abused if left unauthenticated.
  • The opt-in design (if (apiSecret)) preserves local dev ergonomics while ensuring production deployments with the env var set are fully protected.

How Orbis AppSec Detected This

  • Source: Unauthenticated HTTP POST request to /api/translate — specifically, the req.body object consumed at line ~112 of api/translate.js with no prior identity check.
  • Sink: The downstream call to the Anthropic Claude or Google Translate API client within the same handler — a paid, rate-limited external service triggered on every cache miss.
  • Missing control: No authentication middleware, no API key validation, no JWT check, and no session verification existed anywhere in the handler's execution path before the API client was invoked.
  • CWE: CWE-306 — Missing Authentication for Critical Function.
  • Fix: Inserted a shared-secret header check (x-translate-secret vs. TRANSLATE_API_SECRET) at line 109 of api/translate.js, returning HTTP 401 before any business logic executes for unauthenticated requests.

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 authentication on a paid API proxy is one of those vulnerabilities that feels obvious in retrospect but is easy to miss during rapid development — especially when you're focused on getting the translation feature working and the input validation looks solid. The /api/translate endpoint had reasonable validation of what it received, but no validation of who was sending it.

The fix is small — nine lines — but the security improvement is significant. By checking the x-translate-secret header against TRANSLATE_API_SECRET before any other logic runs, the handler now enforces a clear boundary: only callers who know the secret can trigger a translation. Everyone else gets a 401.

If your Node.js application proxies any paid or sensitive backend service, audit your handlers today. Ask one question for each endpoint: "What stops an anonymous HTTP client from reaching the expensive part?" If the answer is "nothing," you have the same vulnerability that was just fixed here.


References

Frequently Asked Questions

What is missing authentication on an API endpoint?

It means any caller — authenticated user or anonymous bot — can invoke the endpoint and trigger its backend logic without proving identity or authorization. In this case, that meant free access to paid AI translation services.

How do you prevent missing authentication in Node.js API handlers?

Add an authentication check at the very start of the handler function, before any business logic runs. Use environment-variable-stored secrets, JWT validation, or an API gateway to enforce identity on every request.

What CWE is missing authentication?

CWE-306 — "Missing Authentication for Critical Function." It describes situations where a function performing a sensitive or costly action is reachable without verifying the caller's identity.

Is rate limiting enough to prevent this type of abuse?

No. Rate limiting slows abuse but does not stop it. An attacker can still consume your paid API quota within the allowed rate, and in-memory rate limiters reset on cold starts. Authentication is the correct primary control.

Can static analysis detect missing authentication vulnerabilities?

Yes. Tools like Semgrep can be configured to flag exported handler functions that lack an authentication check before reaching a known sensitive sink (e.g., an API client call). Orbis AppSec's multi-agent AI scanner detected exactly this pattern in `api/translate.js`.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #954

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.