Back to Blog
critical SEVERITY5 min read

How Missing Authentication on DELETE Endpoints Happens in Node.js Express and How to Fix It

A critical authentication bypass vulnerability was discovered in the skill-cabinet server where the DELETE /api/skills/:id endpoint allowed any unauthenticated user to delete arbitrary skills from the filesystem. The fix implements loopback origin validation to ensure only requests from localhost can perform destructive operations, while also consolidating delete functionality into a single, protected endpoint.

O
By Orbis AppSec
Published September 2, 2026Reviewed September 2, 2026

Answer Summary

This is a Missing Authentication vulnerability (CWE-306) in a Node.js Express application where the DELETE /api/skills/:id endpoint lacked any authentication or authorization checks. Attackers could send unauthenticated HTTP DELETE requests to delete any skill from the local filesystem. The fix removes the unprotected DELETE endpoint entirely and adds a requireLoopbackOrigin middleware that validates the request's Origin header against a whitelist of loopback addresses (127.0.0.1, localhost, ::1) before allowing destructive operations.

Vulnerability at a Glance

cweCWE-306
fixRemove unprotected endpoint and add loopback origin validation middleware
riskUnauthenticated remote deletion of application data
languageJavaScript (Node.js/Express)
root causeDELETE endpoint exposed without any authentication middleware
vulnerabilityMissing Authentication on Critical Endpoint

Introduction

The server/index.js file in skill-cabinet handles a local web service for managing skills, but a critical flaw at line 131 created a severe security risk. The app.delete("/api/skills/:id") endpoint was completely exposed—no authentication, no authorization, no origin validation. Any process with network access to port 3781 could delete arbitrary skills from the filesystem simply by knowing or guessing a skill ID.

This vulnerability is particularly dangerous because it affects a destructive operation. While the service runs on localhost, browser-based attacks (like malicious JavaScript on a webpage) could potentially exploit this endpoint through cross-origin requests, turning a "local-only" service into a remotely exploitable target.

The Vulnerability Explained

The vulnerable code was deceptively simple—a straightforward Express route handler with no security controls:

app.delete("/api/skills/:id", (req, res) => {
  try {
    const result = deleteIds([req.params.id]);
    const status = result.deleted.length ? 200 : 400;
    res.status(status).json(result);
  } catch (err) {
    res.status(err.status || 500).json({ error: err.message });
  }
});

Notice what's missing: there's no middleware checking who is making this request. The deleteIds() function directly receives the req.params.id and proceeds to remove the corresponding skill from the local filesystem.

The Attack Scenario

An attacker could exploit this in multiple ways:

  1. Direct HTTP Request: Simply run curl -X DELETE http://127.0.0.1:3781/api/skills/my-important-skill from any process on the machine
  2. Browser-Based Attack: A malicious webpage could execute JavaScript that sends DELETE requests to localhost:3781, potentially deleting all skills if the attacker can enumerate or guess skill IDs
  3. Malicious Browser Extension: Extensions have access to make requests to localhost, bypassing same-origin restrictions

The real-world impact here is data loss. Skills stored on the filesystem would be permanently deleted, and without backup mechanisms, users could lose important configurations or custom skills they've created.

The Fix

The fix takes a defense-in-depth approach with two key changes:

1. Remove the Unprotected Endpoint Entirely

The standalone DELETE /api/skills/:id endpoint was removed completely. Instead, delete operations are consolidated into the existing POST /api/skills/delete endpoint, which can handle single or batch deletions.

2. Add Loopback Origin Validation Middleware

A new middleware function validates that requests originate from localhost:

const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);

export function isLoopbackOrigin(origin) {
  if (!origin) return false;
  try {
    return LOOPBACK_HOSTS.has(new URL(origin).hostname);
  } catch {
    return false;
  }
}

function requireLoopbackOrigin(req, res, next) {
  if (!isLoopbackOrigin(req.headers.origin)) {
    res.status(403).json({ error: "Cross-origin request blocked" });
    return;
  }
  next();
}

Before vs After

Before (vulnerable):

app.delete("/api/skills/:id", (req, res) => {
  // No authentication - anyone can delete!
  const result = deleteIds([req.params.id]);
  // ...
});

app.post("/api/skills/delete", (req, res) => {
  // Also no authentication
  // ...
});

After (secured):

// DELETE endpoint removed entirely

app.post("/api/skills/delete", requireLoopbackOrigin, (req, res) => {
  // Now requires loopback origin validation
  // ...
});

app.post("/api/skills/quarantine", requireLoopbackOrigin, (req, res) => {
  // Quarantine also protected
  // ...
});

The fix also updates bin/skill-cabinet.js to properly export and call a start() function, ensuring the server initialization is controlled:

// Before
await import("../server/index.js");

// After
const { start } = await import("../server/index.js");
start();

Prevention & Best Practices

1. Apply Authentication Middleware Globally

For local services, consider applying origin validation to all state-changing endpoints by default:

app.use('/api', requireLoopbackOrigin);

2. Follow the Principle of Least Privilege

Destructive operations (DELETE, bulk modifications) should have stricter access controls than read operations.

3. Use Allowlists, Not Blocklists

The fix uses a Set of known-safe loopback addresses rather than trying to block malicious origins. This is more secure because it defaults to denial.

4. Consider Request Source Validation

For localhost services, validate both the Origin header and consider additional checks like checking the remote address:

const isLocal = req.ip === '127.0.0.1' || req.ip === '::1';

5. Audit All Route Handlers

Review every Express route to ensure appropriate middleware is applied, especially for POST, PUT, DELETE, and PATCH methods.

Key Takeaways

  • The DELETE /api/skills/:id endpoint at line 131 was completely unprotected, allowing filesystem manipulation without authentication
  • Removing unnecessary endpoints reduces attack surface—consolidating delete functionality into POST /api/skills/delete with middleware is more secure
  • The requireLoopbackOrigin middleware now guards both /api/skills/delete and /api/skills/quarantine endpoints
  • Origin header validation using a Set of loopback hosts (127.0.0.1, localhost, ::1, [::1]) provides browser-based attack protection
  • Local services are not inherently safe—browser-based attacks can target localhost endpoints

How Orbis AppSec Detected This

  • Source: HTTP DELETE request to /api/skills/:id with user-controlled id parameter
  • Sink: deleteIds([req.params.id]) in server/index.js:131 which removes files from the filesystem
  • Missing control: No authentication middleware, no origin validation, no authorization check before the destructive operation
  • CWE: CWE-306 (Missing Authentication for Critical Function)
  • Fix: Removed the unprotected DELETE endpoint and added requireLoopbackOrigin middleware to validate that requests originate from localhost before allowing delete and quarantine operations

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 common oversight in local service development: assuming that localhost access is inherently trusted. The skill-cabinet server's DELETE endpoint was a single HTTP request away from allowing any local process—or malicious browser code—to delete user data.

The fix properly implements defense in depth by removing unnecessary attack surface (the standalone DELETE endpoint) and adding origin validation to remaining destructive endpoints. When building local services, always consider that browsers can make requests to localhost, and implement appropriate access controls accordingly.

References

Frequently Asked Questions

What is Missing Authentication vulnerability?

Missing Authentication occurs when an application fails to verify user identity before granting access to protected functionality, allowing unauthorized users to perform sensitive operations.

How do you prevent Missing Authentication in Node.js Express?

Use authentication middleware on all sensitive routes, validate session tokens or API keys, implement origin checks for local services, and follow the principle of least privilege for endpoint access.

What CWE is Missing Authentication?

CWE-306: Missing Authentication for Critical Function describes vulnerabilities where authentication is not performed before allowing access to critical functionality.

Is CORS enough to prevent unauthorized API access?

No, CORS is a browser-enforced policy that doesn't protect against direct HTTP requests from tools like curl, Postman, or malicious scripts. Server-side authentication is always required.

Can static analysis detect Missing Authentication?

Yes, static analysis tools can detect routes lacking authentication middleware by analyzing route definitions and middleware chains, flagging endpoints that handle sensitive operations without auth checks.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2

Related Articles

critical

How Rate Limiting Vulnerabilities Happen in Next.js API Routes and How to Fix It

A critical rate limiting vulnerability in the `/api/claim` endpoint allowed attackers to exhaust the shared GitHub API quota by sending unlimited rapid requests. While the `/api/records` endpoint had proper throttling, the claim route only checked for GitHub rate limiting responses but implemented no per-user rate limiting, enabling abuse of the shared `REGISTRY_TOKEN` quota.

high

How Cross-Site Request Forgery (CSRF) happens in Express.js and how to fix it

A semgrep audit flagged `devboard/server/index.js` for lacking any CSRF middleware, meaning every state-changing route (`POST`, `PUT`, `DELETE` under `/api/*`) could be triggered by a forged cross-origin request riding on a victim's session cookie. The fix wires in `cookie-parser` and `csurf` right after body parsing, so every mutating request now requires a valid, per-session CSRF token before it reaches route handlers.

critical

How missing authorization and code injection happen in Mindustry JavaScript mods and how to fix it

A `TapEvent` handler in `scripts/CommandBlock.js` exposed a full administrative command palette — including a `run-javascript` command that piped player-supplied text straight into `new Function(text)()` — behind nothing more than a team-membership check. Any player who happened to share a team with the block could execute arbitrary JavaScript (and, through Rhino's Java bridge, arbitrary host code) inside the game runtime. The fix adds an explicit `if (!e.player.admin) return;` guard at the top

critical

How Unauthenticated API Exposure Happens in Node.js Koa Routers and How to Fix It

The `/api/adapters` and `/api/list` endpoints in the OneBots framework were registered before authentication middleware, making them publicly accessible to unauthenticated attackers. This critical vulnerability allowed anyone to enumerate all configured adapters, accounts, and sensitive metadata with a simple GET request. The fix ensures these endpoints are protected by the existing auth middleware by correcting route registration order.

high

How Unauthorized SSH Command Execution Happens in Go and How to Fix It

A high-severity vulnerability in `golang.org/x/crypto/ssh` (CVE-2026-39828) allowed attackers to execute unauthorized commands by exploiting discarded SSH permissions. The fix involved upgrading `golang.org/x/crypto` from v0.51.0 to v0.52.0 in `go.mod`, closing an authentication bypass that could be triggered remotely in any Go service using the SSH package.

critical

How Supply Chain Attacks Happen via pnpm Workspace Configuration and How to Fix Them

A pnpm workspace configuration was missing the `minimumReleaseAge` setting, leaving the project vulnerable to supply chain attacks from newly published malicious or compromised npm packages. By adding `minimumReleaseAge: 10080` (seven days in minutes), the fix ensures that only packages that have survived community scrutiny for at least a week are resolved during installation. This defensive hardening is especially critical for web applications where a compromised dependency could introduce XSS,