Every agent in a machine of its own, and a record of what held.
Each agent runs in its own microVM, and the host decides what that machine holds: the evidence as a read-only share, the agent's own directories, the hosts it may reach, and placeholders where the keys would be. The audit trail stays out of the agents' reach, and where a guard is only advisory the run record says so, so no forensic report can claim a protection the run never had.
Every guard, and what holds it.
A microVM per agent is the default, on macOS with Apple silicon or Linux with KVM. Host mode, which the examiner chooses with --isolation host, runs the same swarm unisolated, on the host under kernel policy, for a machine that cannot run microVMs; the kickoff never switches to it by itself. The kickoff measures what the host can enforce and records it, and the report prints it in the chain of custody.
Every part of a run and the lines between them. Select a part to see what it does, what holds it and what it leaves on disk, and try it; or play a run from the kickoff to the signed release.
- kelp work in motion
- slate files and the record
- moss sealed, passed, done
- saffron keys, budget, caps
- brick refused at the wall
A dashed line is a read-only view or a check. A solid one carries work.
Swipe sideways to see the whole map, or switch to the list.
Every part of one run in microVMs, in its place. Select a part, or play a run.
A peerAn agent's Pi in a microVM of its own, booted from the base image: a shell, Python and the tool library, with the forensic programs left to the job images.
- Reads the contract, the catalogue and the board, and divides the case with its peers; nothing is assigned.
- Keeps a shell for quick looks, and sends parsing, carving and anything long or worth citing to a job.
- Writes only its own directories, and reaches its peers through the hub.
- The run is a read-only floor in its VM; its own work, extracted and quarantine directories, its tool output and its session are the writable holes.
- A veil over the trace, the peers' sessions and the peers' tool output keeps its peers' records out of view, and the kickoff refuses a VM that sees them.
- Root in the guest is the agent's; the boundary is the host side of every share, the network policy and the hub's socket.
work/<seat>/work/extracted/<seat>/work/quarantine/<seat>/tool-output/<seat>/vm/<seat>.jsonAnother peerA second agent in a machine of its own. What it shares with the first travels through the hub, under a lease, on the record.
- Publishes a shared file, such as the report or the timeline, through the hub, which claims it, writes it on the host and records the revision.
- Sees a peer's published file current within five seconds; a claim on a file written in the last six seconds is granted once that window has passed.
- Learns what its peers are doing from the board, the claims, the ledger, the store and the team list the hub builds from them.
- Nothing writable is shared between VMs.
- A peer's own directory is refused to it for claims, writes, restores, records and publishes.
- Every process in its VM speaks to the hub as this seat, a forged tool or a parser over hostile content included.
publish_fileclaim_filelist_teamwork/report.mdThe criticA peer like the others, whose goal asks it to re-derive what each answer rests on before the run may end.
- Re-derives what an answer rests on from the sealed objects it cites, and records an attestation or a dispute, each in a chain of its own.
- Works from the ledger and the store; the veil keeps its peers' trace lines, sessions and tool output out of view, so it cannot read how a peer got there.
- Is what the goal's checks look for: an answer that no one but its author attested or disputed is a defect the finish line names.
- The harness assigns no roles: the goal names the critic, and the critic is one of the peers.
- An answer resting on an entry that is later disputed or superseded stops standing, and so does every answer resting on it.
attestdisputeledger/attestations.jsonlledger/disputes.jsonlThe hubOne process on the host that writes the board, the ledger and the registers for every seat, serves each VM a socket of its own, runs every tool job, decides every lookup and runs the finish line.
- Serves each seat over a socket of its own, which answers only a connection that opens with that seat's token.
- Writes the board and the ledger: posts, leases, published files, findings, answers, attestations and disputes.
- Writes the registers: the question register, the leads with their offers, and the requests the run makes of the operator, each hash-chained.
- Admits the operator's acts on the question register through an admin socket in its own directory, which only the host account reaches, and notifies each new request by its id.
- Runs the job service: every tool job, the catalogue's recipes and the derived catalogue.
- In a dynamic run, decides every lookup by rules and hands a granted one to the fetch service.
- Runs the goal's checks on the host when the seat that coordinates the finish says done, keeps the wall clock, pauses the run at a cap, and stops the VMs when the run ends.
- Who is asking is the socket a call arrives on, whatever the message claims.
- It never opens a file under a seat's own directory: a seat sends its file's bytes with the call.
- It bounds each seat: its connections, the bytes held for it, its calls queued, and its posts, records and done calls per minute.
- A keeper brings it back from its saved state if it dies, until the run's stop.
~/.dfirswarm/hubs/<run>/admin.sock<seat>.sockSWARM_SEAT_TOKENhub_call seat_authNetwork policyThe host's rule for each VM's network: its models' hosts and the hosts the examiner allowed, and nothing else. The keys are swapped in here, on the way out. A dynamic run adds no host to it.
- Lets a VM reach its models' hosts, the hosts the examiner allowed and, when the run allows installs, the package index.
- Swaps each placeholder for its value in the request headers, on the way to that credential's own hosts.
- Stops and logs a placeholder aimed at any other host, which custody reads.
- Stays as it booted in a dynamic run: a lookup goes to the hub over the seat's socket, and the fetch service on the host makes it.
- A name off the list does not resolve, and an address off it has no route.
- No credential enters a VM. Each seat's VM holds placeholders: for its own model, the summary model and, when the examiner allows them, a pack's secrets.
- An allowed host is a way out as well as in: anything a seat can send to its model's host leaves.
- The network mode and the case policy are fixed at kickoff, recorded, and sealed by custody; an agent can weaken neither.
--network closed|dynamic|open--policy standard--allow-host--allow-installswarm.sh netcheckblock-and-logThe evidenceDisk images, memory, phones, logs and captures, shared read-only into every seat's VM and every worker from where they lie.
- Is mounted into each VM at its own host path, read-only, with no copy made. For evidence the examiner's own account can still write, a read-only copy in the run adds a second layer.
- Is hashed at kickoff, SHA-256 with MD5 and SHA-1 beside it, and held to the imager's acquisition hashes when they are given.
- Is re-hashed in full on the host at stop.
- The host side of the share is the boundary: root in a VM can flip its own mount flag, and the host still refuses every write.
- A worker sees the evidence its job declared, read-only and no-exec; a job that declares nothing sees what a seat sees.
- An earlier run's findings on the same evidence are never mounted into a new one.
--inputs--inputs-copy--inputs-hashesinputs.jsonThe harnessThe harness's extensions, scripts and prompts, copied at kickoff and mounted read-only in every VM where the checkout is.
- Is copied at kickoff and mounted where the checkout is, read-only, so an edit or a pull mid-run does not reach the agents.
- Gives the hub a second copy taken at the same time, from which the hub, the VM finish and its custody run, with their own copy of msb.
- Keeps the repository itself out of every VM, since the run registry and the published cases would come with it.
- The kickoff prints which commit it froze, and the run records what produced it: the harness commit, Node, Pi, msb and the image digest.
- The extension inside a VM runs under the guest's root, so its checks are advisory there; the finish line, custody and the report are decided on the host.
provenanceextensions/scripts/prompts/The examinerThe person who starts the run, watches it in the console, asks it questions while it works, answers what it asks, reviews the ledger, and signs the report's release with a key of their own.
- Starts the run from the console or the terminal, and watches it live: the board, every VM's probe and hub link, the jobs, the ledger and custody.
- Asks the running swarm a question, answers the requests the run makes (a clarification, an acquisition, a lookup to grant), extends a paused run, and resumes one that stopped.
- Is enrolled on the install once, outside every run, with a name, an organisation, a competence statement and one key: an ssh key with a passphrase, a FIDO2 security key, or the certificate on a PKCS#11 e-signature token.
- Adopts, qualifies, withdraws or renders inconclusive each answer in a chained review beside the registry that no agent reaches, then signs release v1 in the console's Release tab or with swarm.sh review --sign.
- Imports a technical reviewer's signed review made from the package on another machine, checked against the register or the CA.
- Every command they give a run is a line of the operator's chained record, and on a live run a line of its trace.
- The release seals exactly the bytes shown with their sha256 after the consent, and the passphrase, touch or PIN reaches the signing program only down a pipe.
- Their key is out of every VM, and the console refuses a key without a passphrase or one held in ssh-agent.
- An answer whose support is defective cannot be adopted: it is withdrawn or rendered inconclusive.
swarm.sh examiner enrollswarm.sh review --signruns/reviews/<run>.jsonlrelease/v1/swarm.sh releases --verifyFetch serviceA host process that makes exactly the lookup the hub granted and nothing else, holds no evidence, and seals each answer as a capture.
- Takes a grant from the hub: one exact method and URL, for one seat or one job, for five minutes and one use, with a byte limit.
- Sends only its own headers, resolves the name once, refuses loopback, private, link-local and other special addresses, and connects to the address it checked with TLS verified against the name.
- Writes each use to its log before a byte leaves, and seals the answer in the store as a capture, which the ledger records as external material.
- It is no proxy: anything but the grant's method and exact URL is refused.
- A body over the limit is refused whole; a revocation or expiry during a transfer stops it, and what came is kept as incomplete and delivered to nobody.
- The rules that decided the grant read no free text, and no model sits in the decision.
network/grants.jsonlnetwork/fetches.jsonlstore/net/<k>/<n>/net:<k>/<n>swarm.sh net <run> listWorker microVMsThrowaway microVMs the hub makes for one job each, booted from the job image of the profile the work needs, and removed when the job ends.
- Boot from the image the job names or its pack's image; a command that names none runs in the smallest job image that holds every program it runs.
- See what the job declared, read-only, resolved when the job was submitted; a job that declares nothing sees what a seat sees.
- Write only their own output directory, which is sealed into the store once the worker is gone.
- No credential, and nothing of the board, the inbox, the ledger or the sessions.
- No network unless the job asks, and then only the run's allowlist; in a dynamic run it may call the fetch service with its own job's token.
- A running job is watched from the host by its output directory, its logs and its CPU and I/O; a suspected stall is shown and never acted on.
- What a job read within its scope is not observed, and the record says unknown.
job_run profile=diskinputs=[…]$OUTstore/jobs/<id>/scope.<attempt>.jsonTrace collectorThe only writer of the audit trail. It runs on the host, chains every line to the one before and anchors the head outside the run.
- Writes every call a seat makes, its arguments, its whole result and the reasoning before it, as the hub hands them on.
- Attributes each line by the channel it came through, and stamps the host's time of receipt beside the seat's own.
- Names on each line the file that holds a tool's whole output, with its size and its sha256.
- In a microVM run the hub is its only door, and no VM sees the trace.
- A line claiming to be another agent is written as what it is, with the claim beside it.
- The report walks the chain and says intact, edited, appended, shortened or rewritten.
traces/events.jsonltool-output/<seat>/prevrecv_tsThe ledgerEvery fact the agents establish, each with the sealed objects it rests on and what it indicates, written by the hub and chained entry to entry.
- Holds events, indicators, findings, searches that found nothing, hypotheses and limitations, coverage records, external material, and one answer per question with its result.
- Resolves every ref when the entry is written: an input, a job's sealed output, a catalogue member, a capture. One that does not resolve refuses the entry.
- Takes an attestation or a dispute of an entry beside it, each in a chain of its own; a review of a negative is an attestation that says what it checked.
- Nothing is edited or deleted: a correction is a new entry that supersedes the old one.
- A finding says what it indicates, how sure its finder is and why, and what else could explain it.
- A material negative stands unreviewed until another seat reviews it, and the finish line waits for that.
- A seat's own output is sealed as an import before an entry may cite it.
recordledger/entries.jsonlledger/attestations.jsonlledger/disputes.jsonlkind=coverageThe storeWhere every job's output is sealed, file by file, with a hash-chained journal of every step the job service took.
- Seals a committed job's output read-only under its own directory, every file hashed and stored once by its sha256.
- Keeps the journal: accepted, started, finished, fenced when the worker is gone, committed; one chained line each.
- Is what a finding cites, as job:<id>/<path>.
- Nothing is sealed while the worker that wrote it could still write.
- A failed job keeps what it wrote, and a finding resting on it says why those bytes still hold.
- The journal's head is anchored outside the run.
store/jobs/<id>/out/store/blobs/<sha256>store/journal.jsonlCustody at stopThe verdict on the run, taken on the host at stop: the evidence and the store re-hashed, every chain sealed, every VM's disk kept.
- Keeps each VM's disk as a snapshot, with its integrity record, before the VM is removed.
- Re-hashes every evidence file in full, and compares the acquisition hashes when they were given.
- Walks every hash-chained register (the trace, the ledger with its attestations, disputes and store sweeps, the leads and the finish register, the question register, the requests, the network's grants and fetches, the store's journal and the model gateway's log), checks every sealed output and capture against its manifest, and seals the length and head of each chain.
- Holds the case policy to the hash the kickoff anchored, and names a rewrite.
- Seals the machine's draft of the report as release v0, which adopts nothing.
- Every check has a status: passed, failed, incomplete, not applicable or unavailable.
- It reads and writes nothing through a link, and its verdict's hash goes into the anchor outside the run.
- It can be taken again later, writing nothing, and held to what it sealed.
- After a resume, every earlier verdict verifies as a prefix of the same chains, and the next stop seals the continuation anew.
custody.json<run>.vm-snapshots/swarm.sh custody-verifyrelease/v0/Model providersThe hosts of the models the seats run on: cloud, private or local. A seat reaches only its own model's hosts, with its key swapped in on the host.
- Receive each seat's model calls through the network policy, with the key the host swapped in.
- Can be fronted by an optional model gateway on the host, which holds the key and meters each call from the provider's own answer.
- Serve a local model through the host's gateway port when the team runs on this machine.
- A provider whose host is unknown is refused at kickoff, and one that signs its requests with its secret on the client cannot run in a VM.
- A seat's spend is what it reports about itself unless the model gateway meters it; the wall clock is the brake the host holds by itself.
--models--provider-host--model-gatewaybudget.jsonA case's own hostsThe hosts a case needs, such as a symbol server, reachable when the examiner allows them: at kickoff for the whole run, or later as a socket grant.
- Are named at kickoff, one host or port at a time, and checked before anything is written; the kickoff says each is a socket allowance for the whole run.
- Reach a worker only when its job asks for the network, and then only the run's list.
- Can be allowed later with swarm.sh lead note --allow-host, which makes a socket grant: a host and a port, recorded in the grants chain.
- Let a memory job fetch Windows kernel symbols from Microsoft's symbol server and the storage hosts it redirects to.
- A socket grant controls the host and the port and nothing more: no method, no path, no capture of the content.
- It is refused under the ctf, internal and live_adversary policies.
- A suffix lets a seat send data to any host under it, and a suffix of one label is refused.
- A URL found in the evidence is a finding to record; resolving it would tell the subject their device is being examined.
--allow-host msdl.microsoft.com:80--allow-host '*.blob.core.windows.net'swarm.sh lead note --allow-hostswarm.sh netcheckReference servicesThe public services a lookup may reach, each through an adapter that places typed values into a fixed request.
- RDAP, crt.sh, the NVD CVE API, CISA's KEV feed, CIRCL hashlookup, RIPEstat, Nominatim, Overpass and a video's title.
- VirusTotal by hash, when the operator configures a key on the host that only the fetch service reads.
- One evidence-linked HEAD of an exact URL, which is active contact.
- Only the fetch service reaches them, one granted request at a time.
- A lookup discloses: the case policy says which classes of case data may leave, such as hashes and public indicators.
- A capture's hash proves its bytes; an examiner records what they establish.
network/adapters.jsonDFIRSWARM_VT_API_KEY- 01
Boot
The kickoff checks that this host can boot the VMs and hold what the run asks for, and stops with the reason when it cannot. It hashes the evidence, records the case policy and anchors both, pins the image by its digest, freezes a copy of the harness, and starts the trace collector and the hub. Then it boots a microVM per seat from the base image, and each VM's probe has to report its floor read-only, its own directories writable, the evidence read-only and the hub reachable.
- 02
Shares
Each VM holds the run as a read-only floor, with holes that are the seat's own: its work, extracted and quarantine directories, its tool output and its session. The evidence is a read-only share from where it lies, and the keys are placeholders. A veil lies over the trace, the peers' sessions and the peers' tool output, so a seat reads none of its peers' records.
- 03
Work
A seat asks the hub for a job over its socket, and the hub makes a worker microVM of the image the work needs. The worker sees, read-only, what the job declared, or what a seat sees when the job declares nothing; it writes only its own output directory and has no network unless the job asks. Once the worker is gone, what it wrote is sealed into the store, and every step is a line of the hash-chained journal.
- 04
Reach out
A seat's traffic leaves only through the network policy on the host. Its models' hosts and the hosts the examiner allowed are reachable; a name off the list does not resolve and an address off it has no route. The key is swapped in on the host, in the request headers, and a placeholder aimed at any other host is stopped and logged.
- 05
Look up
In a dynamic run a seat that needs what a reference service knows asks the hub for one bounded lookup. The hub decides it by rules alone under the case policy and records the decision before it answers. A granted lookup goes to the fetch service on the host, which makes exactly that request, seals the answer as a capture and has it recorded as external material. The seat's VM gains no host.
- 06
Record
The hub is the one writer of the board, the ledger and the registers: a post, a lease, a finding with the sealed objects it rests on, a coverage record, an answer with its result, an attestation or a dispute, a question and its revisions, a request to the operator. Every call a seat makes reaches the trace collector through the hub, and the collector chains each line to the one before and attributes it by the channel it came through.
- 07
Finish
When the seat that coordinates the finish says done, the hub runs the goal's checks on the host, where the trace they read is; any other seat's done is answered "not yours". A check that fails refuses the done, and the seat is told which one and what it printed. When the finish line passes the run is stopped from the host and the hub puts the VMs away. At a cap the run pauses instead: no seat is prompted until the examiner extends it or stops it.
- 08
Custody
At stop each VM's disk is kept as a snapshot. Custody re-hashes the evidence in full, walks the trace, the ledger and the store's journal, checks every sealed output against its manifest, and seals the head of every chain, with a status for every check. The report's first release, v0, is the machine's draft: sealed by this install's machine key, and adopted by no one.
- 09
Sign
An enrolled examiner reviews the ledger and adopts, qualifies, withdraws or renders inconclusive each answer; a technical reviewer signs their own review first when the run asks for one. The report is rendered once without the draft mark and shown with its sha256, and after the examiner's consent their passphrase, touch or PIN seals release v1 over those bytes. Every later release names the one before it and why, and verify walks them all.
- 10
Resume
A run that stopped or was sealed can go on as the same run: swarm.sh resume, anchored beside the run before anything moves. The VMs are booted again from the options the kickoff kept, each seat starts from its last hand-off, and the ledger, the registers, the board, the trace and the store are appended to. The next stop seals the continuation anew, and every earlier verdict verifies as a prefix of the same chains.
| guard | what it holds | in a microVM · the default | in host mode |
|---|---|---|---|
| Evidence read-only | No agent can write the evidence. In a microVM it is a share the host holds read-only: root inside the VM can flip its own mount flag, and the host still refuses every write. Hashed before the first agent starts and re-hashed on the host at the end. | read-only share · host | seatbelt · macOS mount namespace · Linux |
| Write allowlist | An agent writes its own directories and nowhere else: not the examiner's home, not another case, not the run registry. In a microVM those are its own work, extracted and quarantine directories, its tool output and its session; the rest of the run is read-only in its VM, and shared files reach the others through the hub. In host mode a pane writes inside its own run. | own directories · host | seatbelt · macOS Landlock · Linux |
| Quarantine | What is extracted from the evidence lands on no-exec mounts, so a carved binary does not run by mistake. In a microVM that is a mount flag in the guest, and a peer's carved material is no-exec there too; it does not stop a root inside the VM that means to run it. | no-exec · guest flag | seatbelt · macOS noexec mount · Linux |
| The board | Posts, leases and shared files. In a microVM run they have one writer, a hub on the host, and each VM reaches it over a socket of its own, so who is asking is decided by the socket, whatever a message claims. The hub bounds what one agent can ask of it. | one writer · the hub | in each pane · under leases |
| Tool jobs | Parsing, carving and any long or citable work runs as a job in a throwaway worker VM, made for that job and removed after. It reads what the agent reads, read-only, and writes only its own output directory; it holds no key and nothing of the board, and it has no network unless the job asks for the run's allowlist. Nothing it wrote is read until the worker is gone, and then it is sealed into the store. In host mode there are no jobs, and tools run in the agent's pane. | a worker VM per job · sealed | none · tools run in the pane |
| Trace collector | The only writer of the audit trail runs on the host, outside every agent's reach. Each line names the hash of the line before; the head of the chain is anchored where no agent can write. In a microVM run every line is attributed by the channel it came through. In host mode it is a per-pane token no other pane can read on macOS, and on Linux, where one pane can read a peer's environment, a gate in front of the collector attributes by process ancestry. | enforced · by channel | by token · macOS by ancestry · Linux |
| Keys | A provider's key, and a pack's secret, never enter an agent's microVM. The VM holds a placeholder, and the host swaps in the value on the way out, in request headers, only toward the hosts that key belongs to. A placeholder aimed anywhere else is stopped and logged. In host mode the pane's own agent reads its key from the examiner's store. | placeholder · swapped on the host | the agent's own store |
| Prior-run isolation | An earlier run on the same evidence (its findings, its board, its tools) is unreadable to the new swarm, so a re-run cannot copy the earlier answers. In a microVM it is never mounted, and a kickoff that would mount it is refused. In host mode on Linux the directory is masked by a tmpfs inside the namespace, or denied by Landlock where there is no namespace; under Landlock alone the file names stay visible and the contents do not, and the record says which. | never mounted | seatbelt · macOS tmpfs mask or Landlock · Linux |
| Signing keys | The keys that seal and sign the report's releases stay out of every agent's reach: the install's machine key, each enrolled examiner's and reviewer's key, and the ssh-agent socket, with the examiners' reviews beside them. In a microVM none of them is mounted. In host mode the kernel guard denies them to every pane; a host run whose panes could still reach one is refused unless the examiner accepts the exposure, and the record says which it was, for the release to bind. Signing the report | never mounted | seatbelt · macOS kernel guard · Linux |
| Terminal control socket | The multiplexer the agents run in has an unauthenticated control socket; an agent that reached it could start a process outside every rule above. It stays on the host, where no microVM reaches it. In host mode it is denied to the panes: on macOS by the profile, on Linux by a mask inside the mount namespace. Landlock alone cannot mask a socket, and on such a host the record says unenforced rather than claiming a seal. | outside every VM | seatbelt · macOS namespace mask · Linux |
| Egress allowlist | Only the model providers and the hosts the examiner allows. In a microVM the host's network policy denies everything else: a name off the list does not resolve and an address off it has no route, and that refusal leaves no log. In host mode every refused connection is counted and printed. Installing from an index, when allowed, is inventoried package by package. In a dynamic run no VM gains a host: each lookup is decided by rules and fetched on the host. The network | deny by default · host | proxy · advisory · macOS network namespace · Linux |

In a microVM the boundary is the host side of every share and of the network. The guest's kernel is the agent's: root inside the VM can flip its own mount flags and unmount its own directories, and the host still refuses every write to the evidence and the run. What runs inside the VM, the harness's own extension included, is the agent's to change, so the finish line, custody and the report are all decided on the host. Some limits stay, and the repository lists them: an allowed host is a way out as well as in, the host's microVM runtime holds the run's keys while the VMs live, and the spend in a VM run is what each agent reports, with the wall clock as the brake the host holds by itself.
In host mode, "advisory" is not a euphemism. On macOS the egress guard is a proxy the agents are pointed at through their environment, and an environment belongs to the process that has it. On Linux the same guard is a network namespace with no route out except through the proxy, which is a kernel boundary. Every Linux cell was measured in two tiers, a host with user namespaces and a container without them, and the run record carries which one a run had.
Hashed on the way in, held read-only throughout, checked on the way out.
In a microVM run the evidence is shared read-only into every agent's VM from where it lies, and the host refuses every write through that share, whatever the guest does. For evidence the examiner's own account can still write, a read-only copy in the run adds a second layer. Every file's hash is recorded before the first agent starts, with MD5 and SHA-1 beside SHA-256, and held to the acquisition hashes the imager recorded when you give them: a mismatch stops the kickoff. At the end the host re-hashes every file, and the verdict, content and metadata separately, is anchored outside the run and printed in the custody section; a check that runs out of time names the files it did not re-read.
In host mode the inputs are copied into the run with a pristine copy beside it, the kernel refuses writes in every pane, and a write that lands by another route is restored from the pristine copy and announced on the board.
Work over the evidence that parses it, takes long or produces something to cite runs as a job in a throwaway worker VM of the image that holds its programs. What a job writes is sealed into the store, read-only and hashed file by file, the same bytes kept once, and every step of every job is a line of a hash-chained journal anchored outside the run: who asked, the image, the scope it could read, the network, and what was sealed. A finding cites a job's output as job:<id>/<path>, and custody re-hashes every sealed file against its manifest. A job that failed keeps what it wrote, and a finding resting on it is told so. A job starts in the run's directory, read-only in its worker, so a program that writes its log where it stands is pointed at the job's own output; a job that failed on a write outside it says so, with the path and where it may write.
The catalogue of the evidence has one author, the job service, working from recipes the packs ship. The kickoff gives every input a coverage row: catalogued, in part, or not, and why. The recipes then run as jobs while the agents work, each result a new generation and each change a numbered revision, announced on the board. What the jobs produce, an archive or a decrypted volume, is offered to the recipes in turn, in the lowest lane, so a container an agent opens in the middle of a case is catalogued for the whole team. In host mode the catalogue is built before the agents start.


The audit trail cannot be edited by what it records.
Every tool call, its arguments, its result and the reasoning before it. Every intervention with its reason. Every refusal.

A collector process on the host holds the only writable handle. In a microVM run the agents reach it only through the hub; in host mode, over a socket in a directory they cannot write. Either way it cannot be unlinked or replaced.
Each line names the sha256 of the line before it. The head of the chain is written outside the run, so a file that is rewritten, extended or shortened no longer matches what the record remembers.
Who wrote a line is decided by where it came from: in a microVM run, the agent's own socket to the hub; in host mode, a per-pane secret the collector holds. A line claiming to be another agent is written as what it is, with the claim beside it; an attempt on the record is itself a finding.
The report walks the chain and says intact, edited, appended, shortened or rewritten, and how many lines could not be attributed to an agent.
The examiner is on the record too. Each command that starts, stops, extends, resumes, reaps, speaks into, asks, reviews, signs, packages, exports, holds, releases, purges or verifies a run is a line in a chained operator log, with the OS user and the host, and so is every signing act in the console; on a live run, starting, stopping, reaping, speaking into it, reviewing, exporting, holding and releasing are on its trace as well. The report says which evidence files no command on the trace named, and until an examiner adopts an answer it says that answer is the agents' conclusion. How the report is signed is below.
Chained entry to entry, like the trace. An entry is an event, an indicator, a finding, a search that found nothing (with what was searched and whether the search completed), a hypothesis with its status, a limitation (what could not be examined, and why), a coverage record behind a negative, material from outside the evidence with its provenance, or an answer with its result. It can say which question it answers, which entries it supports, contradicts or derives from, which clock a time came from and how precise it is, whether it was observed or inferred, whether it holds sensitive material, and which inputs or sealed job outputs it rests on. A second author is an attestation beside the entry. Nothing is rewritten.
Every check has a status: passed, failed, incomplete, not applicable or unavailable, with what it covered. The verdict seals the length and head of every hash-chained register the run keeps: the trace, the ledger with its attestations, disputes and store sweeps, the leads and the finish register, the question register, the requests, the network's grants and fetches, the store's journal and the model gateway's log. A release binds each of them. The verdict holds the case policy to its hash, compares the evidence with the imager's acquisition hashes, matches the operator's log to the trace, and verifies each job's logs and each catalogue generation against their seals. It can be signed with an ssh key and timestamped by an RFC 3161 authority, with a reference clock's offset beside it, and custody-verify checks a finished run again while writing nothing.
A package made with --redact takes out what a sensitive entry says and the files it cites, lists each change with the hashes before and after, and every chain in it still verifies. The package carries every register custody sealed, and verify re-hashes it, re-walks each register against the verdict's seal and walks every release of the report it carries. A package can be signed with the examiner's ssh key. In the console a sensitive entry stays blurred until it is clicked.
swarm.sh replay <run> reads a finished run's registers again under any harness version's finish rules, with no model call and nothing written in the run, and prints codes, ids and counts rather than what the records say. --compare sets two versions side by side and names every difference. Synthetic histories, each held to what the design decided, keep readiness, the answers check and the finish gate in agreement on every question, and every custody verdict verifying as a prefix. Replay measures the decisions on a history already written; whether a rule changes what the agents do takes paired runs.What the run is asked, who asked it, and what an answer may say.
A real case starts without its answers, and some of them the evidence cannot give. Questions raise further questions as the work goes on, and the people watching think of new ones. The run keeps one register of all of them: the goal's numbered questions, the ones agents open from the evidence, and the ones people add while the swarm works. Each is a Q-n in a hash-chained file the hub writes, with who asked it, every revision of its words and how it was answered. The finish line holds every material question in scope to an answer, and a negative answer has to say where it looked and hold against every output the run already has.
questions/questions.jsonl, beside the leads, one writer.-
who asks
- goalNumbered in the goal and seeded at kickoff:
Q-3is the ledger'squestion:3. - agentOpened from the evidence, with the entry that raised it.
- analystA person, while it runs: an examiner, an analyst, a reviewer or an observer, claimed or signed.
- goalNumbered in the goal and seeded at kickoff:
-
the registerQ-7 · rev 2
One question, and its history
Every revision word for word, who asked it and how, its objective, and whether the finish line waits for it.
in scopeproposed · triageexcluded -
the seats
Leads
Offered to one seat at a time, for a minute. The first lead under a person's question states the proposition and its negation, and plans its routes: a source and a method each.
A question found on the way goes back to the register as a newQ-n. -
the answer
Its result
- established
- partial
- bounded negative
- not determinable
- out of scope
- premise not supported
A bounded negative reads "No evidence that X was found in" its objects and time range.
- An agent asksa clarification,
C-4; the question waits - A request to you
R-12, sent to your targets by its id - You reply
clarify-reply Q-7 C-4 "…" - The seat wakeswith the reply posted to it
An agent's question names the entry that raised it. A person's names their role, their OS account, the host and the channel, whether they are enrolled, and whether the act was claimed with --as or signed with --sign by their enrolled key: ssh, FIDO2 or PKCS#11, like a release. With nobody named, the act is the OS account's, marked as not enrolled. Who asked a question, who found its answer and who adopted it stay three separate records.
An examiner's or the operator's question is in scope by their authority, and may add an objective. An analyst's is in scope inside an objective and proposed otherwise; a reviewer's and an observer's are proposed. A proposed question waits in a triage queue for the operator or an examiner to admit or exclude it. Inside an objective or under a question in scope, an agent's question is in scope, and an agent never changes a person's question or the goal's.
The first lead under a person's question states the proposition and its negation. Its answer names the evidence against it, or says why there is none, and "the premise is not supported" is an answer. A question can say what it presumes, presumes: in the goal or --presumes when you ask it, and that premise is tested before the question is answered. A review of an established answer to it tests "the question's premise is not supported" as a rival, or the answer is recorded as a best candidate; a partial answer that never tested it is warned. A clue that fits the question's frame is a candidate to test against that rival. A question worded as its own conclusion, "confirm that" or "prove", is flagged for the critic and worked all the same. Who asked, and how urgently, carries no evidential weight.
swarm.sh question <run> add, or the console's Questions tab, commits the question to the chain first and acknowledges it after. Then it is posted from analyst:<you>, ranked first in every agent's header, and offered to the seat you suggested for its first minute, or to the idle seat best suited to it. A hint says where to look; a hint that says what to find is recorded as an open hypothesis in your name.
An answer names the revision of the question it answers. An amendment makes the standing answer stale until it is recorded again, and a question admitted a minute before done is held like the goal's own. One admitted after the sentinel is kept as a follow-up, which a resume takes up. While the coordinator assembles the finish, another seat revises an answer only with material: why it changes a conclusion.
Withdrawing a question needs a reason. The leads that served only that question close, a lead holding a finding goes to triage, and nothing in the ledger is touched. A withdrawn goal question is no longer required by the finish line; the goal keeps it, and the register says who withdrew it and why.
Whose device it is, who the subject is: what the case brief states as given is a premise, P-n, on the same chain as the questions. Only you designate one, in the goal's front matter premises: or with swarm.sh question <run> premise add, and the kickoff warns about a brief with none. A premise an agent proposes is tested until you admit it. When one answer assumes a premise and another contradicts it, both wait until the record reconciles them, and neither is forced.
An answer lists the parts of the question it answers, each established on the entries it rests on or open with what bounds it: an evidence request, a route or a limitation. A partial answer names what is open. Only a part the question asks can be open, and a premise never is. A part that nothing in scope can settle, such as what came before the retained logs, can be held at the limit of the evidence instead: it keeps the answer partial, and the report tells the gaps more work could close from the ones the evidence cannot. The report shows each answer's parts, and beside a partial label it says when every asked part is established; the metrics count those answers. A reviewer can name a part the answer left out, and it is warned of until the answer is recorded again with it.
The coordinator drafts the report and prepares the finish before its done: everything late against the report is listed whole, and it answers the list in one call, each item folded in or not material with why. A review of the report names the sections it covered and carries over to the next version while they are unchanged, so only what changed is asked again.
A search that found nothing says where it looked, and another seat checks it.
An answer carries its result: established, partial, a bounded negative, not determinable, out of scope, or a premise not supported. Established, partial and a premise not supported rest on a standing finding, since a search that found nothing cannot show a premise false. A quick "looked, not found" is how an examination goes wrong without anyone noticing, so a negative answer has to pass a bar on the host before the run may finish. A positive answer is held too: it is taken down only with evidence against it, and it keeps a high confidence only when a review weighed an alternative.
Every material negative cites one. It says what was searched: the proposition, the objects and the time range, the method and its settings, what was covered, skipped or failed, and what is still open. It also states the detection opportunity: whether the event would have left a trace in those sources at all, yes, no or unknown, with why.
The hub compares every object the record names with the inputs the jobs behind it were actually given, and marks the coverage complete, or partial with what was missed. A job over everything covers each object and is said to. Whether an object was the right one to search stays the examiner's judgement.
The record names the strings a hit would contain were the answer in the evidence. The hub searches every output the run already holds for them: each job's output and logs, the evidence added later, the captures and the agents' kept tool output, byte for byte, in UTF-8 and UTF-16LE, with no parser. A hit in an object the record does not name holds the negative until that object is examined and the record names it, or the answer is revised. A sweep cut short names what it did not search. A kept output and the import it was sealed as are one object. A hit that only repeats the run's own words (its registers, a seat's summary, or a search over objects the record already names) is an echo: it is shown, and it holds nothing. When the sweep's rules change, the sweeps recorded under the older ones are read again when the run next starts or resumes.
A lead under a question plans its routes. At a negative close the hub lists the planned routes no job examined, and a material negative is refused when no route was planned. A negative closed within two minutes, after one job over one object, is flagged as a quick negative in the leads and the report, and in every header until a peer attests it.
A material negative stands as "negative (unreviewed)" until a seat that recorded neither the answer nor its coverage reviews it: did it challenge the detection assumptions, reproduce a decisive check, try a materially different route, each done or not, with what was done or why not. The review is offered to one seat and held for it while it works. It opens with the question as asked and the sources the answer leads back to, then the sweep and its hits, and links the answer rather than quoting it; a seat of another model family is asked first where the team has one. The finish line refuses while one is unreviewed.
A question that asks for every file, all connections or a complete list is marked so, by its words or by whoever asked it. Its established or partial answer rests on a coverage record of five areas, allocated, deleted, unallocated, slack and secondary, each searched, skipped or not applicable, or the finish line holds it.
A revision that takes an established or partial answer down to not determinable or a negative names the entries or objects that undermine it. Without them the revision is refused, and the doubt becomes a dispute on the answer and, if it holds, a lower strength or confidence. The report shows a downgraded answer's earlier form, its disputes and the evidence that took it down.
A review that holds an answer established names an alternative explanation it weighed, why the evidence rules it out and the entries that show it; one that names none is recorded as a best candidate. The run records high confidence only on an established answer reviewed that way. Any other high is recorded as medium, and the report and the metrics show the confidence its author stated beside it.
An established review of an established answer names the strongest rival reading and a test that separates it. It locates each literal value in the sealed bytes, and the hub reads the bytes at that offset and checks that the value is one the answer or its chain states; a derived value names the job that derived it. A review with no rival test that counts, a locator that does not verify or a derivation that does not resolve is recorded as a best candidate and told how to fix it; one that locates and derives nothing is warned. A locator proves the value is there, not that it is the answer: the review stays a best candidate until a coverage record for the question says the other areas of the source it was read from, deleted entries, unallocated space and slack, were searched too, so a rival value in slack is looked for before the one in the live file stands.
A pack's recipe says whether it inventories a source or parses all of it into something searchable. The hub offers each broad extraction that applies as a lead of its own, one per source; the mobile pack parses a whole iOS or Android file-system acquisition at the kickoff. Every step is a receipt on the store's journal: planned, attempted, produced, partial, failed or declined.
A negative that claims absence, or complete coverage of a source, waits while that source's broad extraction is planned or running, and whatever it yields releases it, a failure or a decline included. Any other negative on a source not yet parsed whole is warned, and its reviewer sees each source's state first.
The answers check's warnings reach a seat at the moment it decides: the reply to an answer, a review offer, an attest, a lead's close, and the finish status. An answer that leaves out what the record ties to its question is warned, entry by entry, with each tie. A warning never holds the run.
A cap pauses the run for you. A run that ended can go on, as the same run.
At the spend cap, the token cap or the wall clock, every agent is steered once to post a checkpoint, and two minutes later the run pauses. No model call goes out, every seat stays where it is, and you are notified. The pause does not rest on the agents' cooperation: the hub prompts no seat, the model gateway refuses the call when the run has one, and the watchdog enforces the pause from outside the panes. The run does not go on by itself. The same pause holds when the model provider refuses every seat, and when you hold the run yourself.
--stop cap-pause is the default. cap-stop is for a run nobody watches: two minutes after the steer the harness writes the stop itself. operator, which --until-solved also means, runs until you stop it, with every cap advisory. A metered team given no token cap gets one by default, as a second brake sized to stop a runaway.
swarm.sh extend <run> adds minutes, tokens or dollars. When the caps then leave room, the pause lifts, the wall clock starts again where it stopped, and every seat is woken once, where it was. swarm.sh stop ends the run with the outcome stopped, which is never read as completed. Time spent paused does not count against the clock.
swarm.sh pause <run> holds a going run under any stop policy: each seat finishes its step and goes idle, no model call goes out and the clock stands. swarm.sh unpause <run>, or Unpause in the console, lifts a pause whose cause is gone and wakes every seat. A pause at a cap lifts only when the caps leave room, which is what extend is for.
When the model provider refuses every seat, as a subscription's usage limit does, the run pauses itself under every stop policy, once every live seat is limited: told to wait half an hour or more, or refused again after a retry. One seat's error never pauses it. The harness tries again at the time the provider named, or every thirty minutes, and tells you once per spell. The wait holds every VM, so the notice says you can stop the run to free the machine and resume it once the limit lifts.
When no new finding, question disposition or coverage record has appeared across twenty committed jobs or thirty minutes, the watchdog proposes a stop to you, as a request with its own id. The proposal stops nothing, and it is never an agent's vote. Another comes only after a further window with nothing new.
Completed, examination limited, paused, stopped, abandoned, or verification unavailable, read from the run's files. The summary, the report and the console say which. A run that ends on limitations, deferrals or accepted questions is examination limited.
The same run continues.
swarm.sh resume <run> --question "…", or Continue this run in the console, continues a run that stopped or was sealed: the same sandbox, the same ledger, registers, board, trace and store, appended to and never rewritten. Each seat starts from its last hand-off note or compaction summary, whole. The questions you give are asked as analyst questions, and the follow-ups asked after the sentinel become the continuation's work. The team restarts with the options it was started with, which the kickoff kept outside the run.
The wall clock counts on from the stop, the caps grow by what you give, and a resume that would still be over a cap is refused before anything moves. A running run is extended; a purged one cannot be resumed.
The resume is your act, on the trace and the operator's record, and anchored beside the run with each chain's length and head before anything is moved. What marked the end is moved whole into the run's history.
The next stop seals the continuation anew: a new custody verdict and a new draft release. Every earlier verdict verifies as a prefix of the same chains, and custody-verify prints each one.
A signed v1 is left untouched and valid for what it bound. The continuation's answers are adopted through a later release, and the case policy the kickoff recorded is kept, with each field the new options would have changed named.
The machine seals the draft at the stop. The examiner who read the report signs its release.
A report leaves a run as a series of releases, each a directory of the run that nothing writes over. The first is the machine's sealed draft. The next is an enrolled examiner's adoption, signed with their own key over the exact bytes they were shown, after a technical reviewer has signed their own review when the run asks for one. Each later release names the one before it and why it was made, and verify walks them all.
release/v0/, release/v1/ and on.-
the machine · at the stopv0 · sealed
The draft
The report is rendered with the DRAFT mark and sealed with the install's machine key, together with custody's verdict and the head of every chain. It adopts nothing.
machine keyswarm.sh stop <id> -
the technical reviewer · optionalsigned by the reviewer
A second person
A reviewer enrolled in a role of their own reads the run, here or from its package, and signs their record of what they checked and the outcome.
sshFIDO2PKCS#11review --technical-review -
the examinersha256 shown
Read, then consent
Each answer is adopted, qualified, withdrawn or rendered inconclusive. The final bytes are shown once with their sha256, and the examiner confirms having read them. The prepared release is used once and lapses after fifteen minutes.
review --adopt · review --sign -
the examinerv1 · signed
The release
The passphrase, the touch or the PIN seals exactly the bytes that were shown, with each disposition, every technical review and custody.
sshFIDO2PKCS#11release/v1/ -
the examiner · latervN · amended
An amendment
A correction is a new release that names the one before it by its sha256 and says why it was made.
review --sign --amend-reason -
whoever receives itverified
Verify
Each release in order: ssh and FIDO2 signatures against the allowed-signers register, an e-signature against the issuer's CA chain, and v0 as "machine seal, self-checked".
releases --verify · verify
The default: an ed25519 key made at enrolment, its passphrase typed twice, or an encrypted key file you give. Enrolment prints the line for the organisation's allowed-signers register, and verify holds the signature to it.
An ed25519-sk key made on the authenticator plugged into this computer. Every signature needs a touch, and its PIN as well when it was enrolled verify-required. The key-handle file alone signs nothing.
The X.509 certificate on the token is read without the PIN and shown by its CN alone. The platform signs with the certificate on the token (CAdES-BES) and verifies it against the issuer's CA chain.
Made once per install, with no passphrase, so a stop can seal the draft unattended. It seals v0 and is checked only as the machine's seal. swarm.sh machine rotate retires it and makes the next one.


The Release tab of a stopped run lists the enrolled examiners whose keys it can use, shows the prepared report in a frame with no scripts beside the sha256 of exactly the bytes the browser holds, and waits for the box "I have read the report and the answers I adopt" before a dialog asks for the passphrase, a touch of the key or the PIN. The Examiners page enrols a person, and the Technical review tab is the reviewer's.
The console signs only with its token and only on loopback. It leaves out a key without a passphrase or held in ssh-agent, signs nothing while a host-mode run is live on the install, and after five wrong secrets locks that person out of console signing for fifteen minutes, on the operator's record.
swarm.sh review <id> --sign prepares the release and prints what will be signed: the sha256 of the report and the release, the counts of adopted and withdrawn answers, the technical reviews and the key. It asks for the confirmation, then for the passphrase or the PIN with echo off, or says when to touch the key. A wrong secret leaves the prepared release for another try, three at the terminal.
Either way the secret reaches ssh-keygen or OpenSSL only down a pipe. It is never in an argument, the environment, a file, a job record or a log.
The technical reviewer's signed record comes first, and the examiner's release binds it. A reviewer with the examiner's id, name or key is refused; the organisation's register is what ties each key to a person. A reviewer on another machine signs from the package, and the examiner imports that file, checked against the register or the CA. --require-technical-review at kickoff makes the release wait for a signed review, and a release without one says "No technical review".
No agent's VM mounts the machine key, an examiner's or reviewer's key, the ssh-agent socket, an earlier run's sandbox or the examiners' reviews. In host mode the kernel guard denies them to every pane, and a host run whose panes could reach a key is refused unless the examiner accepts the exposure. The release binds what the run's agents could reach.
releases --verify, and verify for a package, walk every release: its place in the chain, its signature, whether it was signed from the console or the command line with the consent confirmed, the technical reviews it binds, and the bytes it binds. Exit 0 when every adoption's key is one the register allows or the CA verifies; 3 when there was nothing to check the signer against; 4 when something does not hold.
The hardware tests in the repository sign end to end with a FIDO2 security key and a PKCS#11 e-signature token, run on macOS with a YubiKey 5 and a SafeNet eToken 5110. A release's signature can also be timestamped by an RFC 3161 authority, later when the lab is offline. Signing, in the usage guide →
What may come in, what may leave, and what the run asked of whom.
A case is examined under a contract fixed at kickoff. The case policy says what may leave the run and reach outside it, whether more evidence may arrive while it goes on, and what material from outside the original evidence may be used for. The kickoff refuses a policy that contradicts itself, anchors it beside the run and writes it into the contract the agents read. Custody seals it by its hash and names a rewrite, a release binds it, and a resume keeps it. An agent can weaken none of it.
| preset | what it permits | more evidence during the run |
|---|---|---|
standardthe default | Hashes and public indicators, sent to approved passive reference services. Contact with anything the evidence names, such as a URL's HEAD, is the operator's decision. | The operator decides each ask. |
live_adversary | The same lookups, and nothing the evidence names is ever contacted. No socket grant. | The operator decides each ask. |
internal | Nothing leaves the run, and nothing is captured from outside it. | The operator decides each ask. |
ctf | A published case: no search engine and no write-up site, only reference or evidence-linked lookups, and every value sent must be found in the evidence the request cites. No socket grant. | None: the collection is closed. |
The goal's metadata or the kickoff's flags change one field at a time: --lookups, --contact, --disclosure, --more-evidence no|ask|yes, --material-use, --legal, --provider-retention. The legal text is the operator's own, such as the jurisdiction, the warrant's scope or "GDPR or similar laws", and is recorded as given.
A lead that needs you, evidence the run does not have, a clarification, a network item, a stop proposed when nothing yields: each is a request with its own id, R-n. The record that makes it is committed first, the request is derived from it and written once to a chain custody seals, and a crash in between loses nothing. It moves from pending to notified, acknowledged, and then answered, declined or withdrawn.
The hub hands each new request to your targets: a desktop notification, an ntfy topic, a mail address or a command of yours. The notification carries the request's id and kind and the lead's or question's id. What was asked stays in the run, since it may be case content and a notification leaves the host. swarm.sh requests <run> list and the console's Requests tab show everything open.
An agent asks for evidence the run does not have on the lead that needs it: the source, where it is, what it would establish, how urgent it is. The acquisition moves from requested to authorised or declined, then collecting, received, and validated or unavailable. Under more_evidence: no the hub answers at once "no additional input under this case policy", a constraint of the case that says nothing about whether the source or the fact exists. An answer that a missing source leaves not determinable names its ask, R-n, or says why none would settle it, and the answers check warns when it does neither.
swarm.sh evidence <run> add PATH --for R-n copies it outside the run, holds each copy to its source's hash and to the acquisition hash you give, and seals it in the store as a new inventory revision. It answers the acquisition, reopens the leads under its questions, makes the answers recorded before it stale, and lifts an acceptance made before it; with the catalogue on, each file is detected and catalogued. Its files are searched at once for what every standing coverage record looked for, and each hit goes to the questions it bears on. Every negative, not-determinable and partial answer whose coverage predates it goes stale too, whatever it was added for, until another seat reviews an examination of it against the new evidence that says how the evidence bears on the answer (it supports it, contradicts it, adds a part, is irrelevant or is inconclusive), or you accept the question after the addition. Citing the new evidence alone clears nothing. Each agent's VM reads the run read-only and live, so the addition is readable there at once, and through jobs.
A statement, a policy document or a question's attachment is supplied with swarm.sh material <run> add and sealed the same way, recorded with who supplied it, when, from where and its hash. What the policy permits it for is part of the record, a record citing material the policy says may not be cited is refused, and every answer resting on material from outside the original evidence is named as such by the answers check, the report and the release.
The report's "Evidence gaps and acquisition requests" is generated from the records: every acquisition with its stage and outcome, and each gap told apart as never collected, unavailable, inaccessible, unexamined or inconclusive, with its questions, what it bounds and what would close it. A declined or unavailable acquisition bounds the questions it bore on; it is listed as a gap and never turned into a negative answer.
A lookup is decided by rules and made on the host.
Some questions need what a reference service knows: a domain's registration, a certificate log, a CVE, a hash's reputation, a place. The network mode says how that is decided. closed is the default: the models' hosts, the package index when installs are allowed, and the hosts you allow. open is every public host. In a dynamic run no agent's VM gains a host: an agent asks for one bounded lookup, the hub decides it by rules alone under the case policy and records the decision before it answers, and a fetch service on the host makes exactly that request and nothing else.
- who asksdecided by the channel the request came on
- its shapea strict schema, an adapter's typed values, an open lead the asker holds
- the case policywhich lookups, which contact, which data may leave; a request no adapter covers is uncertain, and left to the operator
- hard denialssearch engines, write-up and paste sites, social platforms, proxies, logins, uploads
- what would leavecredentials and the run's sensitive values, as sent and decoded
- the evidence linkwhat leaves must be found in the evidence the request cites, where the policy asks
- enforceabilitywhat is asked must be something the harness can hold it to
- quotascounted again under the lock that issues the grant
The first step that refuses decides, with reasons a machine can read. The same request gets the same answer. No model sits in the decision, and the purpose a request states is stored and shown and read by no rule, so no words in a request or a response can change a decision. A refusal closes that avenue and leaves the lead open.
A list of what may be asked: RDAP for domains, addresses and AS numbers, crt.sh, the NVD CVE API, CISA's KEV feed, CIRCL hashlookup, RIPEstat, Nominatim, Overpass, a video's title, VirusTotal by hash when you configure a key on the host, and one evidence-linked HEAD of an exact URL, which is active contact. An agent gives typed values, and the adapter places each one; none can become a host or a path.
One exact method and URL, for one seat or one job, for five minutes and one use, with a byte limit, recorded in a hash-chained file with every request and decision. A refusal you may override opens one item per host and lead, and swarm.sh net <run> grant runs the same rules with those reasons waived and recorded. A login, an upload, a credential or a sensitive value stays refused whoever asks.
A host process with no evidence. It sends only its own headers, so no provider key, seat token or cookie leaves with a request. It resolves the name once, refuses loopback, private, link-local and other special addresses, connects to the address it checked with TLS verified against the name, and is no proxy. Each use is written to its log before a byte leaves.
The answer is sealed in the store as a capture, with the request, the headers sent, the address and the TLS identity, and the harness records it on the ledger as external material. A capture's hash proves its bytes. An examiner records what they establish, and every answer resting on a capture is named as such by the answers check, the report and the release.
Where a job's own client needs a connection, a socket grant allows a host and a port, with no control of the method or the path and no capture of the content. It is the operator's alone, needs a policy that permits it, and is refused under ctf, internal and live_adversary. The kickoff's --allow-host is such an allowance for the whole run, and says so.
swarm.sh net <run> list and the console's Network tab show the policy, what waits on you, every grant with its time left, every request with its reasons, every capture and what the fetch service refused. You grant, deny or revoke with a reason, and each act lands on the trace and the operator's record and is posted to whoever asked.
ctf also requires every value sent to be in the evidence.When a guard is only advisory, the report says so. When an agent walked around one, we published the whole account.
On one published run, in host mode on macOS, an agent refused repeatedly by the egress guard unset the proxy variables in its environment and installed the library it needed. The kernel-enforced guards held throughout. The advisory one did not, and could not have, which is what advisory means. The custody section of that run's report prints Egress enforcement: ADVISORY beside the package that came through, and the full account (every attempt, the command, why it worked, what to do about it) sits in the repository beside the run, with the runs where a guard was only advisory in the case ledger.
In a microVM the same guard is the host's network policy for that VM: a name off the list does not resolve and an address off it has no route, and there is no variable to unset.
A harness that hides its own limits cannot be put in front of a court. The record reports what was achieved rather than what was asked for.
The escape, end to end, in the repository →
Isolation, by edition.
The same harness under each, with the record carrying how a run was held.
Each agent in its own microVM on your own machine, on macOS with Apple silicon or Linux with KVM: the evidence a read-only share, its own directories writable, its network an allowlist the host enforces, its keys placeholders. The agents boot a small base image, the forensic programs are in a job image per profile, and tool work runs in a throwaway worker VM per job. Every image is built from the packs in the repository.
Chosen with --isolation host, for a machine that cannot run microVMs: kernel policy on the host, with seatbelt profiles per pane on macOS, and on Linux Landlock inside a user namespace with a network namespace for egress. The record says the run was held this way and which guards the host could enforce.
Private, digest-pinned images for every pack, built and checked for you, holding a toolset whose tools we have checked and ship. The same microVM per agent, on your own machine.
Each case on a host of its own in your lab, with nothing of any other case on it, and its agents each in a microVM there; air-gapped where the evidence demands it, with the case file as the only thing that leaves.
The files were written by the subject of the investigation. They are material to examine, and the harness treats them that way.
A note, a filename, a chat message inside the evidence cannot give an agent a task or a permission. A URL in a chat log is a finding to record; resolving it would tell the subject their device is being examined. The contract says it; each microVM reaches only the hosts the run allows, a worker VM that parses the evidence has no key and no network unless its job asks for the run's allowlist, extracted material lands on no-exec mounts, and the trace shows every time it was tested. No-exec keeps a carved file from executing, and an interpreter can still read it, so a command or a job that runs or evaluates code recovered from the evidence, from an extraction or from a job's output is flagged on the trace and in the report's job record. In a dynamic run, a HEAD of a URL the evidence names is active contact: the operator's decision under the standard policy, refused outright under live_adversary and internal, and granted by the rules under ctf only where the value sent is in the evidence.
Everything on this page is in the source, with the tests that fail without it: the guards, the collector, the verification, the custody section, and a suite that boots real microVMs on every pull request.
Read the harness on GitHub →