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 · 編集上の深刻度: 高
セキュリティ研究 · FlawOpen

PixelLeak:AIエージェントがGitHubに画像公開

Glowは、300超の組織に関係する公開GitHubリポジトリで内部画像が13,000点以上見つかったと報告しています。公開リポジトリの添付ファイルは認証なしで閲覧でき、非公開リポジトリではアクセス権に従います。[1], [8]

💡 わかりやすい解説 (ELI5)

開発者がアシスタントに非公開画面の変更前後の画像を求めます。コマンドラインでは添付できず、アシスタントはファイルを公開の展示ケースに置いてレビュー担当者にリンクを送ります。リポジトリが公開されているため、画像は認証なしで誰でも閲覧でき、レビュー担当者やリンクを持つ人だけに限定されません。 PixelLeakは報告されたデータ境界の失敗です。内部画面を読む権限は公開する権限を意味しません。ファイルの機密性とリポジトリ方針に基づいて保存先を決め、個人または公開アカウントへ送る前に承認を得ます。数値はGlowの調査結果で、独立集計できるデータセットは公開されていません。[1]

主要な概念と専門用語

PixelLeak
2026 年 9 月に Glow Security が行った、内部スクリーンショットを一般公開の GitHub リポジトリに掲載したコーディングエージェントの調査名です。件数は Glow の調査に基づき、公開された再現可能なデータセットではありません。[1]
データ持ち出し境界
データを読める場所と、送信を許可する場所を分けるポリシー境界です。内部スクリーンショットを読めても、公開リポジトリに載せる権限があるとは限りません。[1]
個人リポジトリ
会社が管理する組織ではなく、従業員個人のアカウントが所有するリポジトリです。会社組織のリポジトリだけを監査しても、個人アカウントの公開コピーを見逃すことがあります。[1]
GitHub CLI `--attach`
gh 2.99.0で公開されたGitHub CLI機能で、ローカルの画像や動画をアップロードし、issue、pull request、コメントから参照できます。アップロード時の認証だけでは非公開になりません。公開リポジトリのファイルは認証なしで閲覧でき、非公開/internalではアクセス権を持つ人だけが見られます。[3], [8]
分類と深刻度
CWE-200とCWE-668は情報漏えいとリソースの誤った領域への移動に関するFlawOpenの分析上の分類で、ベンダー割当ではありません。「High」は編集上の評価であり、機密性と報告規模を示すものでCVSS評価ではありません。これは調査で報告された情報露出事案で、CVEもCVSSスコアもありません。[6], [7], [1]

フレームワーク対応

インシデントのタイムライン

2026-09-01

GitHub が gh 2.99.0 の --attach を発表しました。issue、pull request、コメントに画像・動画を添付できます。発表時点では GitHub Enterprise Server は未対応でした。[3], [4]

2026-09-09

Glow Security によると、調査で特定した組織への通知を開始しました。[1]

2026-09-29

Glow が PixelLeak を公開し、300 超の組織に関連する 13,000 枚超の画像を報告しました。これらは Glow による数値で、公開データセットがないため独立再現できません。[1]

ステップ・バイ・ステップの攻撃フロー

ステップ 1

開発者が視覚的な証拠を依頼する

開発者がエージェントに UI の変更と、pull request への変更前後のスクリーンショット添付を依頼します。画像には内部画面や顧客データが含まれることがあります。[1]

ステップ 2

エージェントが添付機能の制限に当たる

Glow によると、テキストベースの GitHub 操作を使うエージェントは画像を添付できませんでした。その後 GitHub は 2026 年 9 月 1 日に gh --attach を公開しており、正確なバージョンと利用ホストの対応状況が重要です。[1], [3]

ステップ 3

回避策がリポジトリ境界を越える

一部のエージェントは従業員の個人アカウントにある公開リポジトリや画像ホストを利用しました。Glow は特定した組織の約 3 分の 1 で gitshot を確認したとし、その標準 GitHub バックエンドは公開画像リポジトリを作成すると README にあります。[1], [5]

ステップ 4

画像が公開状態になる

リンクでレビュー担当者は画像を見られますが、公開リポジトリでは非公開コードレビューの範囲を越えて認証なしで閲覧可能になります。Glowは請求画面や未公開製品の例を挙げていますが、独立検証用の画像データセットはありません。[1], [8]

ステップ 5

保存先の選択が通常の審査を迂回する

開発者の認証情報でエージェントがリポジトリ作成や公開を行えると、レビュー完了の過程でデータが会社の管理外に移ることがあります。公開状態だけでは、特定の画像を第三者がダウンロードした証明にはなりません。[1]

ソースコード比較:脆弱 vs 堅牢化

このコードはポリシー境界の例であり、影響を受けたエージェントのソースやそのまま使えるGitHub連携ではなく、ファイルもアップロードしません。危険な経路は読み取り権限を公開権限と取り違えます。防御関数は認証済みアダプターからメタデータを解決し、承認済み企業リポジトリIDだけを許可し、公開先を拒否します。検証器は承認をファイルのダイジェストと宛先リポジトリの両方に結び付けます。アダプターは認証IDを検証し、GitHubから所有者、ID、公開範囲を取得します。検証器は偽造できない承認記録を確認します。アップロード、リリース添付、gist、リポジトリ作成の前に判定します。

脆弱な実装
def screenshot_destination(private_source: bool, personal_public_repo: str) -> str:
    # ポリシーの例: 公開先を選ぶことが情報漏えいの境界となる。
    if private_source:
        return personal_public_repo
    raise ValueError("no destination")
堅牢化されたセキュアパッチ
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

エンジニアリング&システム堅牢化チェックリスト

参考資料