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/adaptersand/api/listroutes being registered beforeauthMiddlewarecompletely 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/adaptersand/api/listendpoints - Sink: Route handler functions in
packages/onebots/src/routes/adapter-api.ts:19that returnapp.adaptersandapp.accountListdata 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.tsso thatauthMiddlewareis applied to the/api/*prefix before the/api/adaptersand/api/listroute 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.