v3.1.0
Added
trust/untrustCLI for the global workspace allowlist — from a repo root,
python -m rutherford trustregisters (oruntrustremoves) the current directory in the platform
globaltrusted_workspacesallowlist, sowrite/yolodelegations pass the trusted-workspace gate
without a per-calltrust_workspace=true. Takes an optional path argument;trust --listprints the
global list. Creates the globalconfig.tomlwhen missing, preserves unrelated keys and comments, and
refuses to run against a config that is already malformed. Rutherford reads config once at server start,
so restart or reconnect the server after atrustfor it to take effect. Contributed by
@Artemonim in #12.- The allowlist writer validates before it writes. The rewritten
config.tomlis rendered, parsed, and
round-trip-checked in memory and only then swapped into place with an atomic replace, so a path that
cannot be represented in TOML is refused with the existing config untouched rather than truncated. The
assignment scanner is string- and comment-aware, so a[or]inside a trusted path can no longer walk
past the end of the array and drop the[agents.*]tables below it. Unrelated keys and comments are kept
as written, while the block's own managed header is rewritten in place instead of accumulating a copy per
edit. Path quoting goes through the one shared hardened quoter (io/tomltext.py), and the file mode is
carried across the replace so an owner-only config does not widen to the umask default.
Fixed
setupcould write aconfig.tomlit would then refuse to load. Its TOML quoter escaped only
backslashes and double quotes, but on Linux and macOS a control character is a legal filename byte, so
runningsetup --write --trust-workspacefrom a directory holding one emitted an unparseable file --
and becausesetupnever clobbers, it could not repair the file it had just written. Quoting is now a
single hardened implementation (io/tomltext.py) shared by every writer, escaping the full control
range and refusing outright a path with no TOML representation at all, before anything is opened.
Changed
discover's registry-directed-execution guard covers program runners, not just interpreters. It
previously refused to launch an agent resolved to a shell or language runtime, but not topip,
cargo,go,git,docker,make,gh,kubectl,curlorxargs-- each of which executes
attacker-chosen work from its own arguments as directly assh -cdoes, and those arguments come from
the registry. All are now matched by the same leading-family classifier, which leaves longer names
alone (goose,ghostandatlasare unaffected). The guard is a denylist and its docstrings now say
so: it is defense in depth, and a name it does not match is unrecognized rather than vouched for.- The ACP registry cache is written only after the response parses. It was previously persisted
before validation, so a single malformed or hostile body became the fallback replayed on every later
network failure. A bad response now fails once and leaves a previously good cache intact.
Security
- Dependency advisories closed in the development lockfile (
cryptography,mcp,
pydantic-settings,python-multipart,starlette). Reported severity overstates the exposure here:
these are HTTP-server-stack advisories reached throughfastmcp's transitive dependencies, and
Rutherford serves over stdio, so none is reachable in a default deployment.uv.lockships in neither
the wheel nor the sdist, so this affects contributors and CI rather than installed users. - CI workflows pin an explicit
permissions: contents: readceiling. The repository default is
already read-only, so nothing changes today; the block keeps a later settings change, or a job added to
those files, from silently gaining write. The code-review workflow now also skips cleanly on pull
requests from forks, which never receive repository secrets and so could only ever fail.
Documentation
- The trusted-workspace allowlist is documented end to end —
docs/security.mdcovers the
trust/untrustcommands, the platform global config path, and the fact that a project-local
trusted_workspacesreplaces rather than unions the global list at load time (previously undocumented
anywhere).docs/troubleshooting.mdpointsWORKSPACE_NOT_TRUSTEDat the one-shot CLI, andREADME.md
anddocs/configuration.mdfollow.
Thanks to @Artemonim for the trusted-workspace CLI contribution in #12.