Claude (noether) 4fcd8512ae sicd: canonical argc + wire-spec doc rot (reviewer 964 re-probe #1/#2/#3)
@reviewer re-probed the argc path (18 hand-built frames, all rejection paths clean) and cleared
it as deployable. Three non-blocking follow-ups, all closed here:

  #1 doc rot -- the comments ARE the spec on a hand-rolled wire, and three still described removed
     formats: readNetstringStream (claimed v1-only; it now serves both paths), parseV2Content doc
     (empty-netstring terminator), runV2 doc (the two-generations-old command/NUL/payload split),
     and the test frame() helper. All rewritten to netstring(argc) + argc argv netstrings + payload.
  #2 canonical argc -- strconv.Atoi accepted "+1" and "001" (one value, two encodings). Reject
     leading +/-, leading zeros, and empty before Atoi, mirroring readNetstringStream length-token
     validation. New regression TestV2NonCanonicalArgcRejected (01/+1/-1 etc. exit 1, no exec).
  #3 argc-low reclassification -- documented where payload is defined: a low argc turns the
     sender's own trailing netstrings into child stdin; only ever the sender frame, no trust
     boundary. By design, now stated.

Also fixed TestV2FrameElementCapRejected, which under the argc wire was tripping on a bad argc
token rather than the count cap it claims -- now sends argc=5000 to hit maxArgs honestly.

Build/vet clean, full daemon + client suites green. Daemon is deploy-ready at this commit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 12:06:58 +02:00

sic

Run a command on a remote host without a shell re-parsing its arguments.

ssh host touch 'a b' creates two files: ssh joins its arguments with spaces and hands the result to the remote login shell, which splits it again. sic host touch 'a b' creates one file named a b. sic sends the argument vector to a daemon on the host as length-prefixed fields, and the daemon calls execvp on it. No shell parses the arguments.

Components

  • sic — client. Takes argv on the command line, frames it as netstrings, pipes the frame over ssh to sicd.
  • sicd — daemon on the target host. Reads netstrings from stdin and runs the command.

Both are single static Go binaries (cmd/sic, cmd/sicd). A Python reference implementation of the daemon is in gateway/sicd.

Wire format

Each field is a netstring: <length>:<bytes>,. A frame is a mode field, zero or more argument fields, and an empty terminator:

<mode> <arg>* 0:,

argv = ["touch", "a b"] is 4:exec,5:touch,3:a b,0:,. The length prefix means no byte needs escaping and no delimiter can collide with the payload.

mode selector behaviour
exec (default) 4:exec, execvp(argv), no shell. Arguments are passed through unchanged.
sh 2:sh, one field passed to sh -c. Pipes, redirects, and globs work.

Usage

sic <host> <command> [arg ...]
sic --sh <host> '<shell string>'
sic <host> -- <command> [arg ...]      # explicit separator
sic host1 echo hello world
sic host1 touch 'a b'                # one file named "a b"
sic host1 echo '$HOME'               # literal, no expansion
sic --sh host1 'echo hi | wc -c'

Build

go build -o bin/sic  ./cmd/sic
go build -o bin/sicd ./cmd/sicd

Install

Client:

cp bin/sic ~/bin/sic

Daemon on each target host. sicd runs what it is sent, so it must only be reachable over an authenticated transport. Pin it to a dedicated ssh key so the target can run nothing else with that key:

scp bin/sicd host:/tmp/sicd
ssh host 'sudo install -m755 /tmp/sicd /usr/local/bin/sicd'
# ~/.ssh/authorized_keys on the target:
# command="sicd",no-port-forwarding,no-pty ssh-ed25519 AAAA...

Do not listen on a TCP or message-bus port.

Test

printf '4:exec,2:id,0:,' | ssh host sicd
sic host echo hello world

See also

  • docs/design.md — wire format, exec modes, transport and security, prior art.
  • demos/quoting-hell.py — six cases run both ways, plain ssh versus sic.
  • SKILL.md, SKILL-bg.md — agent skill definitions (foreground and background).
S
Description
Quoting-proof remote command execution: netstrings + execvp over ssh.
Readme MIT 3.4 MiB
Languages
Go 71%
Python 23.4%
Shell 5.6%