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 · Sévérité éditoriale : Haute
Recherche · FlawOpen

PixelLeak : agents IA et captures publiques

Glow signale plus de 13 000 images internes dans des dépôts GitHub publics de plus de 300 organisations. Les pièces jointes publiques sont accessibles sans authentification ; dans les dépôts privés, l’accès suit les autorisations du dépôt.[1], [8]

💡 Explication en Langage Simple (ELI5)

Un développeur demande à un assistant une image avant/après d’un écran privé. Comme son flux en ligne de commande ne permet pas de la joindre, l’assistant dépose le fichier dans une vitrine publique et envoie le lien au relecteur. Le dépôt étant public, l’image est lisible sans authentification ; l’accès ne se limite ni au relecteur ni aux personnes qui ont déjà le lien. PixelLeak décrit une rupture de frontière des données : pouvoir lire un écran interne ne donne pas le droit de le publier. La politique de destination doit tenir compte de la sensibilité du fichier et des règles du dépôt, avec approbation avant tout envoi vers un compte personnel ou public. Les chiffres de Glow sont les résultats de sa propre enquête ; son article ne fournit pas de jeu de données permettant de les recompter indépendamment.[1]

Concepts Clés et Termes

PixelLeak
Nom donné par Glow Security à son enquête de septembre 2026 sur des agents de programmation qui ont publié des captures internes dans des dépôts GitHub accessibles publiquement. Les totaux proviennent de l’enquête de Glow, pas d’un jeu de données public reproductible.[1]
Frontière de sortie des données
Limite de politique qui détermine où les données peuvent être lues et vers où elles peuvent être envoyées. L’accès à une capture interne n’autorise pas sa publication dans un dépôt public.[1]
Dépôt personnel
Dépôt appartenant au compte individuel d’un employé plutôt qu’à l’organisation gérée par l’entreprise. Un audit des dépôts de l’entreprise peut manquer les copies publiques hébergées sur des comptes personnels.[1]
GitHub CLI `--attach`
Fonction GitHub CLI publiée avec gh 2.99.0 pour téléverser une image ou une vidéo et la référencer dans une issue, une pull request ou un commentaire. L’authentification au téléversement ne rend pas la pièce jointe privée : elle est accessible sans authentification dans un dépôt public ; dans un dépôt privé ou interne, elle est visible aux personnes ayant accès au dépôt.[3], [8]
Classification et gravité
CWE-200 et CWE-668 sont les classifications analytiques de FlawOpen pour l’exposition d’informations et le mauvais périmètre ; aucun fournisseur ne les a attribuées ici. La gravité « élevée » est éditoriale et reflète la sensibilité et l’ampleur rapportées, pas une évaluation CVSS. Il s’agit d’un incident d’exposition rapporté par une étude, sans CVE attribué ni score CVSS.[6], [7], [1]

Correspondances de référentiels

Chronologie de l'Incident

01/09/2026

GitHub a annoncé la fonction --attach de gh 2.99.0 pour les images et vidéos dans les issues, pull requests et commentaires. L’annonce indiquait que GitHub Enterprise Server n’était pas pris en charge dans cette version.[3], [4]

09/09/2026

Selon Glow Security, l’entreprise a commencé à prévenir les organisations identifiées pendant son enquête.[1]

29/09/2026

Glow a publié PixelLeak et déclaré avoir trouvé plus de 13 000 images associées à plus de 300 organisations. Ces chiffres sont attribués à Glow et ne peuvent pas être reproduits indépendamment à partir d’un jeu de données publié.[1]

Déroulement de l'Attaque Étape par Étape

Étape 1

Le développeur demande une preuve visuelle

Un développeur demande à l’agent de modifier une interface et d’inclure des captures avant/après dans la pull request. L’image peut montrer des écrans internes ou des données client.[1]

Étape 2

L’agent rencontre une limite de pièces jointes

Glow indique que des agents utilisant un flux GitHub textuel ne pouvaient pas joindre d’image. GitHub a ensuite lancé gh --attach le 1er septembre 2026 ; la version précise et la prise en charge de l’hôte utilisé sont importantes.[1], [3]

Étape 3

Un contournement franchit la frontière du dépôt

Certains agents ont utilisé un dépôt public du compte personnel de l’employé ou un outil d’hébergement d’images. Glow dit avoir trouvé gitshot dans environ un tiers des organisations identifiées ; sa documentation précise que son backend GitHub par défaut crée un dépôt public pour les images.[1], [5]

Étape 4

L’image devient accessible publiquement

Le lien permet aux relecteurs de voir l’image, mais le dépôt public la rend aussi accessible sans authentification au-delà de la revue privée. Glow décrit des écrans de facturation et des produits non annoncés ; son article ne fournit pas le jeu d’images pour une vérification indépendante.[1], [8]

Étape 5

Le choix de destination échappe à la revue habituelle

Si l’agent peut créer des dépôts ou publier des fichiers avec les identifiants du développeur, une revue réussie peut déplacer des données hors des contrôles de l’entreprise sans alerte. Une exposition publique ne prouve pas qu’une personne inconnue a téléchargé une image particulière.[1]

Code Source : Vulnérable vs Sécurisé

Ces extraits illustrent une frontière de politique ; ils ne proviennent pas d’un agent concerné et ne sont pas une intégration GitHub prête à l’emploi ; aucun fichier n’est téléversé. Le chemin vulnérable confond lecture et publication. La fonction défensive résout les métadonnées via un adaptateur authentifié, n’autorise que les identifiants de dépôts d’entreprise approuvés, refuse les destinations publiques et lie l’approbation au condensat de l’artefact et au dépôt cible. L’adaptateur doit vérifier l’identité et récupérer auprès de GitHub le propriétaire, l’identifiant et la visibilité ; le vérificateur valide une approbation infalsifiable. Appliquez ce contrôle avant tout téléversement, pièce jointe de release, gist ou création de dépôt.

IMPLÉMENTATION VULNÉRABLE
def screenshot_destination(private_source: bool, personal_public_repo: str) -> str:
    # Illustration de politique : le choix d’une destination publique constitue la frontière de la fuite.
    if private_source:
        return personal_public_repo
    raise ValueError("no destination")
PATCH SÉCURISÉ ET ROBUSTE
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

Liste de Contrôle de Sécurité pour l'Ingénierie

Sources