Skip to content

0.18.0 — Audit r5 hardening

Latest

Choose a tag to compare

@askwigconsulting askwigconsulting released this 30 Sep 15:23
· 1 commit to master since this release
a86f5e1

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-run honoured by every command or refused
  • /consult-gpt is code, not prose. ChatGPT consults go through cohort 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 ~/.grok login.
  • Local records never reach an engine. .cohort/sessions, feedback, proposals and state (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 bwrap for the jailed path.

Changelog

Changed

  • The grok CLI doer is now xAI's Grok Build. The community grok-cli Cohort used to
    drive is unmaintained and its API path was retired (every dispatch returned a 410), and
    Grok Build's installer takes over the grok command — 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 either XAI_API_KEY (what Grok Build
    reads) or GROK_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 from GATED_KINDS; --layer and
    --to are accepted as aliases on the add-* and edit commands (#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, proposals and state (git author name and email, private
    notes) are excluded at the single worktree creation point, so one-shot and agentic
    propose and 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_by key 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 own lstat is 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 --reset rebuild a corrupt pending store fail-closed from disk (#294); the office baseline
    now folds at approve time, and --reset --all withholds everything regardless of a legacy
    baseline. The remedy text no longer says "delete the file".
  • ratchet confines 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; _git has a timeout, non-interactive git
    hardening and commit.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-gpt is gated in code (#266). OpenAI Codex is a registered engine
    (gpt/chatgpt/openai aliases; xai for grok) and cohort engine consult gpt runs the
    egress marker, size cap and secret scan on the assembled prompt before codex 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 runs codex exec itself. engine review checks the review role, 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, proposals or state (#275).
  • Secret scanner catches escaped JSON, YAML block scalars, bracketed and prefixed literals,
    :=, lowercase bearer, AWS ASIA… and xai-… 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 SECONDS exposes 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.cli no longer loads urllib.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-run is honoured by every command or refused with exit 2 (#267); status
    exits 1 when it printed a ! diagnostic and --json carries ok; my-office sync names a
    real remedy and exits non-zero on a failed recompile; /api/state lists 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 tools name is E021 (the vocabulary is declared
    in canonical/schema/agent.json); _SHARED_KEYS derives 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; gc keeps the newest transcripts by number and has a scan budget;
    report says an issue may already exist on a timeout (#299, #268, #269).
  • Docs: AGENTS.md documents the comma form of --ide (repeats keep only the last);
    staleness-check always 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 lists report/update-check egress (#296, #271, #279, #280).
  • Dashboard: every dialog control has a label, --faint meets AA inside cards, action buttons
    carry aria-label, the three homes are OFFICE / MY OFFICE / PROJECT; --help uses plain
    formatting under NO_COLOR or TERM=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 backticked PASSWORD=...
    were consumed as the label's value and skipped over. Every egress path that relies on the
    rule was affected: cohort report bodies, 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 embedded KEY=value is 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