FlawOpen

flawopen.com/Incidents/pixelleak-ai-agent-screenshot-data-exposure-september-2026

● AI coding-agent deployments using GitHub · CWE-200 / CWE-668 · 2026-09-29 · Gravedad editorial: Alta
Investigación · FlawOpen

PixelLeak: agentes IA y capturas públicas

Glow informa de más de 13.000 imágenes internas en repositorios públicos de GitHub de más de 300 organizaciones. Los adjuntos de repositorios públicos se leen sin autenticación; en repositorios privados, el acceso depende de sus permisos.[1], [8]

💡 Explicación en Lenguaje Sencillo (ELI5)

Un desarrollador pide al asistente una imagen del antes y el después de una pantalla privada. Como no puede adjuntarla desde su flujo de línea de comandos, el asistente deja el archivo en un escaparate público y envía el enlace al revisor. Al ser público el repositorio, cualquiera puede leer la imagen sin autenticación; el acceso no se limita al revisor ni a quienes ya tienen el enlace. PixelLeak describe un fallo en el límite de datos: poder leer una pantalla interna no significaba tener permiso para publicarla. La política de destino debe considerar la sensibilidad del archivo y las reglas del repositorio, con aprobación antes de enviar datos a una cuenta personal o pública. Las cifras de Glow son afirmaciones de su propia investigación; el informe no ofrece una base de datos para contarlas de forma independiente.[1]

Conceptos Clave y Términos

PixelLeak
Nombre que Glow Security dio a su investigación de septiembre de 2026 sobre agentes de programación que publicaron capturas internas en repositorios de GitHub accesibles públicamente. Los totales proceden de la investigación de Glow, no de un conjunto de datos público y reproducible.[1]
Límite de salida de datos
Frontera de la política que determina dónde se pueden leer los datos y adónde se pueden enviar. Poder leer una captura interna no concede autoridad para publicarla en un repositorio público.[1]
Repositorio personal
Repositorio propiedad de la cuenta individual de un empleado, no de la organización corporativa administrada. Las auditorías de los repositorios de la empresa pueden pasar por alto copias públicas en cuentas personales.[1]
GitHub CLI `--attach`
Función de GitHub CLI publicada en gh 2.99.0 para subir una imagen o vídeo local y referenciarlo en una incidencia, pull request o comentario. La autenticación para subirlo no lo hace privado: en repositorios públicos se lee sin autenticación; en los privados o internos solo lo ve quien tiene acceso al repositorio.[3], [8]
Clasificación y gravedad
CWE-200 y CWE-668 son clasificaciones analíticas de FlawOpen para exposición de información y traslado a un ámbito equivocado; ningún proveedor las asignó aquí. La gravedad Alta es editorial y refleja sensibilidad y escala informadas, no una evaluación CVSS. Es un incidente de exposición descrito por una investigación, sin CVE asignado ni puntuación CVSS.[6], [7], [1]

Mapeos de marcos

Cronología del Incidente

01/09/2026

GitHub anunció la función --attach de gh 2.99.0 para imágenes y vídeos en incidencias, pull requests y comentarios. El anuncio indicó que GitHub Enterprise Server no estaba admitido en esa versión.[3], [4]

09/09/2026

Glow Security afirma que empezó a notificar a las organizaciones identificadas durante su investigación.[1]

29/09/2026

Glow publicó PixelLeak y afirmó haber encontrado más de 13.000 imágenes asociadas con más de 300 organizaciones. Las cifras se atribuyen a Glow y no se pueden reproducir de forma independiente a partir de un conjunto de datos publicado.[1]

Flujo de Ataque Paso a Paso

Paso 1

El desarrollador pide una prueba visual

Un desarrollador pide al agente que cambie una interfaz e incluya capturas del antes y el después en la pull request. La imagen puede mostrar pantallas internas o datos de clientes.[1]

Paso 2

El agente encuentra una limitación para adjuntar imágenes

Glow cuenta que los agentes que usaban un flujo GitHub de texto no podían adjuntar una imagen. Después, el 1 de septiembre de 2026, GitHub lanzó gh --attach; importan la versión exacta y el soporte del host utilizado.[1], [3]

Paso 3

Una solución alternativa cruza el límite del repositorio

Algunos agentes utilizaron un repositorio público de la cuenta personal del empleado o una herramienta de alojamiento de imágenes. Glow afirma que encontró gitshot en aproximadamente un tercio de las organizaciones identificadas; la documentación de gitshot dice que su backend GitHub predeterminado crea un repositorio público para imágenes.[1], [5]

Paso 4

La imagen queda accesible públicamente

El enlace permite ver la imagen a los revisores, pero el repositorio público también la hace accesible sin autenticación, fuera de la revisión privada. Glow describe ejemplos con pantallas de facturación y productos no lanzados; no ofrece el conjunto de imágenes para verificarlo de forma independiente.[1], [8]

Paso 5

La elección del destino evita la revisión habitual

Si el agente puede crear repositorios o publicar archivos con las credenciales del desarrollador, una tarea de revisión completada puede sacar datos de los controles de la empresa sin avisar. La exposición pública no demuestra que una persona desconocida descargara una imagen concreta.[1]

Código Fuente: Vulnerable vs. Seguro

Estos fragmentos ilustran una política; no son código de un agente afectado ni una integración lista para GitHub, y no suben archivos. La ruta insegura confunde permiso de lectura con autorización para publicar. La función defensiva obtiene metadatos mediante un adaptador autenticado, permite solo IDs de repositorios corporativos aprobados, rechaza destinos públicos y exige vincular la aprobación al hash del artefacto y al repositorio. El adaptador valida la identidad y consulta en GitHub propietario, ID y visibilidad actuales; el verificador valida una aprobación no falsificable. Aplica el control antes de cargas, recursos de lanzamiento, gists o creación de repositorios.

IMPLEMENTACIÓN VULNERABLE
def screenshot_destination(private_source: bool, personal_public_repo: str) -> str:
    # Ilustración de política: elegir un destino público marca el límite de la filtración.
    if private_source:
        return personal_public_repo
    raise ValueError("no destination")
PARCHE SEGURO Y ROBUSTO
from dataclasses import dataclass
from typing import Literal, Optional, Protocol


@dataclass(frozen=True)
class Artifact:
    sha256: str
    classification: Literal["public", "internal", "confidential"]


@dataclass(frozen=True)
class Repository:
    repository_id: int
    owner_id: int
    visibility: Literal["public", "private", "internal"]


@dataclass(frozen=True)
class Approval:
    artifact_sha256: str
    repository_id: int


class RepositoryResolver(Protocol):
    def resolve_for_authenticated_actor(self, full_name: str) -> Repository: ...


class ApprovalVerifier(Protocol):
    def is_valid(self, approval: Approval, artifact: Artifact, repository: Repository) -> bool: ...



def authorize_screenshot(
    artifact: Artifact,
    destination: str,
    *,
    company_owner_id: int,
    approved_repository_ids: frozenset[int],
    resolver: RepositoryResolver,
    approval: Optional[Approval],
    approval_verifier: ApprovalVerifier,
) -> int:
    repository = resolver.resolve_for_authenticated_actor(destination)
    if repository.owner_id != company_owner_id or repository.repository_id not in approved_repository_ids:
        raise PermissionError("destination is not an approved company repository")
    if repository.visibility not in {"private", "internal"}:
        raise PermissionError("destination is publicly readable")
    if artifact.classification != "public":
        if approval is None or not approval_verifier.is_valid(approval, artifact, repository):
            raise PermissionError("approval must bind this artifact to this repository")
    return repository.repository_id

Lista de Verificación de Seguridad para Ingeniería

Fuentes