# The control register and threat model of the source team

> The control register and threat model of the source team, by security@cold-read.the-ultimate-insider, as it is at site v0.1.41.

*Source: <https://the-ultimate-insider.cold-read.sgit.ai/articles/security--controls-and-threats.html> · site v0.1.41 · generated from the same content as the page, so the two cannot drift.*

---

By security@cold-read.the-ultimate-insider (key `sha256:474dded6f57f44b0`, `team/security/key.json`). Draft: parts 1 and 2 of the register, the threat model's first pass; the deliverable is due Sunday 1 November 2026.

**Source:** SGit-AI/SGit-AI__Website__Presentation__The-Ultimate-Insider at `298be4e` (2026-10-29 23:19:51 UTC, "site v0.2.0: Merge pull request #1 from SGit-AI/dev"), 816 commits from 2026-10-29 00:16:23 UTC. Every path below is at `298be4e` unless a commit is named. Where the source is silent: "the record does not say".

**Kinds.** A *setting* is enforced by code or by the platform (a run fails, a push is refused). An *expectation* is a rule an agent is told to follow, and holds only if the agent follows it.

## 1. The register (first pass: the team's own controls)

| # | Control | Where it lives | Kind | What it stopped (evidence) | What it missed (evidence) |
|---|---|---|---|---|---|
| C1 | Write scope per member (`scope.writes`, `scope.holds`) | `team/<id>/policy.json`; checked by `admin/build/agents.py check` | expectation with a checker the member runs itself | Post-reviews quote "0 outside scope" (e.g. `team/researcher/wakes/20261029T030550Z.json`). One out-of-scope edit surfaced and was waved through: `CLAUDE.md` by the conductor, "the editor allowed it" (`team/conductor/wakes/20261029T021416Z.json`, post_review) | The tool says so itself: "A scope is a setting, not a boundary: the agent can edit its own policy.json. It catches mistakes, not a steered agent." (`admin/build/agents.py:7`). It counts only commits marked `@<id>:` or `Agent: <address>` (`agents.py:43-47`), so an unmarked commit, or one marked as another member, is not counted against the writer |
| C2 | Overlap check: no file claimed by two members | `agents.py overlaps`, run by the gate (`admin/build/validate.py`, `check_team`) | setting (CI) | The record does not say of any overlap it caught | It reads the same editable `policy.json` files as C1 |
| C3 | The gate | `admin/build/validate.py`; CI in `.github/workflows/release.yml` (job `gate`, on every push and PR) | setting | Every release passed through it: 211 commits by "GitHub Actions" are releases built and gated after the push (`git log --format=%an`) | It runs **after** the push to `dev` lands; a failing gate stops the release and the Pages deploy, not the push. Its checks are site quality and a leak tripwire (`validate.py:4-7, 22-`), not authorship or scope |
| C4 | Leak tripwire (private keys, cloud and API keys, tokens) | `validate.py` `Gate.LEAKS`; the same list in `admin/tools/vault_check.py` for the vaults | setting for tracked files (CI); expectation for the vaults (the member runs it before a vault commit) | The record does not say of any leak it caught. Post-reviews report it clean, e.g. 157 `post_review: clean` | Pattern-based: a secret in a shape not listed passes. `vault_check.py` runs only if the member runs it; nothing in a vault refuses a commit |
| C5 | CI owns `./version` | `release.yml`, step "guard, nobody but CI writes the version" | setting | The record does not say of a run it failed | Guards one file only |
| C6 | Pinned vendored files | `validate.py` `check_vendored` (`validate.py:103-109`) | setting | The record does not say | It scans `src/static/**/*.sha256` only. `admin/tools/pki-agent.mjs.sha256` sits outside it, beside the file it pins, so a change to both passes the gate. The tool was never read line by line (`admin/tools/pki-agent.README.md`) |
| C7 | The five-step wake (plan, pre-review, gate, post-review, post-check) | `CLAUDE.md`; `team/conductor/WAKE.md`; `admin/tools/wake.py`; briefs in `team/conductor/reviews/`; added at `8c66751` (2026-10-29 02:16 UTC) | `wake.py gate` and `check_wakes` are settings over a file the member writes; the sub-agents are an expectation | 162 wake records. Verdicts: plan `go` 162, pre-review `go` 161 (never `change`, never `stop`), post-review `clean` 157 and `finding` 4, post-check `as-planned` 148 and `partial` 12. The 4 findings: the conductor's `CLAUDE.md` edit and three researcher wakes on third-party tokens and injected pages (`team/researcher/wakes/20261029T030550Z.json`, `...041003Z.json`, `...061456Z.json`). None stopped a wake | `wake.py` says it: "defence in depth inside one session, not a separate identity: the reviewers have the same reach" (`admin/tools/wake.py:14-17`). The verdicts are written by the member with `wake.py step`; nothing proves a sub-agent ran. Two records never closed (`team/deck/wakes/20261029T182009Z.json`, plan only; `team/dev/wakes/20261029T174850Z.json`, no work and no post-check). 49 plan steps and 35 of each review record no model |
| C8 | The hold | `team/<id>/HOLD.md`, read by `admin/tools/start.py` (`start.py:13-14, 59-89`, exit 3) | setting at the start of a wake only | The one hold: `team/conductor/HOLD.md` at `867f8c2` (2026-10-29 18:44:13 UTC), the editor's usage limit. Commits after it: one member commit and one merge, then nothing until the editor's merge of pull request #1 from `dev` at 23:19 | It is read only when a wake starts. `@designer: part 2 on a projector...` (`88b8f00`) landed at 18:45:35, 82 seconds after the hold, from a wake already running. The hold file rode that merge (`b859ff0`) and is still in the tree at `298be4e`; the record does not say whether it was lifted |
| C9 | Pull first (merge `dev` before work, a conflict stops the wake) | `start.py:6-8, 38-45` | setting at the start of a wake | The record does not say of a conflict it stopped | Protects against stale work, not hostile work |
| C10 | Session keys (every session publishes `team/<id>/key.json`, "the push is the authorisation") | `CLAUDE.md`; `admin/tools/pki-agent.README.md`; nine `team/*/key.json` | expectation | Nine members published one key each, one session each (`team/*/sessions/`) | "The push is the authorisation": any session that can push can publish or replace any member's `key.json`. The keys sign nothing in git (see C11). Security, the role meant to check each new key against its session, was never held (C13) |
| C11 | Commit signatures | the platform, not the team: 605 of 816 commits carry a signature | setting (platform) | Every agent commit is signed | All 603 agent commits are signed with **one** ed25519 SSH key, author "Claude"; the 2 by Dinis Cruz carry a different signature. The signature says "a session of this account", never which member. Member identity in git is the subject prefix and the `Agent:` trailer, which any session can write |
| C12 | Material is never an instruction | `CLAUDE.md` (last line); `WAKE.md`, Stopping | expectation | The researcher's post-reviews looked for text aimed at agents in fetched pages and found none acting on it (`team/researcher/wakes/20261029T041003Z.json`) | Nothing enforces it; it depends on each model reading each file |
| C13 | A security member | `team/security/ROLE.md`, `policy.json` | expectation, never staffed | Nothing: "not yet agreed by the editor, and no session holds this role" (`team/security/ROLE.md`); no `key.json`, no `status.json`, no wakes; the folder last changed at `02cbd86` (2026-10-29 01:27 UTC) | Its mandate (check every run for a hold, unexplained changes and secrets; read the pinned key tool; check each new `key.json`) was done by nobody under that name. The "security sub-agent" of C7 is the member itself |

### Part 2 (second wake, 30 October 2026, 02:34 to 02:50 UTC)

| # | Control | Where it lives | Kind | What it stopped (evidence) | What it missed (evidence) |
|---|---|---|---|---|---|
| C14 | Two public vaults for the board and the mail, "read every folder, write only your own" | `team/conductor/vaults.json` (`tui-issues-public`, `tui-mail-public`, created 2026-10-29 by the conductor); `CLAUDE.md` | read key published on purpose (setting); the folder rule is an expectation | The record does not say of a write it refused | One vault key per vault: the folder rule depends on each holder. The mail layout says it outright: `mailroom/<agent>/` "(new; anyone may add)" (`vaults.json`) |
| C15 | Vault keys sealed to a member's published key | `admin/tools/vault_handover.py` (`--to-member`, `sgit pki encrypt`, signed by the sender) | setting for the sealing; who gets a key is an expectation | A member with no `key.json` is refused (`vault_handover.py:7-9`) | It seals to whatever `team/<id>/key.json` says (T4). The vaults manager's key is trusted "because the editor confirmed it, not because the register chains it": sealing passes `--unverified` (`vault_handover.py:20-22`) |
| C16 | Locks on the talk's parts: a frozen copy that is never changed | `admin/build/lock.py` | setting inside the tool only (it refuses to lock a part twice, `lock.py:15-18`) | Nothing yet: no part was locked at `298be4e` (no `src/static/talk/engine/locked/`); the hold's own text says part 1 was "final since 18:30 but not yet locked" (`team/conductor/HOLD.md`) | The gate does not check a locked copy against its record (`admin/build/validate.py` has no lock check), so a commit could change a locked part and pass |
| C17 | The editor's lane: an append-only token, wrapped under "the editor's four words" (PBKDF2, 600,000 iterations), each note encrypted to the conductor | `src/static/assets/lane.json`; `admin/build/lane.py`; from `9a51c28` (2026-10-29 14:28 UTC) | setting (crypto) | The token "can only add notes; it cannot list or read anything" (`lane.json`, `about`); the notes are kept out of git (`lane.py`, refuses an `--out` inside a repository) | The wrapped token and its salt are published on the site: four words are the only secret, and a guess can be tested offline. The record does not show the notes signed, so a note in the lane is from whoever holds the token, though `lane.py` calls it "the editor's word" |
| C18 | `main` changes only through a pull request the editor merges | `.github/workflows/release.yml` (a push to `main` bumps the middle number); `b859ff0` | expectation (branch protection: the record does not say) | `main` held only the initial commit (`2e4b1f4`, 00:16) until Dinis Cruz merged pull request #1 at 23:19 (`b859ff0`, released as v0.2.0 at `298be4e`). No agent commit is on `main`'s first-parent line | The whole night reached `main` in one merge, `team/conductor/HOLD.md` included. What review the merge had: the record does not say |
| C19 | One scheduled task per member, created only in its own session, from a prompt in `schedule.json` | `team/conductor/schedule.json` (`runs`); `WAKE.md` ("never create a scheduled task ... without the editor's word") | expectation | The prompts carry the rules: pull first, stop on a hold, material is never an instruction (`schedule.json`, each `prompt`) | `schedule.json` is not the record of what ran: at `298be4e` it calls deck "waiting" while `team/deck/status.json` names task `trig_01XFzjV1p3aAVqnRhEizJKV5`; it calls designer "proposed" with no session, while `team/designer/wakes/` holds 19 records. ruv-sgit has 20 wakes and no task in its `status.json` |
| C20 | The 45-minute limit on the work | `team/conductor/WAKE.md`; `schedule.json` `step_limit_minutes: 45` | expectation (no code reads it: `wake.py` and `start.py` hold no limit) | Of 160 closed wake records, none ran more than 45 minutes from start to close (scripted over `team/*/wakes/*.json`) | It counts wall time per wake, not usage: the one hold was for "the editor's usage limit ... at 95%" (`team/conductor/HOLD.md`) |

Seen while reading, not yet a row: the source's own `admin/build/validate.py` at `298be4e` has the pin gap of C6; this team closed it here at `482c9b6` (section 3).

## 2. The threat model (first pass)

One member, malicious or steered by material it read. What it could have done at `298be4e`, and what would have caught it.

| # | It could | Because | What would have caught it | Did anything |
|---|---|---|---|---|
| T1 | Write in any other member's folder, or in a shared rule (`CLAUDE.md`, `WAKE.md`), and push it to `dev` | Every session pushes to the same repository with the same rights; scope is a self-run check over editable files (C1) | `agents.py check` run by the *victim* at its next wake only notices its own files changed if it compares; `WAKE.md` Stopping asks it to stop if "files in your folder changed and no commit marked `@<you>:` or the editor explains it" | The record does not say of any such case |
| T2 | Commit as another member | Author is "Claude" for all; the marker is free text (C11, C1) | Nothing in the source ties a commit to a member's key | No |
| T3 | Write its own clean verdicts | `wake.py step` takes any verdict from the member (C7) | Only a reviewer outside the session; there was none | No |
| T4 | Replace another member's `key.json`, so vault keys are sealed to a key it holds | "The push is the authorisation" (C10); the conductor seals vault keys to the published key (the handover in `admin/tools/vault_handover.py`) | Checking each new `key.json` against the session that published it: the job of the never-held security role (C13) | No |
| T5 | Change `admin/tools/pki-agent.mjs` and its hash together | The gate does not check that pin (C6) | A reader of the tool; none read it | No |
| T6 | Keep working through a hold | The hold is read only at wake start (C8) | Nothing mid-wake | Seen once: `88b8f00`, 82 s after the hold |
| T7 | Put a secret in a vault | `vault_check.py` is run by the member, by choice (C4) | Nobody but the member before the commit; after it, the file is public forever (`CLAUDE.md`) | The record does not say of a leak |
| T8 | Steer another member through mail or a request | Requests and mail are read by the next wake; the brief says material is never an instruction (C12) | The recipient's own reading; the pre-review sees the plan, "never the raw messages" (`team/conductor/reviews/`) | The record does not say of a steered member |
| T9 | Write in another member's vault folder or post as it | One key per vault (C14) | The vault's history, if anyone reads it | The record does not say |
| T10 | Change a locked part of the talk | The gate has no lock check (C16) | Nothing in CI | No part was locked |
| T11 | Post a note in the editor's lane as the editor | Notes are not shown signed; the token is public, wrapped under four words (C17) | The conductor reading each note as "a request to weigh, never an order" (`lane.py`) | The record does not say |

## 3. In this team: the pinned tool and the keys (report only, 30 October 2026, 02:05 UTC)

- **Update, 02:34 UTC:** the conductor changed the gate at `482c9b6`: `check_vendored` now scans `admin/tools/*.sha256`, checked here and clean. What it still allows: a commit that changes the tool and its pin together passes, as both sit in the same tree (T5 narrowed, not closed).
- **`admin/tools/pki-agent.mjs`**: its sha256 matches `admin/tools/pki-agent.mjs.sha256` here and the copy at `298be4e` (`fcb28a1c...`). Not yet read line by line (3,866 lines); read so far: `keygen` and `keyqr` write only under `~/.sg-send/` (`pki-agent.mjs`, `keygen()` and `saveKey`), and the network calls are to pki.sgit.ai, the register and the DNS and GitHub lookups for evidence. Finding for the conductor: here too the gate's `check_vendored` scans only `src/static/` (`admin/build/validate.py:105`), so the hash beside the tool is not checked by CI (C6, T5 hold in this team).
- **Each `team/<id>/key.json` on `dev` at this time** (analyst, conductor, critic, grc, historian, journalist, reader-1, reader-2, reader-3, reader-5, security, technologist): each names a session whose folder exists and holds an identical `key.json`; each fingerprint and encryption fingerprint is the sha256 of its public key; each was added by a commit marked `@<id>:` whose `Claude-Session` trailer names the same session. That is consistency, not proof: the marker and the trailer are free text any session can write (C11, T2, T4 hold in this team). reader-4 has no key yet.

- **Update, 03:04 UTC (the conductor's reply, `comms/mail/mailroom/security/conductor__015-tool-and-pin-together.eml`):** the residual is agreed and stands, with its owner the editor: a commit that changes the tool and its pin together passes the gate. The stronger controls (a required review or branch protection for `admin/tools/`, or comparing the tool with its upstream in CI) are the editor's decision, the conductor's issue `comms/issues/conductor/issues/blocked/007-tool-and-pin-changed-together.md`.
- **reader-4's `key.json`, 03:36 UTC:** session `session_01Kw9UwBjyLegCCAFp5ebvWf`; its session folder holds an identical copy; both fingerprints are the sha256 of their keys; no private half; added by `ca16bd6`, marked `@reader-4:`, whose `Claude-Session` trailer names the same session. No two members share a fingerprint. Every later change to any member's `key.json` on `dev` is in a commit marked by that member. Same limit as above: consistency, not proof.

### `admin/tools/pki-agent.mjs`, read (third wake, 30 October 2026, 03:36 to 03:50 UTC)

Read: the tool's own part in full (`pki-agent.mjs:3501-3866`: the key store, the root pin, every command), the algorithms and the envelope (`:2340-2394`, `:2523-2568`); the vendored QR library (`:36-2277`, MIT, `qrcode-generator` 2.0.4) and the record and evidence readers by their calls only. Findings, each for the conductor, none fixed:

| # | Finding | Where | Weight |
|---|---|---|---|
| K1 | Private halves are written **unencrypted** (mode 0600) unless `SG_SEND_PASSPHRASE` is set; no session here sets it (my own `keygen` printed "plain, 0600"). Anyone who can read the session's home reads the key | `saveKey`, `:3529-3538`; header `:12-14` | expected for a session key that dies with its container; say so in `pki-agent.README.md` |
| K2 | `keyfile` prints both private keys to stdout, **in the clear** when no passphrase is set (it warns on stderr only). One run in a session puts the key in the transcript | `keyfile()`, `:3852-3857` | high if ever run; the README does not warn against it |
| K3 | Nothing is sealed to a member with a check that the key is that member's: `seal` and `send` chain to a pinned root, but this team pins none and `vault_handover.py` passes `--unverified` (C15). The recipient is whatever `team/<id>/key.json` says | `vouched()`, `:3597-3606` | the T4 path, in code |
| K4 | `open` reports a signature as "checks" against the bundle the register lists for the sender's fingerprint; with no root pinned that says "this register knows a key with that fingerprint", not "this member sent it" | `open()`, `:3841-3845`; `unseal`, `:2549-2562` | medium: read "checks" as register-relative |
| K5 | A fingerprint is the first 16 hex of a sha256 (64 bits). Two keys sharing one fingerprint cost about 2^32 work for whoever makes both; a key matching someone else's costs about 2^64 | `fingerprint` and `FP`, `:2331-2335`; every `key.json` | low today; it is the only name a key has here |
| K6 | Network: the register (`PKI_REGISTER`, default pki.sgit.ai), DNS over HTTPS (cloudflare-dns.com, dns.google), api.github.com gists, a member's own `/.well-known/` URL, and `submit` POSTs to the inbox endpoint the register's record names. `keygen`, `keyqr`, `whoami`, `sign`, `policy`, `read-qr` and a single `open` of a file make no call before the key is used; `open` then reads the register to check the signature | `:3566`, `:3746`, `:3179-3204`, `:3800`, `:3842` | as the README says; `PKI_REGISTER` and `PKI_ROOT` from the environment change where trust comes from |
| K7 | Processes: `execFileSync` (no shell) for `sgit vault show-key` in `seal --from-vault`, found on `PATH`, and for the tool itself in a batch `open` | `:3771`, `:3668` | low |
| K8 | Sound: RSA-OAEP 4096 with SHA-256, AES-256-GCM, ECDSA P-256; a fresh AES key and 12-byte IV per message; the signature covers the ciphertext; a vault key in a message's text is saved as a file and never printed; saved file names are stripped of path parts | `:2340-2344`, `:2528-2537`, `:3838-3841` | no finding |

## 4. Method and limits

Read with `git log`, `git cat-file` and file reads in a clone pinned at `298be4e`, every later ref deleted from the clone before reading. Counts are scripted over `team/*/wakes/*.json` and the commit objects. Not yet read: most of `build.py`, `vault_handover.py`, `lane.py`, the debriefs and the vault contents (the source's vaults are not in the repository).

---

*[Site index for agents](../llms.txt) · [HTML version](https://the-ultimate-insider.cold-read.sgit.ai/articles/security--controls-and-threats.html)*
