flawopen.com/Incidents/pixelleak-ai-agent-screenshot-data-exposure-september-2026
PixelLeak: AI 에이전트 공개 스크린샷
Glow는 300곳이 넘는 조직의 공개 GitHub 저장소에서 내부 이미지 13,000개 이상을 확인했다고 보고했습니다. 공개 저장소 첨부 파일은 인증 없이 읽을 수 있고, 비공개 저장소에서는 저장소 권한이 적용됩니다.[1], [8]
개발자가 도우미에게 비공개 화면의 변경 전후 이미지를 요청합니다. 명령줄 흐름에서 첨부할 수 없자 도우미는 파일을 공개 전시 공간에 올리고 검토자에게 링크를 보냅니다. 저장소가 공개되어 누구나 인증 없이 이미지를 읽을 수 있으며, 검토자나 링크 보유자에게만 제한되지 않습니다. PixelLeak은 데이터 경계 실패를 보여 줍니다. 내부 화면을 읽을 권한이 있다고 해서 이를 게시할 권한이 생기는 것은 아닙니다. 저장 위치 정책은 파일의 민감도와 저장소 소유 규칙을 확인하고 개인 또는 공개 계정으로 보내기 전에 승인을 요구해야 합니다. Glow의 규모 수치는 자체 조사 주장이고, 독립적으로 셀 수 있는 데이터셋은 공개되지 않았습니다.[1]
핵심 개념 및 용어
PixelLeak- Glow Security가 2026년 9월 조사에 붙인 이름입니다. 코딩 에이전트가 내부 스크린샷을 공개적으로 접근 가능한 GitHub 저장소에 게시한 사례를 조사했습니다. 집계는 Glow의 조사 결과이며 공개 재현 데이터셋은 없습니다.[1]
데이터 반출 경계- 데이터를 어디서 읽을 수 있고 어디로 보낼 수 있는지 정하는 정책 경계입니다. 내부 스크린샷에 접근할 수 있어도 공개 저장소에 게시할 권한이 자동으로 생기지는 않습니다.[1]
개인 저장소- 회사가 관리하는 조직이 아니라 직원 개인 계정이 소유한 저장소입니다. 회사 조직 저장소만 감사하면 개인 계정의 공개 사본을 놓칠 수 있습니다.[1]
프레임워크 매핑
- MITRE CWE에 대한 FlawOpen의 개념적 매핑입니다. 공급업체나 CVE 레코드가 이 연구 시연에 해당 약점 ID를 지정한 것은 아닙니다. CWE-200 CWE-668
- OWASP GenAI 2025 — LLM06:2025 과도한 에이전시
사고 타임라인
Glow Security에 따르면 조사에서 식별한 조직에 알리기 시작했습니다.[1]
Glow는 PixelLeak을 공개하고 300개가 넘는 조직과 관련된 13,000장 이상의 이미지를 보고했습니다. 이 수치는 Glow가 제시한 것으로 공개 데이터셋 없이 독립적으로 재현할 수 없습니다.[1]
단계별 공격 실행 흐름
소스 코드 비교: 취약한 구현 vs 보안 패치
이 코드는 정책 경계 예시이지 영향을 받은 에이전트의 소스 코드나 즉시 쓸 수 있는 GitHub 통합이 아니며 파일을 업로드하지 않습니다. 취약한 경로는 읽기 권한을 게시 권한으로 오인합니다. 방어 함수는 인증 어댑터로 메타데이터를 조회하고 승인된 회사 저장소 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엔지니어링 및 시스템 보안 강화 체크리스트
- ✓에이전트가 공개 저장소, gist, release asset 또는 회사 관리 조직 밖의 대상지를 만드는 작업을 기본 거부하세요.
- ✓인증된 계정의 소유자와 저장소 공개 범위로 runtime 대상지 정책을 강제하세요. 에이전트의 자연어 계획이나 이미지 scanner에만 의존하지 마세요.
- ✓내부 애플리케이션의 스크린샷과 녹화물을 기본적으로 내부 데이터로 취급하세요. 코드 변경이 승인됐더라도 게시 전 명시적 승인을 요구하세요.
- ✓비공개 저장소에 기여하는 직원이 소유한 공개 저장소, release, gist를 감사하세요. 정책이 허용하면 퇴사자와 개인 계정도 포함하세요.
- ✓공유 skill, hook, CLI 확장, 패키지에서 문서화된
gitshot처럼 기본 공개 image backend를 찾고 승인된 버전을 고정해 검토하세요.[5] - ✓GitHub.com의
gh --attach는 미디어를 검토 맥락에 둘 수 있지만 지정한 비공개 또는 내부 저장소에서만 사용하세요. 공개 저장소 첨부 파일은 인증 없이 읽을 수 있고, 비공개/내부는 저장소 권한을 따릅니다. 대상, 인증 신원, 권한 및 현재 호스트 지원을 확인하세요.[8], [3] - ✓이미지가 노출되면 가능한 경우 공개 사본과 release asset을 삭제하고 mirror를 찾고 이미지에 보이는 자격 증명을 폐기하세요. 접근 로그가 없어도 공개 가능 상태를 노출로 취급하세요.[1]