CVE-2026-103956 - Loom for AWS - Critical - Auth bypass - unauthenticated super-admin when no IdP is configured
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · cve-2026-103956-loom-unauth
Loom for AWS < 1.6.1 - AWS Labs
When Cognito is unset and no external IdP is active, get_current_user hands every request t-admin / g-admins-super. That includes requests with no Authorization header. A fresh deploy before IdP setup is an open admin panel.
| ID | CVE-2026-103956 |
| CWE | CWE-306 / CWE-1188 |
| CVSS | Critical: 10.0 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H |
| Product | Loom for AWS |
| Affected | < 1.6.1. Lab pin v1.6.0 (8c658d61). Fixed in v1.6.1 (ccad5665). |
| Auth | unauthenticated |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 only |
The attacker owns the agent control plane with zero credentials.
GET /api/auth/me with no header returns username=local-dev, sub=local, groups t-admin and g-admins-super. Every scope in GROUP_SCOPES comes with that identity.POST /api/mcp/servers is 201. MCP, A2A, agents, memories, security, settings, credentials, and admin audit sit behind the same dependency.GET /api/mcp/servers/{id}/export is admin:write. On this pin it returns oauth2_client_secret from SQLite. The CVE text also names IAM role-policy rewrite on managed agent roles; that path wants AWS and is outside this loopback lab.v1.6.1 requires LOOM_ALLOW_UNAUTHENTICATED_LOCAL_DEV plus a loopback client. Same request is 401 (No identity provider configured).
AWS posted CVE-2026-103956 with GHSA-vgmj-998f-r8mp and bulletin 2026-124-AWS. I pinned awslabs/loom v1.6.0 (8c658d61) next to v1.6.1 (ccad5665) and ran the backend on SQLite with LOOM_COGNITO_USER_POOL_ID unset.
backend/app/dependencies/auth.py get_current_user checks Cognito env and an active IdP row. Both empty: it returns UserInfo(sub="local", username="local-dev", groups=["t-admin", "g-admins-super"], scopes=ALL_SCOPES). The token is never read.
I stood up stock FastAPI on loopback 127.0.0.1:18180 (v1.6.0) and 18181 (v1.6.1). No frontend. No AWS. Unauthenticated GET /api/auth/me on 1.6.0 was 200 with those groups. Unauthenticated POST /api/mcp/servers with oauth2_client_secret=CVE-2026-103956-WITNESS was 201. GET /api/mcp/servers/1/export handed the secret back. On 1.6.1 the same /api/auth/me was 401.
SUCCESS CVE-2026-103956 me-http=200 me-user=local-dev me-sub=local me-groups=t-admin,g-admins-super mcp-create=201 export-has-secret=yes list-has-witness=yes patched-me-http=401 CVE-2026-103956-WITNESS
Wrong turns already recorded: public.ecr.aws/docker/library/python:3.13-slim was skipped for Docker Hub python:3.13-slim; first /health on v1.6.0 was curl 52 while uvicorn bound, then 200; MCP create schema matched on the first POST, no 422 retry. A reverse shell. Theatre. The oracle is /api/auth/me plus the exported secret.
cd lab
./run.sh
Target only http://127.0.0.1:18180 (v1.6.0) and http://127.0.0.1:18181 (v1.6.1). run.sh shallow-clones those tags, builds both backends, then tears the stack down. SQLite. No Cognito env. No LOOM_ALLOW_UNAUTHENTICATED_LOCAL_DEV on the patched service.
Upgrade to 1.6.1 or later. AWS recommends 1.7.0 for the sibling SSRF/token issues. The 1.6.1 bypass is opt-in and loopback-only. Until then, finish Cognito or an external IdP before the backend is reachable past loopback, and keep LOOM_ALLOW_UNAUTHENTICATED_LOCAL_DEV unset on anything deployed.
backend/app/dependencies/auth.py (get_current_user)