
PoC — abuso de proxy de origen cruzado de claves API de proveedores configuradas en PasteGuard (GHSA-q94x-p9rc-q89f, CVE-2026-86998, CVSS 7.6).
Estado del CVE: solicitado, pendiente de asignación. Este hallazgo se publica como GHSA-q94x-p9rc-q89f. Tras la asignación del CVE, este repositorio se renombra
CVE-YYYY-NNNNN-pasteguard-PoCy este banner se reemplaza por el enlace del CVE.
| Investigador | Dostxodjayev Abdullox (@squeeze440) |
| Aviso | GHSA-q94x-p9rc-q89f |
| CVSS 3.1 | 7.6 (Alto) |
| Debilidad | CWE-352, CWE-942 |
Resumen: La falta de protección CORS/CSRF en las rutas del proxy LLM en PasteGuard 0.9.1 permite a un atacante remoto (cualquier sitio web que visite el navegador del operador, o cualquier host en la red local) desencadenar solicitudes autenticadas a la API de OpenAI/Anthropic configurada por el operador usando la clave de API de respaldo del lado del servidor de PasteGuard, y leer la respuesta, mediante un fetch() de origen cruzado a /openai/v1/chat/completions (y su hermana /anthropic/v1/messages).
Producto: PasteGuard (github.com/sgasser/pasteguard)
Versión probada: commit 100718191499c52934f3b2ccca72ca16373fb765 (2026-07-30), versión de package.json 0.9.1
CVSS v3.1 estimado: CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:H — 7.6 (Alto)
UI:R — el operador debe tener su navegador abriendo una página (cualquier página, en cualquier sitio) mientras PasteGuard está en ejecución; no se necesita ninguna otra interacción.C:L / I:L — el JS del propio atacante solo lee la respuesta a su propio prompt elegido por el atacante (no las conversaciones previas ni los datos del panel del operador, que están correctamente excluidos de CORS), pero esto aún confirma/usa una cuenta activa con fondos y contamina el registro de uso/auditoría de la cuenta bajo la identidad del operador.A:H — el impacto realista e ilimitado: una página del atacante puede repetir esta solicitud indefinidamente, generando uso facturado real contra la cuenta/cuota del proveedor del operador (drenaje financiero, posible agotamiento del límite de velocidad o marcas de abuso del lado del proveedor) sin limitación de velocidad ni verificación de origen que lo impida.S:U — el impacto permanece dentro del mismo límite de confianza (la credencial configurada del propio proxy es la que se usa); no se cruza ninguna autoridad de seguridad separada.Detalles:
La capa HTTP de PasteGuard aplica una política CORS permisiva con comodín a todas las rutas excepto el panel:
src/index.ts:40-45 — const corsMiddleware = cors(); (el cors() de Hono sin opciones usa por defecto Access-Control-Allow-Origin: *) se aplica mediante app.use("*", ...) a todo, con una excepción explícita solo para /dashboard y /dashboard/*. El comentario justo encima (src/index.ts:34-39) muestra que el autor razonó cuidadosamente sobre la exposición CORS del panel pero no extendió el mismo razonamiento a las rutas del proxy que están debajo.src/config.ts:153 — host: z.string().default("0.0.0.0"), confirmado en vivo en config.example.yaml:13. El proxy escucha en todas las interfaces por defecto, no solo en loopback, por lo que también es accesible desde la LAN, no solo desde el navegador del propio operador.src/providers/openai/client.ts:48-53:
// Use client's auth header if provided, otherwise fall back to config
if (authHeader) {
headers.Authorization = authHeader;
} else if (config.api_key) {
headers.Authorization = `Bearer ${config.api_key}`;
}
Authorization, PasteGuard adjunta silenciosamente su propia providers.openai.api_key retenida en el servidor a la solicitud saliente. config.example.yaml:22-24 documenta esto como una característica de conveniencia intencional ("Respaldo opcional si el cliente no envía encabezado de autenticación") para el caso de uso "Apps & APIs".src/providers/anthropic/client.ts:37-61 implementa el patrón de respaldo idéntico para /anthropic/v1/messages (respaldo de x-api-key/Authorization a config.api_key) — confirmado como una instancia hermana de la misma causa raíz, no reverificado de forma independiente de extremo a extremo (ver el alcance del PoC a continuación).Debido a que la política CORS con comodín se sitúa delante de rutas que contienen esta credencial de respaldo, cualquier origen puede (1) hacer que el navegador envíe la solicitud sin encabezado Authorization, provocando que el servidor adjunte su propia clave de API real, y (2) leer la respuesta JSON, ya que Access-Control-Allow-Origin: * está presente. Ninguna verificación de Origin/Referer ni token CSRF protege estas rutas.
Prueba de concepto (confirmada dinámicamente, no solo trazada estáticamente):
config.yaml con providers.openai.base_url: http://127.0.0.1:9091 (upstream simulado) y providers.openai.api_key: "sk-VICTIM-SECRET-DO-NOT-LEAK-12345", pii_detection.enabled: false (detector simulado solo /health, para evitar necesitar el servicio completo del modelo GLiNER), secrets_detection.enabled: false. Se inició PasteGuard con bun run src/index.ts, se confirmó /health → 200.mock_upstream.py) en 127.0.0.1:9091 que registra cualquier encabezado Authorization que reciba.http://127.0.0.2:8001/attack.html (IP de loopback literal diferente, según las reglas de aislamiento de sitios de Chrome — no una prueba de mismo origen en localhost), mediante python3 -m http.server 8001 --bind 127.0.0.2. La única acción de la página al cargar:
fetch("http://127.0.0.1:3000/openai/v1/chat/completions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ model: "gpt-4o-mini", messages: [{ role: "user", content: "cross-origin drive-by call, attacker supplied NO api key" }] })
}).then(r => r.json()).then(data => { /* render on page */ });
http://127.0.0.2:8001/attack.html. Se confirmó mediante inspección de red de DevTools:
origin: http://127.0.0.2:8001, sec-fetch-site: cross-site, sin encabezado Authorization enviado por la página del atacante.access-control-allow-origin: *, HTTP 200, cuerpo JSON totalmente legible por el JS de la página del atacante.../evidence/cross_origin_csrf_success.png.RECEIVED AUTH HEADER: Bearer sk-VICTIM-SECRET-DO-NOT-LEAK-12345 — lo que demuestra que PasteGuard adjuntó la clave real configurada del operador del lado del servidor, sin que la página del atacante la conociera ni la proporcionara.Evidencia sin procesar: ~/engagements/pasteguard/evidence/cross_origin_csrf_success.png, ~/engagements/pasteguard/evidence/mock_upstream_log.txt.
Se confirmó estáticamente (archivo:línea arriba) que la ruta /anthropic/v1/messages comparte el patrón idéntico de respaldo + CORS con comodín, pero no se volvió a ejecutar por separado a través del PoC del navegador en vivo, en aras de la guía de profundidad sobre amplitud/regla de 5 minutos de la sesión, una vez que la causa raíz ya estaba confirmada en /openai.
Impacto: Cualquier sitio web que visite el navegador del operador de PasteGuard (anuncio malicioso, sitio comprometido, o un atacante en la misma LAN dado el bind predeterminado 0.0.0.0) puede impulsar silenciosamente solicitudes facturadas ilimitadas a través de la propia cuenta de OpenAI/Anthropic del operador mediante su instancia local de PasteGuard, sin interacción del usuario más allá de tener la pestaña abierta y sin forma de que el operador lo note salvo vigilando el panel de facturación de su proveedor. Este es un vector directo de abuso financiero / agotamiento de cuota, y secundariamente contamina el registro de uso/auditoría de la cuenta del proveedor del operador con contenido elegido por el atacante.
Debilidades:
Remediación (sugerencias):
cors() con comodín a /openai, /anthropic, /codex (y /api/mask si alguna vez se extiende para usar credenciales retenidas en el servidor) cuando se configura una api_key de respaldo. Como mínimo, hacer que los orígenes permitidos para estas rutas sean explícitos/configurables (p. ej., solo el origen conocido de la extensión del navegador), con valor predeterminado de ningún acceso de origen cruzado en lugar de *./dashboard en src/index.ts.server.host de 0.0.0.0 a 127.0.0.1 en src/config.ts:153 y config.example.yaml:13, requiriendo una adhesión explícita para enlazar en todas las interfaces.Crédito: Dostxodjayev Abdullox
Canal de reporte: No existe ningún SECURITY.md en el repositorio (confirmado mediante gh api repos/sgasser/pasteguard/contents/SECURITY.md → 404, reverificado en el momento de la auditoría). Tampoco hay avisos de seguridad publicados previamente (gh api repos/sgasser/pasteguard/security-advisories → []), por lo que esto no parece ser un duplicado de un problema divulgado anteriormente. Canal recomendado: el flujo predeterminado de reporte privado de vulnerabilidades de GitHub en https://github.com/sgasser/pasteguard/security/advisories/new.