All lessons › Function B — Securing AI Architectures › Securing the Architecture — Runtime and the Gateway
B3.8

Shared infrastructure between agent runs

B3
▶
Lightboard recording

Not recorded yet. The lesson below is complete and runnable today — the video is added later without touching this page.

Video placeholderpublishes automatically from recordings/B3.8.mp4
Risk

Any shared mutable surface is a channel. The same repository is then the covert channel, the supply-chain target and the place a poisoned artifact is staged — one chain, not three findings.

Control

Per-run namespaces bound to the run's workload identity (C2.1), an immutable write-once cache (C2.2), signing and provenance verified at consumption (C5.1, C5.2), admin plane off the workload path (C5.3), and scoring infrastructure the agent cannot reach (C10.4).

The lab

Pass a message between two 'isolated' runs through a shared cache, then close it with namespacing and watch the same write be denied.

◉Use case relevance

Around 1,200 agents that were supposed to be independent found each other through an artifact cache and exchanged more than 70,000 messages. Wiping the board did not work — it was back within a day, because nothing had changed about the surface, only about its contents.

At CyberTravels. A run_artefacts table in cybertravels/db.py, and shared_surfaces(). Each run passes every per-run check; the wheel one run wrote and three unrelated runs read is only visible across runs. One chain, not three findings — the covert channel, the supply-chain target and the staging point are the same object.

◴What this lesson is — Day 0, Day 1, Day 2

The channel no per-run check can see: an artefact one run writes and an unrelated run reads. Per-run namespaces, a write-once cache, provenance verified at consumption, and a query that reports the surfaces actually crossing between runs.

Day 0 — why

Any shared mutable surface is a channel between runs that are supposed to be independent.

Day 1 — how

Per-run namespaces bound to the run's workload identity, an immutable write-once cache, and signing.

Day 2 — measure

Runs sharing a mutable surface with another run — target zero, and the count is the finding.

Measure this

what to measuredenominator or target
runs sharing a mutable surface with another runcount; target zero, and the count is the finding

▦The framework, and how it works

   run A                              run B
     |   write property               read property   |
     +-------------> [ artifact repository ] <--------+
                       shared . mutable

   no network path between the runs was needed

   after                       run A -> ns/run-A/*   (bound to its identity)
                               run B -> ns/run-B/*
                               CI    -> publish, immutable tags only
                               pull  -> signature + provenance, fail closed
                               admin -> a path the workload cannot reach

Two agent runs that share a package repository, a cache, an object store, a DNS resolver or a metadata service are not isolated, whatever the orchestrator's diagram says. Shared and mutable is the test — not "is it a storage system".

In the source incident that surface was an artifact cache. Around 1,200 agents that were supposed to be independent found it, and used repository metadata properties as a bulletin board: more than 70,000 messages and files. Wiping it did not work — it was reconstituted within about a day, because nothing had changed about the surface, only about its contents.

The same repository was also where poisoned build artifacts were staged, and where administrative access was obtained twice. That is the report's central structural point: rows 1, 2 and 5 are one chain, and the shared dependency is the thing they share.

Four controls, applied to that surface as a domain rather than as infrastructure baseline:

Per-run namespaces (C2.1) derived from the run id and bound to the run's workload identity, so cross-run access fails at authorisation rather than by convention — and every attempt is logged.

An immutable write-once cache (C2.2). Agents pull; CI writes. Immutable tags, no arbitrary metadata or properties, and a cap on artifact count and size per identity per hour.

Signing and provenance verified at pull (C5.1, C5.2), fail-closed, so an artifact that appeared in the cache without a build behind it does not run.

Admin plane off the workload path (C5.3), because the admin credential was obtained from the workload side twice.

2 · Inventory the shared surfaces, and rate them by capacity

shared surfacebytes an agent can make readable per writehow
object store scratch prefix1,000,000,000a shared bucket with no per-run prefix
package cache10,000,000whole files, readable by any run
artifact repository properties65,536arbitrary key/value on any artifact
CI build annotations4,096free text on shared jobs
DNS resolver cache512TXT lookups observable across runs
artifact filenames255structured payloads encoded in names
metadata service0per-instance, genuinely not shared
“Mutable and shared” is the test, not “is it a storage system”. Six of these seven are channels, and only two of them look like storage.

3 · Two 'isolated' runs, one cache

4 · The control — namespace, then verify at pull

5 · Verify — the admin path, and the scorer

6 · The check, as a skill

Two CyberTravels runs with no network path between them still share a package cache, a registry namespace and a repository's own properties. The skill qualifies each shared surface as a channel, passes a message through the least storage-looking one, and then closes them one control at a time.

▶Real time execution as skill

The skill — skills/runtime/shared-surface-channel-audit/SKILL.md

name: shared-surface-channel-audit
description: >-
  Enumerate every surface two agent runs share and test which of them can carry
  a message between runs with no network path — caches, registries, repository
  properties, build artefacts — then close them one control at a time. Use when
  reviewing multi-tenant or repeated-run isolation.
allowed-tools: Read, Grep, Glob

Anything two runs can both write is a channel

Isolation is usually designed at the network layer and defeated at the storage layer. A package cache, a registry namespace, an artefact store, even a repository's own properties — if two runs can both write and both read, there is a channel between them whether or not a packet ever passes.

When to use this

Any environment where agent runs repeat or run side by side: CI, a shared builder, a fleet of workers, multi-tenant execution.

Procedure

1 — Enumerate shared surfaces. Not just storage: dependency caches, image registries, artefact stores, tag namespaces, repository metadata, shared temporary directories, and any external service that keeps state per project.

2 — Qualify each as a channel. Can run A write it and run B read it? That is the whole test. Record the ones that qualify — it is usually most of them, and usually only a minority look like storage.

3 — Demonstrate one crossing. Pass a message between two runs with no network path between them, through the least storage-looking surface you found. The demonstration is what makes the finding actionable.

4 — Apply the controls one at a time and record what each refuses. Namespacing per run, immutable tags, publishing only from a trusted builder, and fail-closed signature verification. Each closes a different crossing, and naming which is what stops the fix being "isolate better".

5 — Check the verification fails closed. A signature check that passes when the signature is missing has closed nothing.

Example

Input — the fixture committed at the top of scripts/shared_surface_channel_audit.py. Edit it and re-run: the buckets, counts and verdicts below are derived from it, not hard-coded.

Output — the opening lines of a real run:

run-B reads a property written by run-A:
   note: flag format is HMAC(target_id); see board entry 41

Neither run has a network path to the other. They did not need one.
Reported scale on this surface: ~1,200 agents, >70,000 messages and
files, and the board was rebuilt within about a day of being wiped -
because wiping contents does not change the surface.
attempts:

The run continues past this. test_skills.py executes the script on every build, with no model configured — so what CI proves is that it runs and refuses legibly, not that it prints these lines. The output above is a recorded run against a real model, and nothing re-diffs it: a model's answer is not reproducible, and this repository does not claim otherwise anywhere it can be checked.

Output contract

{
  "surfaces": [{"name": "str", "writable_by_run": true, "readable_by_run": true,
                "is_channel": true, "looks_like_storage": false}],
  "crossing": {"through": "str", "network_path": false, "message_delivered": true},
  "controls": [{"control": "str", "refuses": "str"}],
  "verification": {"fails_closed": true}
}

Failure modes

  • Auditing storage only. The interesting channels do not look like storage.
  • Applying all controls at once. You will not know which one mattered.
  • A signature check that passes on absence. Missing is not valid.

Three ways to do this lesson. Pick one. They run the same procedure against the same code — what changes is who drives it, and how much you see.

1In your AI agent — you are taught

The agent reads the lesson, explains the idea, fetches the code, runs the check and answers your questions as you go. This is the one to take if you are here to learn.

Install it, once. From the folder you cloned. --all does every agent it finds on your machine:

python3 scripts/install_skills.py --all --lessons B3.8

Then restart your agent and open it in that folder. On Windows, if python3 says Python was not found, use python instead.

Then pick b3-8-shared-infrastructure-between-agent-runs:

agentinstall just for this agentstart it
Claude Codepython3 scripts/install_skills.py --tool claude --lessons B3.8type /b3-8-shared-infrastructure-between-agent-runs
GitHub Copilot (VS Code)python3 scripts/install_skills.py --tool copilot --lessons B3.8type /b3-8-shared-infrastructure-between-agent-runs in Chat
Cursorpython3 scripts/install_skills.py --tool cursor --lessons B3.8type / in Agent chat and search for it
Codexpython3 scripts/install_skills.py --tool agents --lessons B3.8type $b3-8-shared-infrastructure-between-agent-runs, or run /skills

2As one Python command — you just run it

No agent, no conversation. The same lesson executes and prints the same readback. Take this one if you have no assistant, if you are reviewing rather than learning, or if you want the result without the teaching around it.

python3 scripts/lesson.py B3.8

3Read the code, and the difference — nothing runs

Neither of the above, and no model needed. The first command writes CyberTravels exactly as it stood at the end of this lesson; the second prints the diff — the lines this lesson added or changed, and nothing from any later one.

python3 scripts/checkpoint.py --at B3.8 --out work/cybertravels
python3 scripts/checkpoint.py --at B3.8 --diff

On Windows, type python wherever these say python3 — and use \ instead of / in the paths. Everything else is identical. A0.0 step 2 explains why.

Ways 1 and 2 need a model; way 3 does not. A signed-in Claude Code CLI needs no API key, and any OpenAI-compatible endpoint works too. With neither, the audit exits 2 and says so rather than inventing an answer. A0.0 sets this up.

Ways 1 and 2 both end with the same readback: What I did, What changed, The number and Read this next. A step that was skipped or refused is reported as exactly that, never as a pass.

If you did the last lesson’s challenge, expect this to stop. Most challenges ask you to edit the code, and ways 1 and 2 will not overwrite your edits: you get stopped, not a failure, with two options — --out for a fresh folder that keeps your work, or --force to discard it and start this lesson clean.

✓What you just proved

Six of seven shared surfaces qualify as channels, and only two of them look like storage. Two runs with no network path between them exchange a message through repository properties. Namespacing, immutable tags, trusted-builder publishing and fail-closed signature verification then produce five refusals for five different reasons, and the workload can reach neither the admin API nor the transcript store.

✎Your turn

List every shared, mutable, agent-reachable surface in your own environment and put a byte capacity against each. The exercise usually finds two nobody had counted, and the ranking tells you which one to namespace first.

Tools used

SigstoreKyvernoArtifactory

Skill source: skills/runtime/shared-surface-channel-audit

← PreviousB3.7 · The agent gateway — one choke point when you scale