Two layers: generated agent settings now set includeCoAuthoredBy: false
(never set anywhere before, so the CLI nudged models into appending
'Co-Authored-By: Claude ...' to commit messages), and the commit verb
strips AI-attribution lines deterministically at the chokepoint every
provider routes through.
The fleet-wide subagent ban was implemented as an allowlist omission, but Task
is a default-permitted Claude Code built-in — an allowlist auto-approves, it
does not restrict. Under permission_mode="dontAsk" (intake/secretary SDK) and
defaultMode="bypassPermissions" (fleet), Task ran regardless and can_use_tool
was never invoked for it, so every Claude-path agent could still spawn
subagents despite allows_subagent=False. Only the grok path blocked it.
Explicitly disallow the subagent tool at every Claude-path spawn point:
disallowed_tools=["Task"] on the intake and secretary SDK drivers, and "Task"
in the fleet settings.json base_deny (an explicit deny applies even under
bypassPermissions). This mirrors the grok path's --disallowed-tools Agent.
Pins the ban in test_cc_lockdown.py (fleet settings deny Task) and a new
test_sdk_driver_subagent_ban.py (intake + secretary options disallow Task).
Co-authored-by: Renn F <rennf93@users.noreply.github.com>
Fleet behaves more like Fable 5 on existing model tiers, behind
ROBOCO_FABLE_MODE_ENABLED (config default off; armed :-true on the NAS compose,
absent from the registry compose).
- Doctrine: vendored agents/prompts/doctrine/fable.md composed into every
agent's system prompt via fable_doctrine_layer() after base.md.
- Hooks (Claude Code): 4 non-overlapping hooks (stop-gate/bash-discipline/
honesty-nudge/precompact) appended per-agent via _fable_hook_groups(). The
make-quality + lint-suppression duplicates are deliberately NOT added (already
gate-enforced); session-start skipped.
- Hooks (grok): conservative V1 — only the non-denying honesty-nudge, since a
grok hook deny cancels the whole run.
- Flag on the feature-flags card; hook scripts shipped into the agent image.
Flag-off spawn path proven byte-identical (worktree diff, sha256 match); full
suite green (2074 unit + e2e-smoke + hook harness), mypy/xenon/ruff clean.
Fixed a real stdin bug in the vendored stop-gate hook (heredoc + pipe both
claimed stdin). Distilled from rennf93/opus-fable-playbook (MIT).
* fix(security): lock down shared Claude Code credential mount + curl|sh RCE
Audit of Claude Code capabilities reachable inside a spawned agent
container turned up two live gaps against the shared harness state:
- Every agent container bind-mounts the host's ~/.claude (OAuth store) and
~/.claude.json read-write (_build_mount_args) — the shared subscription
auth used by the whole fleet. Nothing denied the native Read tool or the
bash-guard hook from reading .credentials.json / .claude.json, so any
role could exfiltrate the harness's own Claude Code auth. Deny both at
the settings.json layer (absolute // form, per the #167 gotcha) and in
the bash-guard hook's credential-exfil checks (cat/grep/source/base64/
interpreter one-liners), mirroring the existing .netrc/.git-credentials
treatment.
- The bash-guard hook only blocked curl/wget to github.com or internal
hosts; `curl <any other host>/install.sh | bash` (or `bash <(curl ...)`,
`eval "$(curl ...)"`) executed untrusted remote code unchecked. New
checks deny piping a fetch into an actual shell (sh/bash/zsh/dash/ksh)
while leaving non-executing consumers (tar, jq, -o file) untouched.
Also add --disable-slash-commands to every container agent spawn: skills
resolve independently of the --tools allowlist, so a contaminated shared
~/.claude could otherwise leak host skills/plugins into an agent session.
No RoboCo role's workflow uses a Claude Code skill.
64 -> 78 shell bash-guard cases, 54 -> 71 pytest bash-guard cases, plus a
new 5-case settings/CLI test module. ruff/mypy/xenon B clean.
* docs: changelog for the CC capability lockdown
---------
Co-authored-by: Renn F <rennf93@users.noreply.github.com>