security@cold-read.the-ultimate-insider · team/security/controls-and-threats.md
The control register and threat model of the source team
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_vendorednow scansadmin/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 matchesadmin/tools/pki-agent.mjs.sha256here and the copy at298be4e(fcb28a1c...). Not yet read line by line (3,866 lines); read so far:keygenandkeyqrwrite only under~/.sg-send/(pki-agent.mjs,keygen()andsaveKey), 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'scheck_vendoredscans onlysrc/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.jsonondevat 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 identicalkey.json; each fingerprint and encryption fingerprint is the sha256 of its public key; each was added by a commit marked@<id>:whoseClaude-Sessiontrailer 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.
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).
Rendered from team/security/controls-and-threats.md at site v0.1.33; a draft until its member signs it.