Title: Unauthenticated access to portal management endpoints in shared mode exposes LettaBot pairing and agent status metadata
Description:
LettaBot's local management HTTP API fails to enforce the documented API key requirement on portal-backed read endpoints when the agent conversation mode is shared, which is the project's default. A remote attacker who can reach the HTTP API can query management metadata without authentication, including pairing request listings and agent status details.
The issue is in src/api/server.ts. The GET /api/v1/pairing/:channel and GET /api/v1/status routes do not apply unconditional API key validation. Instead, both routes only reject unauthenticated requests when the effective portal mode is not shared:
if (getPortalMode(options) !== 'shared' && !validateApiKey(req.headers, options.apiKey)) {
sendError(res, 401, 'Unauthorized');
return;
}getPortalMode() returns shared when all agents are in shared mode, and the main application sets shared as the default conversation mode when no explicit mode is configured. In that default state, the authorization branch is skipped and the route proceeds to sensitive reads:
GET /api/v1/pairing/:channelreturns pending pairing requests from the local pairing store.GET /api/v1/statusreturns agent metadata includingagentId,conversationId, channels,baseUrl, and timestamps.
The finding was verified through the real HTTP route handlers in createApiServer() using localhost requests. The control case switched the same harness to per-channel mode and received 401 Unauthorized on both endpoints, confirming that the behavior is a mode-dependent authorization bypass rather than a benign no-op.
- Node.js and the repository dependencies needed to run the project TypeScript sources through
tsx - Python 3
- Network access to the exposed LettaBot HTTP API
- The target instance running with the default
sharedconversation mode, or equivalent logic that causesgetPortalMode()to resolve toshared
- Download the verification PoC from: verification_test.py
- Download the control script from: control_normal_behavior.py
- Run the verification script:
python3 verification_test.py - Observe that the script starts the real HTTP server in
sharedmode and sends unauthenticated requests to:GET /api/v1/pairing/telegramGET /api/v1/status
- Confirm that both requests return
200and that the status response contains agent metadata. - Run the control script:
python3 control_normal_behavior.py - Confirm that the same unauthenticated requests return
401when the server runs inper-channelmode.
[INFO] Running control (per-channel mode) and vulnerable (shared mode) checks
--- server output (per-channel) ---
[20:06:24] INFO: [API] Server listening on 127.0.0.1:18080
--- server output (shared) ---
[20:06:24] INFO: [API] Server listening on 127.0.0.1:18081
[INFO] Control pairing status: 401
[INFO] Control status endpoint status: 401
[INFO] Vuln pairing status: 200
[INFO] Vuln status endpoint status: 200
[INFO] Vuln pairing body: {"requests":[]}
[INFO] Vuln status body: {"agents":{"LettaBot":{"agentId":"agent-canary","conversationId":"conv-canary","conversations":{},"channels":["telegram"],"baseUrl":"http://localhost:8283","createdAt":"2026-06-12T00:00:00.000Z","lastUsedAt":"2026-06-12T00:00:00.000Z"}}}
[DEFECT CONFIRMED] Shared-mode portal endpoints are reachable without API key and leak agent-status / conversation metadata; non-shared mode blocks the same requests.
This is an authentication bypass on management-plane read endpoints. Any unauthenticated party that can reach the LettaBot HTTP API can enumerate portal-backed management metadata that should be restricted to API key holders. The exposed data includes pairing state and internal agent status, which can assist follow-on targeting and reveal deployment topology, active channels, conversation identifiers, and service base URLs.
- Ecosystem: npm
- Package name: lettabot
- Affected versions:
0.2.0unreleased GitHub source snapshot at commit99c3b5dd73550fe0a4eac2ee31b1c3229ca9e550 - Patched versions:
- Severity: Medium
- Vector string: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
- CWE: CWE-306: Missing Authentication for Critical Function
| Permalink | Description |
|---|---|
| https://github.com/letta-ai/lettabot/blob/99c3b5dd73550fe0a4eac2ee31b1c3229ca9e550/src/api/server.ts#L449-L456 | The GET /api/v1/pairing/:channel route skips API key enforcement whenever getPortalMode(options) resolves to shared, allowing unauthenticated reads of pairing request data. |
| https://github.com/letta-ai/lettabot/blob/99c3b5dd73550fe0a4eac2ee31b1c3229ca9e550/src/api/server.ts#L707-L714 | The GET /api/v1/status route uses the same mode-gated authorization check, exposing agent status metadata to unauthenticated callers in shared mode. |
| https://github.com/letta-ai/lettabot/blob/99c3b5dd73550fe0a4eac2ee31b1c3229ca9e550/src/api/server.ts#L928-L934 | getPortalMode() returns shared when no explicit non-shared mode is configured, making the insecure branch reachable under the default conversation-mode behavior. |