Repository navigation
Cohort 0.18.0 — Audit r5 hardening
Installing
Clone this repo, then ./installer/bootstrap.sh --ide claude.
Or hand an AI agent AGENTS.md and ask it to install Cohort for you.
Do not
pip install cohort. That name on PyPI belongs to a different project by different authors, an unrelated multi-agent orchestration tool. Installing it will look like it worked but won't install this software. There is no PyPI release of Cohort, by design; cloning this repository is the only supported path.
Highlights
- Audit r5 remediated. A 15-worker crew closed the r5 findings and the actionable r4 leftovers (#305), and the residuals followed in #308:
- my-office quarantine hardened against symlinked artifacts
- ratchet evaluator jailed
- dashboard token kept to the URL fragment, never sent to the server
- staging swapped in under a lock
- global
--dry-runhonoured by every command or refused
/consult-gptis code, not prose. ChatGPT consults go throughcohort engine consult gpt: the egress, secret and size gates run first, then codex runs inside a bubblewrap jail that can see only the prompt.- The grok doer is xAI's Grok Build (#307). The old community grok-cli's API path was retired. Grok now runs again under Cohort's jail, which no longer mounts your saved
~/.groklogin. - Local records never reach an engine.
.cohort/sessions,feedback,proposalsandstate(git author identity, private notes) are excluded from every engine worktree and refused by the agentic read tools (#308).
Known limitations
- No PyPI package. Deliberate; see the warning above.
- codex reads outside the prompt when bubblewrap is absent. The consult says so before it starts. Install
bwrapfor the jailed path.
Changelog
Changed
- The grok CLI doer is now xAI's Grok Build. The community
grok-cliCohort used to
drive is unmaintained and its API path was retired (every dispatch returned a 410), and
Grok Build's installer takes over thegrokcommand — so Cohort was building argv the
new binary rejects outright. It now speaks Grok Build's flags (--cwd,--max-turns,
--permission-mode bypassPermissions,--disable-web-search), invokes the resolved
binary rather than a PATH lookup, and accepts eitherXAI_API_KEY(what Grok Build
reads) orGROK_API_KEY(what Cohort's registry declares). Confinement is unchanged and
still Cohort's bubblewrap jail, not the vendor's own sandbox. - Compiled project doers are labelled "(project doer — write-capable)" instead of "advisory
office agent"; my-office help derives the gated kinds fromGATED_KINDS;--layerand
--toare accepted as aliases on the add-* andeditcommands (#300). - Dashboard aggregate cache TTL is 30 s (above the poll), session/feedback feeds parse only the
newest records, parity parses canonical once per poll, and the YAML loader uses libyaml when
available (#295). - CI: bubblewrap can sandbox on ubuntu-24.04 (user namespaces unlocked), the confinement tests
fail rather than skip there, and the real-venv bootstrap round-trip runs on Linux (#277).
Security
- The grok jail no longer mounts the user's real
~/.grok. Grok Build stores its saved
login there, so the previous read-only bind exposed that credential to the engine — and
still broke it, since it writes session state to the same directory. The ephemeral HOME
now gives the engine its own, and the API key reaches it through the scrubbed environment. - No engine path is handed Cohort's local records any more (#306, completing #275).
.cohort/sessions,feedback,proposalsandstate(git author name and email, private
notes) are excluded at the single worktree creation point, so one-shot and agentic
proposeand the ratchet get the same exclusion the CLI doers had; a worktree whose records
could not be excluded is discarded. The agentic read tools refuse those paths by spelling
(case-folded) and by symlink target, and hide them from listings, grep and find. - A quoted
signed_bykey is no longer read as absent (#306).'signed_by' = [...]is
valid TOML; the array reader now unquotes keys like the scalar reader (#276), so signature
pinning no longer silently fails open. - A symlinked artifact no longer walks through the my-office quarantine (#285). Discovery
(compile and quarantine alike) reads only<canonical>/<kind-dir>/*.md, refuses an entry
whose ownlstatis a symlink, never descends a linked directory, and a pull that touches a
symlink falls back to a whole-tree record.my-office review --reset/office review --resetrebuild a corrupt pending store fail-closed from disk (#294); the office baseline
now folds at approve time, and--reset --allwithholds everything regardless of a legacy
baseline. The remedy text no longer says "delete the file". ratchetconfines the evaluator (#286, #237). The evaluator executes engine-written
code, so it now runs under the scrubbed environment with an ephemeral HOME and, where
bubblewrap is present, inside a no-network jail;_githas a timeout, non-interactive git
hardening andcommit.gpgsign=false; the evaluator is killed as a process group; the codex
path applies the wire cap and worktree secret scan; the egress opt-out is read from the repo./consult-gptis gated in code (#266). OpenAI Codex is a registered engine
(gpt/chatgpt/openaialiases;xaifor grok) andcohort engine consult gptruns the
egress marker, size cap and secret scan on the assembled prompt beforecodex exec --sandbox read-only, jailed under bubblewrap to an empty scratch root and an ephemeral HOME
holding only codex's auth; without bwrap it says so. The command file no longer runscodex execitself.engine reviewchecks thereviewrole, so a consult-only engine cannot be
routed to the xAI review transport.- Doer preflight refuses tracked symlinks (#288) before reading anything; the doer
worktree no longer ships.cohort/sessions,feedback,proposalsorstate(#275). - Secret scanner catches escaped JSON, YAML block scalars, bracketed and prefixed literals,
:=, lowercasebearer, AWSASIA…andxai-…keys (#289). - The dashboard token is no longer served to any loopback client (#293): it travels only
in the URL fragment the CLI prints and opens. - Executor refuses a destination whose parent escapes the declared root via a symlink
(#276);[update]honours quoted TOML keys.
Fixed
cohort gc --scan-deadline SECONDSexposes the scan budget (finite, > 0) and reports how
many candidates went unexamined, in text and--json; a timed-out scan no longer prints
"Nothing to reclaim" (#306).- The dashboard says when the office feed is partial: "⚠ N projects unreadable", with the
names on hover (#306). - Engine worktrees tolerate non-UTF-8 tracked file names instead of crashing the dispatch.
import cohort.clino longer loadsurllib.request; the xAI transport imports it on
first use, trimming every hook invocation (#306).- The test suite no longer leaks
cohort-proposal-*worktrees into the system temp dir;
pytest keeps only failed runs' temp dirs (#306). - Global
--dry-runis honoured by every command or refused with exit 2 (#267);status
exits 1 when it printed a!diagnostic and--jsoncarriesok;my-office syncnames a
real remedy and exits non-zero on a failed recompile;/api/statelists skipped projects (#270). - Staging is swapped in under the manifest lock (#291); roster and adopt append under the
lock (#292); the rollback ledger is locked and atomic; quarantine reconcile scans under the
lock (#290). - Duplicate YAML keys are an E001; an unknown
toolsname is E021 (the vocabulary is declared
incanonical/schema/agent.json);_SHARED_KEYSderives from the schema; the frontmatter
delimiter is only recognised at column 0;docs/quick-reference.*shows each command's own
description (#297, #299). - Unquoted YAML timestamps no longer drop records or 500 the dashboard; quoted
auto_capture
is read correctly;gckeeps the newest transcripts by number and has a scan budget;
reportsays an issue may already exist on a timeout (#299, #268, #269). - Docs:
AGENTS.mddocuments the comma form of--ide(repeats keep only the last);
staleness-checkalways exits 0; the orchestration-cap lint scans README and docs; RFC
0001's prerequisite is marked resolved; the autonomy memory states the machine-local truth;
README qualifies "no phone-home" and listsreport/update-checkegress (#296, #271, #279, #280). - Dashboard: every dialog control has a label,
--faintmeets AA inside cards, action buttons
carryaria-label, the three homes are OFFICE / MY OFFICE / PROJECT;--helpuses plain
formatting underNO_COLORorTERM=dumb(#298, #300). - Secret scanner: quoted keys and label-glued assignments were exempt (#265). The
generic-assignment rule required the separator to follow the identifier directly, so
{"password": "..."}(JSON, the shape a config dump or API error body takes) and quoted
YAML/TOML keys never matched. And because a harmless label's value token runs to the next
whitespace,WARN: DB_PASSWORD=...,x=1;PASSWORD=...and a backtickedPASSWORD=...
were consumed as the label's value and skipped over. Every egress path that relies on the
rule was affected:cohort reportbodies, the CLI-doer tracked-file scan, the API toolbox
per-read scan, and/consult-*prompts. The identifier may now be closed by a quote, and
a consumed value is rescanned from its start so an embeddedKEY=valueis a candidate in
its own right. Two escaped-source fixtures the old scanner missed in this repo's own tests
are now declared in the suppression manifest.
Full diff: v0.17.0...v0.18.0