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();

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.

Prevention and further reading

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #2

Related Articles

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

How Information Disclosure Vulnerabilities Happen in Python APIs and How to Fix It

A critical information disclosure vulnerability in the Hermes plugin dashboard API was exposing sensitive filesystem paths, credential file locations, and configuration details without authentication. The fix redacts this sensitive information from API responses, replacing absolute paths with boolean status indicators to prevent attackers from locating and targeting credential files.

critical

ExternalHttpClient::request() Sent Basic Auth Over Plain HTTP

The `ExternalHttpClient::request()` helper accepted a `$basicAuth` string and passed it straight to the HTTP client's `auth` option without checking that the target URL used `https://`. Any external JSON data source configured with an `http://` endpoint therefore shipped a base64-encoded `Authorization: Basic` header in cleartext on every scheduled load. The fix rejects the request outright — before a client is even created — when the URL scheme is not HTTPS.