# samples/

Catalog of malware (or behaviour-matched mimics) the fleet draws from.
**Sample binaries are NEVER committed to this repo.**

## What's here

```
manifest.toml      schema-checked catalog (loaded by samples/manifest.py)
manifest.py        loader + per-(host_id, slot, ep) deterministic selection
store/             SHA-256-pinned binary content (gitignored — never commit)
.bazaar.token      MalwareBazaar API key (mode 0600, gitignored)
```

## Manifest schema

Each entry in `manifest.toml`:

```toml
[[sample]]
name        = "xmrig-cryptominer"   # unique within manifest, DNS-safe
family      = "XMRig"               # canonical family label for ML
category    = "cryptominer"         # one of: cryptominer, botnet, ransomware,
                                    # banking-trojan, fileless, rat, worm,
                                    # loader, wiper, other
profile     = "cpu-saturate"        # behaviour profile from
                                    # exploits/workloads.py — gates the
                                    # in-session shell workload when no
                                    # real binary is staged
description = "..."

# Optional — present iff this is a real binary the fetcher should pull:
sha256 = "abc123..."
source = "MalwareBazaar"
url    = "https://bazaar.abuse.ch/sample/abc123/"
```

The loader rejects unknown categories and duplicate names. See
`tests/test_fleet.py` for the property tests covering selection
distribution + catalog walkability.

## "real" vs "mimic"

`Sample.kind` is **`"real"`** when `sha256` is set, otherwise **`"mimic"`**.

- **Mimic** — the orchestrator runs the matching profile-shaped shell
  command (cpu-saturate / scan-and-dial / io-walk / bursty-c2 /
  low-and-slow / shell-resident) inside the guest. No real binary
  needed; useful right now for testing the dataset pipeline and as
  the realistic-but-safe envelope class the trainer expects.
- **Real** — the orchestrator's Tier-3+ driver chunked-uploads
  `samples/store/<sha256>` into the shell session, sha256-verifies on
  the guest side, and execs it. Hash mismatch fail-stops the run; a
  tampered binary is never executed.

`meta.sample.kind` lands in every episode's `meta.json`, so trainers
can stratify on it (the realistic-model path consumes only
`kind == "real"` episodes by default).

## Fetching a real binary

```sh
# 1. Register a (free) account at https://bazaar.abuse.ch and get the API key.
echo "<your-key>" > samples/.bazaar.token
chmod 0600 samples/.bazaar.token

# 2. Add an entry with sha256+source+url to manifest.toml.

# 3. Pull the binary into samples/store/<sha256>:
uv run python tools/fetch_sample.py <sha256>
```

Idempotent — re-running checks the staged copy's sha256 and skips the
download if it already matches.

## Per-(host, slot, episode) selection

`manifest.py::SampleManifest.select(host_id, slot, episode_index)`
hashes those three into a uniform integer and indexes the catalog.
Two lab hosts on the same slot pick *different* samples (collision
rate ~1/N). A single host walks the whole catalog within ~`len(manifest)`
episodes. No coordinator.

## Safety rules

- **Only download to a lab host, never to a developer workstation.**
  `samples/store/` lives only there, gitignored, on a disk that is
  not auto-mounted elsewhere.
- The lab host's `br-malware` bridge is host-only by design (no NAT,
  no route). Real malware running in the guest cannot call out unless
  the operator explicitly opens egress, which we don't.
- Snapshot/revert (see `EpisodeConfig.revert_at_*` + `qmp.savevm`/
  `loadvm`) means every fresh episode starts from a known-good
  baseline regardless of what the previous one did to the guest.
- The fetcher verifies sha256 on download; the driver verifies again
  in-guest before exec. Both layers must match the manifest.

## Adding a sample

1. Pick a `family` + `category` from the closed enum above.
2. Pick a `profile` from `exploits/workloads.all_profiles()`. If the
   sample's behaviour doesn't match any of the six existing shapes,
   add a new factory to `exploits/workloads.py` *first*, with tests.
3. (Real-only) Compute `sha256`, fetch via `tools/fetch_sample.py`,
   verify the staged file's hash matches.
4. Append the entry to `manifest.toml`.
5. Run the test suite — the manifest loader's invariants catch typos.
