Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save YLChen-007/2ba2e586f3d16cb368c8dcd6ef680178 to your computer and use it in GitHub Desktop.

Select an option

Save YLChen-007/2ba2e586f3d16cb368c8dcd6ef680178 to your computer and use it in GitHub Desktop.
lettabot: Unauthenticated access to portal management endpoints in shared mode exposes LettaBot pairing and agent status metadata

Advisory Details

Title: Unauthenticated access to portal management endpoints in shared mode exposes LettaBot pairing and agent status metadata

Description:

Summary

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.

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/:channel returns pending pairing requests from the local pairing store.
  • GET /api/v1/status returns agent metadata including agentId, 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.

PoC

Prerequisites

  • 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 shared conversation mode, or equivalent logic that causes getPortalMode() to resolve to shared

Reproduction Steps

  1. Download the verification PoC from: verification_test.py
  2. Download the control script from: control_normal_behavior.py
  3. Run the verification script: python3 verification_test.py
  4. Observe that the script starts the real HTTP server in shared mode and sends unauthenticated requests to:
    • GET /api/v1/pairing/telegram
    • GET /api/v1/status
  5. Confirm that both requests return 200 and that the status response contains agent metadata.
  6. Run the control script: python3 control_normal_behavior.py
  7. Confirm that the same unauthenticated requests return 401 when the server runs in per-channel mode.

Log of Evidence

[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.

Impact

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.

Affected products

  • Ecosystem: npm
  • Package name: lettabot
  • Affected versions: 0.2.0 unreleased GitHub source snapshot at commit 99c3b5dd73550fe0a4eac2ee31b1c3229ca9e550
  • Patched versions:

Severity

  • Severity: Medium
  • Vector string: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N

Weaknesses

  • CWE: CWE-306: Missing Authentication for Critical Function

Occurrences

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment