Back to Intelligence

CVE-2026-77936: Letta AI Agent Server Ships Unauthenticated API and Exposed PostgreSQL — Detection and Remediation Guide

SA
Security Arsenal Team
October 20, 2026
12 min read

The National Vulnerability Database has published CVE-2026-77936, a CVSS 9.8 (Critical), network-exploitable vulnerability affecting Letta through version 0.16.8 — an increasingly popular open-source framework for building stateful AI agents. While the CVE is cataloged against the PostgreSQL attack surface, the root cause is a deployment failure in Letta's shipped compose.yaml: the file never sets LETTA_SERVER_SECURE, so Letta's password middleware is never loaded, every API route is unauthenticated, and the bundled PostgreSQL instance is exposed to the network with default superuser credentials.

This is the kind of chained misconfiguration that keeps IR teams busy. A remote, unauthenticated attacker can:

  • Execute arbitrary Python via POST /v1/tools/run on the Letta server
  • Create or modify admin users via /v1/admin endpoints
  • Run operating system commands by abusing PostgreSQL's COPY ... FROM PROGRAM feature as the database superuser

If your organization is piloting AI agent frameworks — and in 2026, most are — you need to assume at least one developer has stood up Letta via the stock Docker Compose file, possibly on an internet-facing VM. This post walks through the attack chain, detection content you can deploy today, and the remediation path.

Technical Analysis

Affected Products and Versions

ComponentAffected VersionsNotes
Letta serverThrough 0.16.8When deployed using the shipped compose.yaml
Bundled PostgreSQLAs shipped in the compose stackExposed port, default superuser credentials

The vulnerable condition requires both of the following to be true:

  1. The deployment was created from Letta's stock compose.yaml (the default path most users take), and
  2. The operator did not manually set LETTA_SERVER_SECURE=true and a strong LETTA_SERVER_PASSWORD, and did not override the database credentials.

Deployments fronted by an authenticating reverse proxy, or built with a hardened custom compose file, are not in the vulnerable state — but you should verify, not assume.

Root Cause: Two Failures, One Chain

Failure 1 — Authentication middleware never registered. Letta's server gates its REST API behind password middleware that is only added when the LETTA_SERVER_SECURE environment variable is set. The shipped compose.yaml omits it. The result: routes like /v1/tools/run (which executes arbitrary Python as a "tool") and /v1/admin/* (user management) are reachable by anyone who can open a TCP connection. POST /v1/tools/run with an attacker-controlled tool body is effectively unauthenticated remote code execution inside the Letta container.

Failure 2 — PostgreSQL superuser exposed with default credentials. The compose stack publishes the PostgreSQL port and retains default superuser credentials. With authenticated database access as a superuser, an attacker can issue:

Code
COPY tbl FROM PROGRAM '<os command>'

COPY ... FROM PROGRAM executes the supplied command as the OS user running the PostgreSQL process. It is a legitimate administrative feature that becomes a direct command-execution primitive when superuser credentials are default and the port is network-reachable.

Attack Chain (Defender's View)

  1. Discovery: Attacker scans for Letta's API port (default 8283) or PostgreSQL (5432) exposed on target hosts. The unauthenticated Letta API answers requests that a hardened instance would reject with 401.
  2. Initial execution: POST /v1/tools/run with a Python payload — code runs inside the Letta server container.
  3. Persistence / privilege expansion: POST /v1/admin/users to create a rogue admin account, giving durable API access even if the original path is noticed.
  4. Alternative / parallel path: Connect to PostgreSQL on 5432 with default credentials, then use COPY FROM PROGRAM to run OS commands as the database user — a foothold that bypasses the Letta application layer entirely.
  5. Post-exploitation: Container escape attempts, credential harvesting from environment variables (LLM API keys are almost always present in these deployments), lateral movement to internal networks the host can reach.

Exploitation Status

As of publication, CVE-2026-77936 is newly published on NVD and has not yet been added to the CISA Known Exploited Vulnerabilities catalog. However, the exploitation requirements are trivially low: no authentication, no user interaction, no race conditions — just an HTTP request or a database login. Exposed AI-agent infrastructure is an active scanning target in 2026, and internet-facing instances should be treated as already probed and potentially compromised until proven otherwise. Check CISA KEV status at https://www.cisa.gov/known-exploited-vulnerabilities-catalog and the NVD entry at https://nvd.nist.gov/vuln/detail/CVE-2026-77936 for updates.

Detection & Response

This is a technical threat — the detections below target the specific behaviors in the attack chain: unauthenticated tool execution, rogue admin creation, and COPY FROM PROGRAM abuse. Deploy them alongside the network-level hunting queries.

Sigma Rules

YAML
---
title: PostgreSQL COPY FROM PROGRAM Command Execution
description: Detects PostgreSQL superuser abuse of COPY FROM PROGRAM to execute operating system commands, as seen in exploitation of CVE-2026-77936 against exposed Letta deployments. Source can be PostgreSQL server logs ingested via Sysmon, auditd, or file-based log collection.
references:
  - https://nvd.nist.gov/vuln/detail/CVE-2026-77936
  - https://attack.mitre.org/techniques/T1059/
author: Security Arsenal
id: 8c2f4a71-3b5e-4d9a-b6c1-7e2f8a4d5c90
status: experimental
date: 2026/04/06
tags:
  - attack.execution
  - attack.t1059
logsource:
  product: linux
  service: postgresql
detection:
  selection:
    - 'COPY '
    - 'PROGRAM'
  condition: selection
falsepositives:
  - Rare legitimate ETL or administrative use of COPY FROM PROGRAM by DBAs; baseline approved usage and alert on all deviations
level: high
---
title: Shell or Interpreter Spawned by PostgreSQL Server Process
description: Detects the PostgreSQL backend process spawning shells or scripting interpreters, a strong post-exploitation indicator of COPY FROM PROGRAM abuse in CVE-2026-77936.
references:
  - https://nvd.nist.gov/vuln/detail/CVE-2026-77936
  - https://attack.mitre.org/techniques/T1059.006/
author: Security Arsenal
id: 3d7b9e52-1c4a-4f8b-a2d6-9e1c5b7a3f08
status: experimental
date: 2026/04/06
tags:
  - attack.execution
  - attack.t1059.006
logsource:
  category: process_creation
  product: linux
detection:
  selection_parent:
    ParentImage|endswith:
      - '/postgres'
  selection_child:
    Image|endswith:
      - '/sh'
      - '/bash'
      - '/dash'
      - '/zsh'
      - '/python'
      - '/python3'
      - '/perl'
      - '/curl'
      - '/wget'
      - '/nc'
      - '/ncat'
  condition: selection_parent and selection_child
falsepositives:
  - Legitimate PostgreSQL extensions or maintenance scripts; uncommon in most environments
level: critical
---
title: Outbound Network Connection from PostgreSQL or Letta Server Process
description: Detects unexpected outbound network connections from postgres or letta server processes, consistent with reverse shells or data exfiltration following CVE-2026-77936 exploitation.
references:
  - https://nvd.nist.gov/vuln/detail/CVE-2026-77936
  - https://attack.mitre.org/techniques/T1071/
author: Security Arsenal
id: 5a1e6c84-7d2b-4f3c-98a1-2b4d6e8f0c17
status: experimental
date: 2026/04/06
tags:
  - attack.command_and_control
  - attack.exfiltration
  - attack.t1071.001
logsource:
  category: network_connection
  product: linux
detection:
  selection_proc:
    Image|endswith:
      - '/postgres'
      - '/letta'
      - '/uvicorn'
  filter_loopback:
    DestinationIp|startswith:
      - '127.'
      - '::1'
  filter_internal_pg:
    DestinationPort: 5432
  condition: selection_proc and not filter_loopback and not filter_internal_pg
falsepositives:
  - Letta legitimately calls LLM provider APIs (api.openai.com, api.anthropic.com); tune with a destination allowlist for known model endpoints
level: medium

KQL — Microsoft Sentinel / Defender

Letta and PostgreSQL logs reach Sentinel through Syslog/CEF ingestion; process data arrives via Defender for Endpoint or the Azure Monitor agent. The query below hunts the three core behaviors: COPY FROM PROGRAM abuse, shells spawned under postgres, and unauthenticated hits against the sensitive Letta routes.

KQL — Microsoft Sentinel / Defender
// Hunt 1: PostgreSQL COPY FROM PROGRAM abuse via Syslog ingestion
Syslog
| where TimeGenerated > ago(7d)
| where ProcessName has_any ("postgres", "postmaster")
| where SyslogMessage has_all ("COPY", "PROGRAM")
| project TimeGenerated, Computer, ProcessName, SyslogMessage, HostIP
| order by TimeGenerated desc;

// Hunt 2: Shell/interpreter spawned by postgres or letta process (MDE)
DeviceProcessEvents
| where TimeGenerated > ago(7d)
| where InitiatingProcessFileName has_any ("postgres", "letta", "uvicorn")
| where FileName has_any ("sh", "bash", "dash", "python", "python3", "perl", "curl", "wget", "nc", "ncat", "socat")
| project TimeGenerated, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, FileName, ProcessCommandLine, AccountName
| order by TimeGenerated desc;

// Hunt 3: Unauthenticated requests to sensitive Letta API routes (via W3C/CEF web or container logs in CommonSecurityLog)
CommonSecurityLog
| where TimeGenerated > ago(7d)
| where RequestURL has_any ("/v1/tools/run", "/v1/admin")
| where RequestMethod == "POST"
| extend IsSensitiveToolRun = RequestURL has "/v1/tools/run"
| project TimeGenerated, SourceIP, DestinationIP, DestinationPort, RequestMethod, RequestURL, IsSensitiveToolRun
| order by TimeGenerated desc;

// Hunt 4: Exposed PostgreSQL — inbound connections to 5432 from non-RFC1918 sources
CommonSecurityLog
| where TimeGenerated > ago(7d)
| where DestinationPort == 5432
| where not(ipv4_is_private(SourceIP))
| summarize ConnectionCount = count(), Sources = make_set(SourceIP) by DestinationIP, DestinationPort
| order by ConnectionCount desc;

Velociraptor VQL

Use this artifact to sweep Linux hosts for the live forensic indicators: postgres/letta processes with suspicious children, listening database ports, and COPY FROM PROGRAM strings in PostgreSQL logs.

VQL — Velociraptor
-- CVE-2026-77936: Hunt for exposed/exploited Letta + PostgreSQL deployments
-- Collects suspicious process trees, listening ports, and log evidence.

LET procs = SELECT Pid, Ppid, Name, CommandLine, Exe, Username, CreateTime
FROM pslist()
WHERE Name =~ '(?i)postgres|letta|uvicorn';

LET suspicious_children = SELECT Pid, Ppid, Name, CommandLine, Username, CreateTime
FROM pslist()
WHERE Name =~ '(?i)^(sh|bash|dash|python3?|perl|curl|wget|nc|ncat|socat)$'
  AND Ppid IN (SELECT Pid FROM pslist() WHERE Name =~ '(?i)postgres|letta|uvicorn');

LET listeners = SELECT Pid, Name, Family, Address, Port, Status
FROM netstat()
WHERE Port IN (5432, 8283) AND Status = 'LISTEN';

LET log_hits = SELECT FullPath, Line
FROM foreach(
  row={
    SELECT FullPath FROM glob(globs='/var/log/postgresql/*.log')
  },
  query={
    SELECT FullPath, parse_file_with_regex(
      filename=FullPath,
      regex='(?im)^.*COPY.*PROGRAM.*$') AS Line
    FROM scope()
  });

SELECT * FROM procs
UNION ALL
SELECT * FROM suspicious_children;

SELECT * FROM listeners;
SELECT * FROM log_hits;

Remediation and Verification Script

Run this Bash script on hosts suspected of running Letta. It identifies the vulnerable configuration, applies hardening, and collects quick triage evidence. Test in a staging deployment before running against production agent servers.

Bash / Shell
#!/usr/bin/env bash
# CVE-2026-77936 - Letta deployment verification & hardening
set -euo pipefail

REPORT="/tmp/letta_cve_2026_77936_$(date +%Y%m%d_%H%M%S).txt"
echo "=== CVE-2026-77936 Letta Assessment $(date) ===" | tee "$REPORT"

# 1. Locate Letta compose files and check for LETTA_SERVER_SECURE
echo "--- [1] Scanning for Letta compose files ---" | tee -a "$REPORT"
find / -maxdepth 6 \( -name 'compose.yaml' -o -name 'docker-compose.yml' -o -name 'docker-compose.yaml' \) 2>/dev/null | while read -r f; do
  if grep -qi 'letta' "$f" 2>/dev/null; then
    echo "FOUND: $f" | tee -a "$REPORT"
    if grep -q 'LETTA_SERVER_SECURE' "$f"; then
      echo "  [OK] LETTA_SERVER_SECURE present" | tee -a "$REPORT"
    else
      echo "  [VULNERABLE] LETTA_SERVER_SECURE missing -> API is unauthenticated" | tee -a "$REPORT"
    fi
    grep -i 'PGPASSWORD\|POSTGRES_PASSWORD\|5432' "$f" | tee -a "$REPORT" || true
  fi
done

# 2. Check running containers
echo "--- [2] Running Letta/Postgres containers ---" | tee -a "$REPORT"
if command -v docker >/dev/null 2>&1; then
  docker ps --format '{{.Names}}\t{{.Image}}\t{{.Ports}}' | grep -Ei 'letta|postgres' | tee -a "$REPORT" || echo "  none found" | tee -a "$REPORT"
  # Check env of running letta containers
  docker ps --format '{{.Names}}' | grep -i letta | while read -r c; do
    echo "  Container: $c" | tee -a "$REPORT"
    docker inspect "$c" --format '{{range .Config.Env}}{{println .}}{{end}}' | grep -E 'LETTA_SERVER_SECURE|LETTA_SERVER_PASSWORD|PGPASSWORD' | sed 's/=.*PASSWORD.*/=<redacted-present>/' | tee -a "$REPORT" || true
  done
fi

# 3. Check for network exposure of sensitive ports
echo "--- [3] Listening sockets (5432 / 8283) ---" | tee -a "$REPORT"
ss -tlnp 2>/dev/null | grep -E ':(5432|8283)\b' | tee -a "$REPORT" || echo "  not listening locally" | tee -a "$REPORT"

# 4. Triage: evidence of COPY FROM PROGRAM abuse
echo "--- [4] PostgreSQL log check for COPY ... PROGRAM ---" | tee -a "$REPORT"
grep -rniE 'COPY .* PROGRAM' /var/log/postgresql/ 2>/dev/null | tee -a "$REPORT" || echo "  no evidence in local postgres logs" | tee -a "$REPORT"

# 5. Triage: suspicious children of postgres/letta
echo "--- [5] Suspicious child processes ---" | tee -a "$REPORT"
for pid in $(pgrep -f 'postgres|letta|uvicorn' 2>/dev/null); do
  ps --ppid "$pid" -o pid,ppid,comm,args 2>/dev/null | grep -E 'sh|bash|python|curl|wget|nc' | tee -a "$REPORT" || true
done

# 6. HARDENING: generate a hardened override compose file
echo "--- [6] Writing hardened override to ./compose.hardened.yaml ---" | tee -a "$REPORT"
PG_PASS="$(openssl rand -base64 32 | tr -dc 'A-Za-z0-9' | head -c 32)"
SRV_PASS="$(openssl rand -base64 32 | tr -dc 'A-Za-z0-9' | head -c 32)"
cat > ./compose.hardened.yaml <<EOF
# CVE-2026-77936 hardening override - review service/db names against your stack
services:
  letta:
    environment:
      LETTA_SERVER_SECURE: "true"
      LETTA_SERVER_PASSWORD: "${SRV_PASS}"
      PGPASSWORD: "${PG_PASS}"
  db:
    environment:
      POSTGRES_PASSWORD: "${PG_PASS}"
    ports: []   # remove host port publish; keep DB on internal docker network only
EOF
echo "  Override written. Deploy with: docker compose -f compose.yaml -f compose.hardened.yaml up -d" | tee -a "$REPORT"
echo "  Rotate ALL secrets previously in env (LLM API keys) - assume compromise." | tee -a "$REPORT"

echo "=== Assessment complete: $REPORT ==="

Remediation

Treat any internet-reachable Letta deployment through 0.16.8 as potentially compromised, not merely vulnerable. Prioritize accordingly.

Immediate (within 24 hours):

  1. Take the service off the network path. Block inbound access to the Letta API port (default 8283) and PostgreSQL (5432) at the host firewall/security group, or place the API behind an authenticating reverse proxy. The PostgreSQL port should never be published to the host — remove the ports: mapping for the database service entirely and leave it on the internal Docker network.
  2. Enable authentication. Set LETTA_SERVER_SECURE=true and a strong, randomly generated LETTA_SERVER_PASSWORD in the compose environment. This restores the password middleware across all API routes.
  3. Rotate PostgreSQL credentials. Replace the default superuser password with a unique random value. Audit pg_roles for unexpected accounts: SELECT rolname, rolcreatedb, rolsuper FROM pg_roles;
  4. Upgrade Letta. Pull the latest release beyond 0.16.8 from the official repository (https://github.com/letta-ai/letta) and review the project's security release notes; monitor the NVD entry (https://nvd.nist.gov/vuln/detail/CVE-2026-77936) for the vendor advisory reference as it is populated.

Compromise assessment (this week):

  1. Audit Letta admin users via the API or database for accounts your team did not create — rogue /v1/admin users are a documented persistence mechanism here.
  2. Review PostgreSQL logs for COPY ... PROGRAM statements and unexpected superuser logins (source IPs outside your management range).
  3. Rotate every secret in the container environment — LLM provider API keys, database passwords, and any cloud credentials. Assume anything readable from inside the container was exfiltrated.
  4. Rebuild from scratch where exposure is confirmed. Containers exploited through unauthenticated RCE should be destroyed and redeployed from a hardened compose file, not patched in place.

Structural (this quarter):

  1. Inventory shadow AI infrastructure. This class of flaw thrives in developer-spun pilot deployments. Sweep your cloud estates and container registries for Letta and similar agent frameworks; bring them under your vulnerability management program.
  2. Enforce a compose-file security baseline. No database port publishing, no default credentials, mandatory auth flags — validate in CI before deployment.
  3. Add the detections above to your SIEM content pipeline and schedule recurring hunts for exposed 5432/8283 listeners across the estate.

There is currently no CISA KEV mandate attached to this CVE, but CVSS 9.8 unauthenticated RCE in a framework deployed with default-insecure configuration warrants KEV-level urgency regardless of catalog status.

Related Resources

Security Arsenal Penetration Testing Services AlertMonitor Platform Book a SOC Assessment vulnerability-management Intel Hub

Is your security operations ready?

Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.