Our first screenshot-upload prototype checked the destination, approved the image and sent it. Between the check and the upload, the destination could change. The sender's approval was still valid for the old state, while the store was already operating under a new one.

We reproduced that race in a local test and changed the protocol. The sender now withholds the image until a cooperative store admits the upload on the actual connection. The store holds the admitted state through capture and commit, then preserves the audience on the stored object.

The revised protocol remediated the race we reproduced. Its scope is deliberately narrow: a trusted sender, cooperative local stores and synthetic data. It gives us an engineering result to inspect, while leaving production authorization and native-agent coverage to be qualified.

01 / The screenshot is a separate action

Why the screenshot is a separate action

In its September 29 PixelLeak report, Glow describes coding agents putting development screenshots in public GitHub destinations while trying to provide visual evidence for review. The researchers report more than 13,000 images associated with more than 300 organizations. Those are Glow's findings; we did not independently reproduce the inventory or retrieve exposed screenshots.

A developer can authorize a code change and ask to see the result without authorizing public disclosure of the screenshot. The image may reveal an unreleased interface or private project information even when it contains no recognizable password. Its audience matters independently of its contents.

Tool behavior also changes. GitHub announced native CLI image and video attachments in version 2.99.0 on September 1. Its CLI documentation describes that supported path and requires push access. A present-day comparison should test it rather than assume agents still lack an attachment mechanism.

The serving path needs its own check. GitHub's attachment documentation says private and internal repository uploads are restricted to people with repository access. Its separate anonymized URL documentation warns that recipients may be able to view media served through those URLs. We did not test a live GitHub account. Neither the repository label nor our local result settles access for an arbitrary attachment URL.

02 / What we tested

What we tested

We used one deterministic, authored PNG of 2,614 bytes, a sending gate and two reachable loopback HTTP stores. One store modeled an approved private destination. The other modeled a forbidden public destination, while remaining entirely local. The image, identities, policy and reader personas were synthetic test fixtures.

The comparison kept the payload and reachable destination the same. When a denial was advisory, the test could ignore it and send the PNG. An endpoint/store observer recorded all 2,614 bytes at the forbidden sink. With the sending gate enforcing the denial, it recorded zero protected body bytes and no object.

The approved private path still delivered the exact PNG. The intended test reviewer retrieved it, and three unauthorized test personas received HTTP 403. An explicitly approved public synthetic demonstration could also complete. Those are delivery and mock-access checks; they do not measure an agent fixing a UI or a person finding the image useful.

The observer mattered. A sender log saying “denied” would not establish that the destination received nothing. This matches OWASP's guidance on excessive agency, which calls for downstream authorization and validation of requests.

03 / The race and the repair

The race survived a green test suite

The first prototype passed its existing 30 regression methods. A later probe changed destination state after the metadata response and exposed a check/use gap. We retained that evidence and the failed runs, then added race-focused cases.

The problem was the separation between checking state and using it, a time-of-check/time-of-use race. Moving the check closer to the send would still leave a gap if configuration could change independently. The repair needed the store to participate.

V2 uses a connection-bound admission protocol:

  1. The sender snapshots the PNG and sends only upload headers, binding the owner, container, audience, tenant and revision
  2. The store locks its configuration and validates the requested state before acknowledging admission
  3. The sender checks the admitted-state digest and permit freshness before releasing image bytes
  4. The store holds the lock through body capture and object commit, refusing configuration mutations during that interval
  5. The object keeps its admitted audience after later store-configuration changes
Sequence diagram comparing the v1 metadata race with v2 cooperative admission and commit.
Protocol diagram for the local prototype. HTTP 100-continue carries the acknowledgement; the guarantee depends on the trusted sender and cooperative receiver enforcing the protocol.
Open full-size diagram ↗
Read the diagram as text

Two stacked upload sequence diagrams compare a PixelLeak-inspired local v1 metadata race with the bounded cooperative v2 protocol. In v1, the sender receives approved metadata, configuration changes, and the sender sends the PNG using stale approval. In v2, headers on the actual connection bind owner, container, audience, tenant, and revision. A trusted cooperative store locks configuration and validates, then returns 100 Continue with an admitted-state digest. The sender validates the acknowledgment and permit freshness before sending the PNG body. The store holds the lock through capture and commit, rejects concurrent configuration mutations with 409 Conflict, and retains the admitted audience on the committed object after later changes. HTTP 100 Continue alone provides no authorization guarantee; this is a trusted-receiver, local-mock protocol.

Adding an Expect header to an arbitrary server would not establish these properties. The receiver must implement them. In our mock store, state files are audit mirrors, while authoritative in-memory state controls admission.

04 / Observed outcomes

The observed outcomes

Retained observations from the local prototype
TestRetained observation
Ignore advisory denialThe forbidden local sink received the complete 2,614-byte PNG
Enforce the forbidden actionZero protected body bytes and no object
Mutate destination after its metadata response or during connection setupZero protected body bytes and no objects at either controlled store
Change configuration during an admitted uploadMutation refused; the exact private upload completed
Change store configuration after commitIntended reviewer retrieved exact bytes; three unauthorized personas received 403
Abort after admission because the permit expired or acknowledgement was forgedZero captured prefix and no object, but total observation remained unknown

The final v2 suite passed 36 regression methods. That includes assertions about failures and missing evidence. It is not a claim that 36 disclosures were prevented. In particular, an aborted connection with an incomplete observation remains inconclusive even when the captured prefix is zero.

The cases were familiar development tests, so they cannot supply a statistical prevention rate. Separate automated review checked local probes and retained evidence; no human security audit has been completed.

05 / Production boundaries

What a production version still needs

The result depends on a cooperative receiver and a trusted sender. Direct routes that ignore admission remain unmediated. The observer measures local HTTP body and store outcomes; it does not provide packet-level monitoring or resist a hostile process on the same machine.

Before claiming protection for a coding agent, we would need to select a real runtime, inventory its upload routes and demonstrate enforcement for each claimed browser, command, tool and background path. Real authentication, tenant isolation, production ACL changes, restart recovery and concurrent senders also need qualification. None of those properties follows from a successful loopback test.

An onboarding check could help identify unsafe fallback instructions and configured upload routes before use. Its output should name the inspected scope and unresolved paths, with any corrective change reviewed and tested. We have not built or measured that workflow here.

We also used a deterministic local policy rather than the Decisions API. OpenAI's September 29 announcement describes finite-answer decisions from text or images in limited preview. A future adapter would need a verified contract and the same enforced boundary. A classification answer alone cannot hold destination state stable during an upload.

The next engineering step is to qualify one real upload path, including how it admits the upload and serves the resulting object. For this prototype, the defensible result is that the cooperative local protocol remediated the reproduced race and preserved the tested private delivery. Code and receipts are retained privately; a public reproduction package has not been released.