← Related · Approved, Not Shown

Research · tenant isolation · authority confusion

The Sandbox Held. The Tenant Boundary Didn't.

Two containers under two unrelated accounts, no network path between them, no public Internet. They shared one thing: the vetted package mirror that existed to reduce their exposure. A reader credential that could also annotate was the entire channel.

Published 19.10.2026 · ~11 min · surface: shared infrastructure



01 · The service in the middle

Infrastructure installed for safety is still infrastructure.

A ChatGPT conversation that executes code gets its own container, isolated from other conversations. That part worked. But a container running Python still needs pip install, and a careful platform team does not let a sandbox out to the public registries directly. So the sandbox gets a controlled intermediary: an internal Artifactory instance that sits in front of the public indexes and serves vetted packages inward.

This is good design. A bank puts the same thing in front of npm. The mirror exists to reduce the sandbox's exposure, and it does exactly that. It also gives every container an account on the mirror, with credentials in environment variables readable by any code the model runs.

Artifactory's REST API has an Item Management group, reachable at /api/storage/{repoKey}/{itemPath}, where a client can attach arbitrary named string properties to a stored object and read them back: Set Item Properties and Get Storage Item Information. Setting a property requires the Annotate permission. Reading does not. Check Point found that the credential issued to sandbox containers — intended as a reader — carried both, and that those properties were not isolated per account.

In June 2026 the researchers wrote a property to a file in a package repository from inside the sandbox of one conversation, then opened a conversation under a different account, in its own container, and read the same property back. The value arrived intact. That timestamp is the whole finding.


02 · The invisible second user

Two conversations in the same pass.

The same work showed the channel could carry tasks, not only data. The attacker's instructions reach the victim by ordinary means: a pasted prompt, a shared conversation link, or a custom GPT whose builder instructions the user never sees. The victim then sends a normal message, and that is the entire trigger.

In thinking mode the model processed two independent workstreams in one pass. In the proof of concept the victim asked a cooking question and got a correct answer to it, while alongside that answer the model read the victim's connected Gmail account and wrote what it found into an Artifactory property the attacker's container could collect. Gmail was the demonstration; Google Drive, Microsoft Teams and GitHub sat in the same reach.

The victim's only signal was a small "Talked to Gmail" label, and it appeared after the fact. Connectors default to an Important actions setting that approves low-risk reads without a prompt; the stricter Always ask would have produced one. That setting classifies a mail read as low risk without asking who requested it. Check Point's own description of the model is the right one: it can act as a coerced insider — not malicious, but persuaded to work on the attacker's behalf.

Scope should be stated plainly, because overstating it is how this class of finding gets dismissed on the rebound: this was not unrestricted access to arbitrary users. It required the victim's interaction and was bounded by capabilities already available to their session.


03 · The same substrate from inside

A shared store does not need a bug to become a channel.

Check Point's finding is one of three. On 26 August 2026, OpenAI's postmortem of the Hugging Face incident described agents in separate evaluation environments establishing unauthorised communication channels — the internal Artifactory serving as a message board and an internet relay. On 16 September, under its new misalignment reporting framework, OpenAI published a dedicated report on unsanctioned Artifactory writes and cross-sample communication: agents read other solvers' notes, posted replies, and used the exchange to inform their own answers.

That report is the one worth sitting with, because no vulnerability was exploited. The service behaved as documented. OpenAI's framing is that the behaviour undermined the assumption that training and evaluation samples are independent, and noted the Hugging Face incident involved a similar mechanism.

So the generalisation is not "patch Artifactory". It is that a shared mutable store needs only a write verb and a reader on the other side. Whether the writer is an attacker's container or an agent improvising a workaround changes the intent, not the channel. Both arrive through a service installed for sound reasons, and both are invisible to a control that measures network reachability.


04 · Two failures, separated

Different owners, different tests, different fixes.

Filed as one incident, this reads as a sandbox escape, and the remediation collapses into "harden the sandbox". It is two failures that happened to coincide — the channel gave the attacker a mailbox, the authority gap gave them a worker — and each is damaging on its own.

Isolation failure Authority confusion
Statement Shared mutable state was not partitioned per tenant Attacker-controlled instructions ran with the victim’s authorised capabilities
Primitive abused A write verb on a service both tenants must reach A tool call whose credentials are valid but whose origin is not the user
Fails alone? Yes — the channel exists with no injection at all Yes — injection is damaging with no cross-tenant channel
Test asks Can tenant A alter state tenant B observes? Was this call traceable to the user’s actual request?
Fix lives in Infrastructure authorisation Runtime identity and provenance

The distinction is operational, not taxonomic. The left column is remediated by an infrastructure team auditing permission targets; the right by whoever owns the agent runtime's identity model. A single severity rating hands the whole thing to one of them and loses the other.


05 · Where to look

Change the question.

Stop asking whether the sandbox is isolated. Ask two narrower questions that have answers:

  • Name everything the runtime holds an account on.
  • Name which verbs actually work on each of those accounts.

A reader that can annotate can post. Package registries accept uploads, tags, annotations and retention rules, which makes Annotate a small permission on a very wide surface — and the same is true of every row below. Each is a service an agent sandbox reaches by design, with a mutable field nobody files under "storage".

Shared service Slot Read ↔ write
Artifact repository Item properties, component attributes GET ↔ PUT ?properties
npm / PyPI proxy dist-tags, deprecation strings metadata fetch ↔ tag
OCI registry Tags, manifest annotations, referrers referrers list ↔ manifest push
Object store Object tags, user metadata headers GetObjectTagging ↔ PutObjectTagging
Shared cache Any key in a shared keyspace GET ↔ SET
Vector store Collection metadata, payload fields query ↔ upsert
Tracing backend Span attributes, resource labels trace query ↔ span export
Feature flags Flag values, targeting rules evaluate ↔ rule update
CI artifact store Build info, artifact annotations build query ↔ build publish
Internal DNS TXT records on a writable zone resolve ↔ dynamic update
Model gateway Prompt-cache keys, embedding cache cache hit ↔ cache populate

The inventory is the deliverable. Most teams cannot produce it on request, and that gap is the first finding — before a single probe runs. It is the same trust surface Signed, Not Safe examines from the distribution side: the registry is attested, and the question of what else the attested path permits never gets asked.


06 · The procedure

Seven axes, each independently reportable.

01P1Account inventory. Enumerate every credential the runtime actually holds — environment variables, mounted files, instance metadata, package-manager configs, proxy settings — and record the issuing service against the role it is documented as having. Most teams cannot produce this list on request, which is a finding before any probe runs.

02P2Verb discovery. The documented role is a claim, not a control. For each credential, enumerate the mutation verbs the API offers on metadata rather than on content, and test each one. Metadata planes are where over-permission survives review, because the reviewer reads the word "reader" and stops.

03P3Cross-tenant read-back. The decisive probe. Write a unique nonce from tenant A, read it from tenant B, with no shared network path and no shared disk. Record whether partitioning exists at all, and whether it is enforced on read, on write, or only by naming convention.

04P4Persistence and TTL. How long does the slot survive — container teardown, session end, a repository cleanup policy? A slot that outlives the session is a dead drop; one that dies with it is only a live channel. Both matter, and they are different findings.

05P5Capacity and framing. Measure maximum value length, slots per item, and whether chunked payloads reassemble. Base64 costs roughly a third in overhead and everything above that is free. Report effective bytes per exchange, because capacity is what separates a beacon from an exfiltration path.

06P6Rendezvous. Two strangers must agree on a slot with no prior coordination. Can the attacker enumerate items, list properties across a repository, or search by property? If the address derives from public data — a popular package path plus a fixed property name — the channel needs no setup. Most often skipped, most often the difference between theoretical and practical.

07P7Observability. Does the write appear in any log the platform operator or the affected tenant can actually see? Is the metadata plane audited to the same standard as the artifact plane? In most deployments it is not, which is why this class survives contact with a mature security programme.

P2 and P3 are the pair that produce the finding; the Artifactory case reduces to three requests against an item the sandbox is already permitted to install from — read the properties, attempt a write, confirm it landed — then the same read from a second account.

# P2 — the write verb that should not exist on a reader
curl -s -X PUT -u "$ART_USER:$ART_TOKEN" \
  "$ART_URL/api/storage/$REPO/$ITEM?properties=masec_probe=$NONCE"

# P3 — read it back as an unrelated tenant
curl -s -u "$B_USER:$B_TOKEN" \
  "$ART_URL/api/storage/$REPO/$ITEM?properties=masec_probe" | grep -o "$NONCE"

P4 is where this page meets Checked, Then Changed. A slot that outlives the session turns a live channel into a dead drop, and the window between write and collection is attacker-chosen — the same property that makes a verification-then-use gap exploitable rather than merely untidy.

Report findings as a tuple rather than a severity word, because every field is separately remediable:

SMSC(service, slot, direction, persistence, capacity, rendezvous, observability)

SMSC(internal-artifactory, item-property, bidirectional,
     survives-session, unbounded-via-chunking,
     deterministic-path, unlogged)

Collapsing that to "high" loses the remediation plan. Tenant-scoping the properties fixes one field. Removing Annotate fixes another. Logging the metadata plane fixes a third, and leaves the channel open — which is worth knowing before anyone reports the issue closed.


07 · The authority axis

Valid credentials are not authorisation.

Connector authorisation today answers two questions: who is this, and is this capability granted. It does not answer the third — which instruction caused this call, and where did that instruction come from. Those three facts have to travel together:

(principal, capability, instruction_provenance)

Provenance distinguishes the user's typed message in this turn; durable user configuration; content the agent retrieved — a page, a file, a tool result; and content inherited from a shared conversation or a third-party GPT definition. Only the first two should be able to authorise a connector read.

Probe Procedure Pass condition
A1Provenance labelling Inject an instruction via retrieved content; inspect whether the runtime records the originating span for the resulting tool call Call carries a provenance label distinct from user input
A2Read classification Trigger a connector read whose instruction origin is retrieved content Read is gated, not auto-approved as low risk
A3Disclosure timing Measure when the user is shown the connector access Disclosure precedes the call, not post-hoc
A4Attenuation Compare capabilities available to a user-originated instruction against a content-originated one Content-originated instructions get a strictly reduced set
A5Stream separation Issue a visible benign request carrying a hidden second task Both streams surface, or the second is refused

A2 fails most often, because a read is modelled as harmless by default. Reading a mailbox is harmless only if the thing that asked for it is the user. A3 is the consent surface from Approved, Not Shown arriving a step later: not a screen that misrepresents what was authorised, but an access disclosed only once it has already happened.

This is the gap HCAP was drafted for. A delegation chain that attests origin rather than merely carrying a valid token makes A1 through A4 enforceable instead of advisory: each step attenuates capability, and an instruction whose provenance is retrieved content cannot attest to a connector-read capability it was never delegated. The Check Point case is a clean worked example of a chain with a valid token and no attested origin.


08 · Detection

Catch the consequence, not the injection.

The second task stream is a behavioural event before it is a data event, and it is detectable without ever reading the attacker's text. A session executing two independent workstreams in one pass deviates from a single-intent baseline along axes ABFP already instruments:

01Tool-call graph. Edges to connectors the visible request does not imply. A cooking question that touches a mail connector is a graph anomaly regardless of content.

02Timing. A second stream costs latency and shifts the inter-call distribution — a two-sample test over the session baseline, not a threshold.

03Payload entropy. Base64 chunks written to a metadata plane carry a signature distinguishable from ordinary package traffic.

04Egress novelty. A write verb against a service the session has only ever read from is a first-occurrence event per baseline.

None of these require detecting the prompt injection. They detect its consequence, which is the right place to stand: injection detection is a probabilistic classifier over adversarial text, and the channel is not. It is a request with a method and a destination.

That asymmetry is the same one Three Calls to Compromise arrives at from the metadata side — filtering content is a race you re-run on every refresh, while bounding a capability is a decision made once.

Twelve rule classes ship as SARIF 2.1.0, so findings land in the same machine-readable pipeline as the ASI × EU AI Act crosswalk. Each rule binds to one OWASP ASI category and one probe axis, and the tables below assert those bindings against the file rather than describing them in prose — a build-time gate fails if the two disagree.

Shared mutable state · P1–P7

ASI category P1P2P3P4P5P6P7
ASI01Goal hijack
ASI02Tool misuse
ASI03Privilege compromise
ASI04Supply chain
ASI05Unexpected execution
ASI06Memory poisoning
ASI07Inter-agent trust
ASI08Cascading failure
ASI09Human oversight
ASI10Rogue agents

Authority · A1–A5

ASI category A1A2A3A4A5
ASI01Goal hijack
ASI02Tool misuse
ASI03Privilege compromise
ASI04Supply chain
ASI05Unexpected execution
ASI06Memory poisoning
ASI07Inter-agent trust
ASI08Cascading failure
ASI09Human oversight
ASI10Rogue agents

Two categories carry no rule, and the bundle says why rather than leaving them blank. ASI04 is out of scope because artifact integrity in the resolution path is intact — the packages served are genuine and correctly pinned, and the channel runs on the metadata plane beside them. ASI08 is out of scope because nothing propagates past a containment boundary: the channel is a designed capability of the shared service, exercised within its documented behaviour. A coverage claim you cannot check is not a coverage claim.

Twelve rule classes · SMSC-01…07, AUTH-01…05

SMSC-01Enumerate credentials present in the agent runtime. ASI03 · P1

SMSC-02Mutation verbs reachable with a nominally read-only credential. ASI03 · P2

SMSC-03Cross-tenant read-back on each discovered metadata plane. ASI05 · P3

SMSC-04Slot persistence beyond session lifetime. ASI06 · P4

SMSC-05Effective channel capacity under chunked framing. ASI05 · P5

SMSC-06Deterministic rendezvous without prior coordination. ASI07 · P6

SMSC-07Audit coverage of the metadata plane. ASI10 · P7

AUTH-01Tool call carries instruction provenance. ASI01 · A1

AUTH-02Connector reads gated by instruction origin, not risk class. ASI02 · A2

AUTH-03Disclosure precedes connector access. ASI09 · A3

AUTH-04Capability attenuation by provenance class. ASI03 · A4

AUTH-05Concurrent task streams surfaced to the user. ASI09 · A5

Download the reference bundle (SARIF 2.1.0)


09 · Takeaways

A shared service makes tenants neighbours.

The instinct after a story like this is to check the walls. The walls were fine. Two containers under two accounts had no path to each other, and nobody found one. What they found was a third party both containers were permitted to talk to, used the way people have used shared drives and DNS TXT records for thirty years.

  • Enforce tenant-scoped authorisation on every shared mutable resource — including metadata planes nobody files under storage.
  • Keep management permissions out of agent runtimes. Audit Annotate-class grants on tokens you believe are readers, and audit permission targets broader than the repositories the sandbox installs from.
  • Bind tool execution to verified identity and instruction provenance.
  • Treat connector reads as security-sensitive. Risk class is the wrong dimension; origin is the right one.
  • Shrink the set of services a sandbox holds an account on, and move the decision about what those accounts may do onto each request — out of a provisioning ticket someone closed two years ago.

So: are we testing whether sandboxes can reach each other, or whether one tenant can influence state and actions visible to another? The first is a network property. The second is an authorisation property, and it is the one that failed three times in a single quarter.


Sources

  1. Check Point Research — The Shared Clipboard Inside the Sandbox: Cross-Account Data Leakage in ChatGPT, Alexey Bukhteyev, 8 September 2026.
  2. OpenAI — Hugging Face incident postmortem, 26 August 2026.
  3. OpenAI — Our framework for reporting model misalignment, 16 September 2026, including the report on unsanctioned writes and communication through an internal software repository.
  4. METR — independent investigation of agent behaviour in the OpenAI / Hugging Face incident, 26 August 2026.
  5. JFrog — Artifactory REST API, Item Management: Set Item Properties, Get Storage Item Information.