Back to Blog
critical SEVERITY7 min read

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.

O
By Orbis AppSec
Published August 31, 2026Reviewed August 31, 2026

Answer Summary

This is a broken authentication vulnerability (CWE-306) in a Node.js/Koa application where the `/api/adapters` and `/api/list` route handlers in `packages/onebots/src/routes/adapter-api.ts` were registered before the authentication middleware was applied to `/api/*` routes. This made sensitive adapter enumeration endpoints publicly accessible. The fix corrects the middleware ordering so that authentication checks execute before these route handlers, ensuring unauthenticated requests receive 401/403 responses.

Vulnerability at a Glance

cweCWE-306 (Missing Authentication for Critical Function)
fixReorder route registration so auth middleware executes before adapter API endpoints
riskUnauthenticated attackers can enumerate all adapters, accounts, and connection metadata
languageTypeScript (Node.js / Koa)
root causeRoute handlers registered before auth middleware in the Koa middleware chain
vulnerabilityMissing Authentication on Sensitive API Endpoints

Introduction

The file packages/onebots/src/routes/adapter-api.ts is responsible for exposing adapter management endpoints in the OneBots multi-bot framework. These endpoints—/api/adapters and /api/list—return detailed information about every configured bot adapter and account, including adapter types, connection metadata, and account identifiers. But a flaw at line 19 of this file created a critical security gap: these route handlers were registered before the authentication middleware was applied to the /api/* route prefix, making them completely accessible to unauthenticated attackers.

This isn't a hypothetical risk. A single curl command—GET /api/adapters—would return the full internal topology of a running OneBots instance. For developers building bot orchestration platforms or any Node.js application with Koa routing, this vulnerability illustrates a deceptively simple mistake with severe consequences.

The Vulnerability Explained

How Koa Middleware Ordering Works

In Koa (and Express-like frameworks), middleware executes in the order it is registered. When you write:

// Auth middleware applied to /api/* prefix
router.use('/api', authMiddleware);

// But these were registered BEFORE the auth middleware
router.get('/api/adapters', async (ctx) => {
  ctx.body = app.adapters.map(adapter => adapter.toJSON());
});

router.get('/api/list', async (ctx) => {
  ctx.body = app.accountList;
});

The route handlers for /api/adapters and /api/list match and execute before the authMiddleware ever gets a chance to run. Koa's router resolves the first matching handler in registration order. Because these specific GET routes were defined prior to the router.use('/api', authMiddleware) call, the auth check was effectively bypassed for these two endpoints—even though every other /api/* route was properly protected.

The Specific Attack Scenario

An unauthenticated attacker targeting a running OneBots instance would execute:

# Step 1: Enumerate all adapters without any credentials
curl https://target-instance/api/adapters

# Step 2: Enumerate all accounts and connection metadata
curl https://target-instance/api/list

The response would include:
- Adapter types (e.g., OneBot v11, v12, ICQQ, Discord)
- Account identifiers for every configured bot
- Connection metadata including protocol types and configuration details
- Status information revealing which adapters are active

This is a classic 2-step exploitation chain: first enumerate the attack surface (adapter types and accounts), then use that information to craft targeted attacks against specific adapter protocols or accounts. The enumerated data could also be used for social engineering or to identify high-value targets in the bot infrastructure.

Why This Is Critical

This vulnerability doesn't just leak metadata—it exposes the entire internal architecture of the bot management system. An attacker gains a complete map of:
1. What messaging platforms are connected
2. Which accounts are active
3. What adapter configurations are in use

This information dramatically reduces the effort needed for further exploitation and violates the principle of least privilege at the most fundamental level: the application doesn't even ask "who are you?" before handing over its configuration.

The Fix

The fix is surgically precise: ensure the authentication middleware is registered before the /api/adapters and /api/list route handlers in the Koa middleware chain within packages/onebots/src/routes/adapter-api.ts.

Before (Vulnerable)

// adapter-api.ts — routes registered BEFORE auth middleware
export function registerAdapterRoutes(router: Router, app: OneBots) {
  // These handlers execute without authentication
  router.get('/api/adapters', async (ctx) => {
    ctx.body = app.adapters.map(adapter => adapter.toJSON());
  });

  router.get('/api/list', async (ctx) => {
    ctx.body = app.accountList;
  });

  // Auth middleware registered AFTER — too late for the routes above
  router.use('/api', authMiddleware);
}

After (Fixed)

// adapter-api.ts — auth middleware registered FIRST
export function registerAdapterRoutes(router: Router, app: OneBots) {
  // Auth middleware now executes before any /api/* route handler
  router.use('/api', authMiddleware);

  // These handlers are now protected — unauthenticated requests get 401/403
  router.get('/api/adapters', async (ctx) => {
    ctx.body = app.adapters.map(adapter => adapter.toJSON());
  });

  router.get('/api/list', async (ctx) => {
    ctx.body = app.accountList;
  });
}

Why This Works

By moving the authMiddleware registration above the route handler definitions, Koa's middleware chain now processes authentication before attempting to resolve and execute the adapter API routes. Any request to /api/adapters or /api/list without a valid authentication token now receives a 401 Unauthorized or 403 Forbidden response, and the route handler code never executes.

The change is scoped to a single file (adapter-api.ts) and only affects the order of middleware registration. Valid authenticated requests continue to work exactly as before—the behavior change is exclusively for unauthenticated requests, which are now correctly rejected.

Regression Test Validation

The accompanying regression test confirms the fix holds:

const payloads = [
  { desc: "missing token", auth: undefined },
  { desc: "malformed token", auth: "Bearer invalid.token.here" },
  { desc: "expired token", auth: "Bearer eyJhbGciOiJIUzI1NiIs..." }
];

test.each(payloads)("rejects %s on /api/adapters", async ({ auth }) => {
  const res = await request(app.callback())
    .get("/api/adapters")
    .set(auth ? "Authorization" : "X-No-Auth", auth || "true");
  expect([401, 403]).toContain(res.status);
});

This test exercises three distinct failure modes—missing, malformed, and expired tokens—against both endpoints, ensuring the auth middleware correctly gates access regardless of how the authentication failure occurs.

Prevention & Best Practices

1. Middleware-First Architecture

Always register authentication and authorization middleware before route handlers. In Koa and Express, consider a pattern where auth middleware is applied at the application level:

// Apply auth to ALL /api routes at the app level
app.use(mount('/api', authMiddleware));

// Individual route files don't need to worry about auth ordering

2. Explicit Public Route Whitelisting

Instead of protecting routes individually, protect everything by default and explicitly whitelist public endpoints:

const PUBLIC_ROUTES = ['/api/health', '/api/version'];

app.use(async (ctx, next) => {
  if (PUBLIC_ROUTES.includes(ctx.path)) {
    return next();
  }
  await authMiddleware(ctx, next);
});

3. Integration Testing for Auth Boundaries

Every sensitive endpoint should have a test that verifies unauthenticated access is rejected. The regression test in this PR is an excellent template—adapt it for every new route you add.

4. Security Linting and Static Analysis

Use tools that can detect middleware ordering issues:
- Semgrep rules for Koa/Express middleware ordering
- ESLint security plugins for route handler analysis
- Multi-agent AI scanners that trace data flow through middleware chains

5. Defense in Depth

Even with middleware ordering fixed, consider additional layers:
- Rate limiting on all API endpoints
- IP allowlisting for management APIs
- Audit logging for adapter enumeration attempts
- Network segmentation to limit who can reach the management API

Key Takeaways

  • Koa middleware registration order is the security boundary: In adapter-api.ts, the /api/adapters and /api/list routes being registered before authMiddleware completely negated authentication for those endpoints.
  • Sensitive enumeration endpoints are high-value targets: The adapter and account list endpoints exposed the entire internal topology of the OneBots instance—adapter types, account IDs, and connection metadata—to unauthenticated attackers.
  • A single line reorder fixed a critical vulnerability: Moving the router.use('/api', authMiddleware) call above the route handler registrations was the entire fix—demonstrating that security bugs aren't always complex code errors but can be simple ordering mistakes.
  • Regression tests for auth boundaries prevent recurrence: The test suite covering missing, malformed, and expired tokens on both endpoints ensures this specific bypass cannot be reintroduced by future code changes.
  • "Protected by default" architectures prevent this class of bug entirely: If all /api/* routes required authentication at the application level with explicit opt-out for public routes, this ordering mistake would have been impossible.

How Orbis AppSec Detected This

  • Source: Unauthenticated HTTP GET requests to /api/adapters and /api/list endpoints
  • Sink: Route handler functions in packages/onebots/src/routes/adapter-api.ts:19 that return app.adapters and app.accountList data directly to the response body
  • Missing control: Authentication middleware (authMiddleware) was registered after the route handlers in the Koa middleware chain, so it never executed for these specific endpoints
  • CWE: CWE-306 (Missing Authentication for Critical Function)
  • Fix: Reordered middleware registration in adapter-api.ts so that authMiddleware is applied to the /api/* prefix before the /api/adapters and /api/list route handlers are defined

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 in packages/onebots/src/routes/adapter-api.ts is a textbook example of how middleware ordering in Node.js frameworks can silently undermine your entire authentication model. The /api/adapters and /api/list endpoints were intended to be protected—the auth middleware existed and was applied to the /api/* prefix—but a registration ordering mistake meant it never ran for these specific routes.

The fix was minimal (reordering route and middleware registration), but the security impact was critical. Every developer working with Koa, Express, or similar middleware-based frameworks should internalize this lesson: the order you register middleware is your security architecture. When in doubt, apply authentication at the highest possible level and whitelist exceptions explicitly.

References

Frequently Asked Questions

What is missing authentication for critical function?

It occurs when an application exposes sensitive functionality—like API endpoints returning internal configuration—without verifying the caller's identity, allowing anyone to access protected resources.

How do you prevent unauthenticated API access in Node.js Koa?

Ensure authentication middleware (e.g., JWT verification) is registered in the middleware chain before any route handlers that serve sensitive data. In Koa, middleware executes in registration order, so ordering is critical.

What CWE is missing authentication?

CWE-306: Missing Authentication for Critical Function. It covers cases where an application does not perform any authentication for functionality that requires a provable user identity.

Is route-level middleware enough to prevent authentication bypass?

Route-level middleware works only if applied correctly to every sensitive route. A more robust approach is to use application-level middleware that protects entire route prefixes (e.g., `/api/*`) and explicitly whitelist only public endpoints.

Can static analysis detect missing authentication on routes?

Yes. Static analysis tools and multi-agent AI scanners can trace route registration order, identify endpoints lacking auth middleware, and flag publicly accessible sensitive routes before they reach production.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #236

Related Articles

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

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 Unauthenticated HTTP Endpoints happen in Node.js ECP Servers and how to fix it

The ECP (External Control Protocol) server in `src/server/ecp.js` exposed device control endpoints—like launching apps and sending keypresses—over the local network with zero authentication. Any attacker sharing the same Wi-Fi or LAN could send unauthenticated HTTP requests to take full control of the simulator. The fix introduces local-only binding controls and access restrictions to close this attack surface.

high

How Authorization Bypass and Balance Corruption happen in Node.js and how to fix it

A high-severity authorization bypass in `commands/profile/transfer.js` allowed any user to transfer coins directly to owner/admin accounts, bypassing privilege checks entirely. Compounding the issue, the absence of a numeric guard on `targetDb.coin` could corrupt balances with `NaN` when the field was uninitialized. Three targeted lines of code closed both attack surfaces without changing any valid transfer behavior.

critical

How Cross-Site Scripting happens in fast-xml-parser and how to fix it

CVE-2026-25896 is a critical Cross-Site Scripting vulnerability in fast-xml-parser stemming from improper DOCTYPE entity handling, which could allow attackers to inject malicious scripts through crafted XML payloads. The fix upgrades the vulnerable dependency from version 4.4.1 to patched versions 5.3.5 and 4.5.4, eliminating the unsafe parsing behavior while preserving all legitimate XML processing functionality.