让本地 AI Agent 在线管理你的远程服务器。Zero-overhead SSH multiplexing CLI for AI agents — manage remote servers like local commands.
SaferSkills independently audited sshc (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Pure C daemon architecture (cross-platform: Unix + Windows). System deps only (python3 + nc + ssh). Single-file distributable. SSH ControlMaster persistent multiplexing eliminates repeated auth delay.
./sshc health > /dev/null 2>&1 || ./sshc daemon &./sshc exec <profile> "<command>"chmod 600 ~/.sshc/profiles.json# 1. Compile daemon
cc -O2 -o sshc-daemon sshc-daemon.c # Unix
# x86_64-w64-mingw32-gcc -O2 -o sshc-daemon.exe sshc-daemon.c -lws2_32 # Windows cross-compile
# 2. Ensure executable
chmod +x sshc sshc-daemon sshc-web
# 3. Create config
mkdir -p ~/.sshc
cat > ~/.sshc/profiles.json << 'EOF'
{
"default": "my-server",
"servers": {
"my-server": {
"host": "1.2.3.4",
"user": "root",
"key": "~/.ssh/id_ed25519",
"port": 22
}
}
}
EOF
# 4. Start daemon
./sshc daemon
# 5. Health check
./sshc health
# 6. Execute remote command
./sshc exec my-server "uptime"
# 7. (Optional) Launch web UI for visual management
./sshc web
# Open http://127.0.0.1:17375sshc daemon # Start daemon
sshc health [profile] # Health check (omit for all, "*" = all)
sshc exec <profile> <cmd> [timeout] # Execute command (default timeout: 30s)
sshc profiles # List all servers and status
sshc add <name> <user@host> -i <key> [-p port] [--proxy <cmd>] [--allow <re>] [--deny <re>] # Add server
sshc remove <name> # Remove server
sshc default <name> # Set default server
sshc reconnect <profile> # Force reconnect (kill + re-establish ControlMaster)
sshc upload <profile> <local> <remote> # Upload file via SFTP (dirs auto tar-pipe)
sshc download <profile> <remote> <local> # Download file via SFTP (dirs auto tar-pipe)
sshc web [port] # Start web UI (default :17375)# Key auth
./sshc add prod [email protected] -i ~/.ssh/id_rsa
# Key auth with custom port + proxy + command safety
./sshc add prod [email protected] -i ~/.ssh/id_rsa -p 2222 \
--proxy "nc -X 5 -x 127.0.0.1:1080 %h %p" \
--allow "^(ls|df|uptime|systemctl|apt|docker).*" \
--deny ".*rm -rf.*|.*mkfs.*"
# Password auth (server must be added via Web UI or direct JSON edit for password)./sshc web starts a management dashboard at http://127.0.0.1:17375. Features:
~/.sshc/keys/<profile>/<filename># ── List all servers (instant, no health check — alive: null) ──────────
curl http://127.0.0.1:17375/api/profiles
# Response: {"default":"prod","profiles":{"prod":{"host":"1.2.3.4","user":"root","port":22,"alive":null,"has_key":true,"has_password":false,"proxy":"nc -X 5 -x ..."}}}
# ── Add server (key path) ──────────────────────────────────────────────
curl -X POST http://127.0.0.1:17375/api/profiles \
-H 'Content-Type: application/json' \
-d '{"name":"prod","host":"1.2.3.4","user":"root","key":"~/.ssh/id_rsa"}'
# ── Add server (key content — base64, from file upload) ─────────────────
curl -X POST http://127.0.0.1:17375/api/profiles \
-H 'Content-Type: application/json' \
-d '{"name":"prod","host":"1.2.3.4","user":"root","key_content":"<base64>","key_filename":"id_rsa"}'
# ── Add server (password auth) ─────────────────────────────────────────
curl -X POST http://127.0.0.1:17375/api/profiles \
-H 'Content-Type: application/json' \
-d '{"name":"staging","host":"5.6.7.8","user":"admin","password":"mypass","port":2222}'
# ── Add server (with proxy) ────────────────────────────────────────────
curl -X POST http://127.0.0.1:17375/api/profiles \
-H 'Content-Type: application/json' \
-d '{"name":"hidden","host":"10.0.0.5","user":"root","key":"~/.ssh/id_rsa","proxy":"nc -X 5 -x 127.0.0.1:1080 %h %p"}'
# ── Update proxy ───────────────────────────────────────────────────────
curl -X PUT http://127.0.0.1:17375/api/profiles/hidden \
-H 'Content-Type: application/json' \
-d '{"proxy":"nc -X connect -x proxy.internal:3128 %h %p"}'
# ── Clear proxy ────────────────────────────────────────────────────────
curl -X PUT http://127.0.0.1:17375/api/profiles/hidden \
-H 'Content-Type: application/json' \
-d '{"proxy":null}'
# ── Set default server ─────────────────────────────────────────────────
curl -X PUT http://127.0.0.1:17375/api/profiles/prod -d '{"default":true}'
# ── Test connection (blocking — calls daemon health check) ─────────────
curl -X POST http://127.0.0.1:17375/api/test/prod
# Response: {"ok":true,"name":"prod","alive":true}
# ── Delete server ──────────────────────────────────────────────────────
curl -X DELETE http://127.0.0.1:17375/api/profiles/staging| Type | Generated ProxyCommand |
|---|---|
| HTTP | nc -X connect -x {host}:{port} %h %p |
| SOCKS5 | nc -X 5 -x {host}:{port} %h %p |
| SOCKS4 | nc -X 4 -x {host}:{port} %h %p |
| Custom | user-provided raw ProxyCommand string |
For agents adding servers programmatically, use the proxy field directly with any valid OpenSSH ProxyCommand string.
~/.sshc/profiles.json:
{
"default": "prod",
"servers": {
"prod": {
"host": "10.0.0.1",
"user": "root",
"key": "~/.ssh/prod.key",
"port": 22
},
"staging": {
"host": "10.0.0.2",
"user": "admin",
"password": "secret",
"port": 2222
},
"hidden": {
"host": "10.0.0.5",
"user": "root",
"key": "~/.ssh/id_rsa",
"port": 22,
"proxy": "nc -X 5 -x 127.0.0.1:1080 %h %p"
},
"safe-prod": {
"host": "10.0.0.1",
"user": "root",
"key": "~/.ssh/id_rsa",
"port": 22,
"allow": "^(ls|df|uptime|systemctl|apt|docker).*",
"deny": ".*rm -rf.*|.*mkfs.*|.*dd if=.*"
}
}
}default — default server name (used when profile omitted in exec)servers.<name>.host — IP or domain (required)servers.<name>.user — SSH user (default: root)servers.<name>.key — SSH private key path on disk (mutually exclusive with password)servers.<name>.password — plaintext password (mutually exclusive with key)servers.<name>.port — SSH port (default: 22)servers.<name>.proxy — OpenSSH ProxyCommand string (optional, default: none / direct connect)servers.<name>.allow — POSIX regex for allowed commands (optional, default: allow all)servers.<name>.deny — POSIX regex for denied commands (optional, default: deny none)Per-server command validation via POSIX extended regex (Unix) / glob matching (Windows). Rules are evaluated as: deny takes priority. If deny matches → rejected. If allow is set but doesn't match → rejected.
# Add with command restrictions
./sshc add safe-prod [email protected] -i ~/.ssh/id_rsa \
--allow "^(ls|df|uptime|systemctl).*" \
--deny ".*rm -rf.*"
# Update via API
curl -X PUT http://127.0.0.1:17375/api/profiles/safe-prod \
-H 'Content-Type: application/json' \
-d '{"allow":"^(ls|df|uptime|systemctl|apt).*"}'Validation happens in the C daemon before any command reaches SSH — no way to bypass at the CLI or Web UI level.
File upload/download via OpenSSH sftp reusing the existing ControlMaster connection (no extra auth). Directories are auto-detected and transferred via tar pipe for efficiency.
# CLI upload (single file)
./sshc upload prod ./app.tar.gz /opt/app.tar.gz
# CLI upload (directory → auto tar pipe)
./sshc upload prod ./my-project/ /opt/my-project/
# CLI download
./sshc download prod /var/log/syslog ./syslog
./sshc download prod /opt/data/ ./data-backup/
# Web UI: click "上传" / "下载" buttons in server listWeb UI upload API:
# Upload file (base64 content in JSON)
curl -X POST http://127.0.0.1:17375/api/upload/prod \
-H 'Content-Type: application/json' \
-d '{"content":"<base64>","remote_path":"/opt/app.tar.gz","filename":"app.tar.gz"}'
# Response: {"ok":true,"name":"prod","size":12345,"remote":"/opt/app.tar.gz"}Web UI download API:
# Download file (streaming binary response)
curl -o app.tar.gz 'http://127.0.0.1:17375/api/download/prod?path=/opt/app.tar.gz'The C daemon automatically detects config file changes via mtime (last-modified timestamp). No daemon restart needed — any changes to ~/.sshc/profiles.json take effect on the next request. This includes adding/removing servers, changing proxies, and updating allow/deny rules.
sshc (bash) ──nc──> daemon.sock (Unix) / 127.0.0.1:{port} (Windows TCP)
│
sshc-daemon (C, fork/thread-per-request)
│
posix_spawnp/vfork+execvp("ssh") / CreateProcess + pipes
│
ControlMaster ──> Remote Server
(~/.sshc/mux/ssh-<name>, persist 300s)
Web UI (python3) ──socket──> daemon IPC ──> health/exec requests
│
└── profiles.json (read/write directly, atomic replace + chmod 600)ensure_master + ssh exec "echo ok" — also serves as connection warmup~/.sshc/daemon.sock, fork() per request, posix_spawnp for master, vfork for exec~/.sshc/daemon.port), CreateThread per request, CreateProcess for all SSH callssftp -b - with ControlPath reuse for file transfer; tar pipe for directoriesstat()-based mtime detection at start of each request handler, no polling loopFileReader.readAsDataURL() → base64 → ~/.sshc/keys/<profile>/<filename> (chmod 600, basename-sanitized)-o ProxyCommand="..." into all SSH invocations| Aspect | Unix (macOS/Linux) | Windows |
|---|---|---|
| IPC | AF_UNIX socket | TCP 127.0.0.1 (auto-assigned port) |
| Concurrency | fork() per request | CreateThread per request |
| Process spawn | posix_spawnp / vfork+execvp | CreateProcess |
| Socket path | ~/.sshc/daemon.sock | ~/.sshc/daemon.port (contains port number) |
| Compile | cc -O2 -o sshc-daemon sshc-daemon.c | Add -lws2_32 |
| Shell | bash (native) | Git Bash / MSYS2 / WSL |
| Dependency | Purpose | Required |
|---|---|---|
cc (clang/gcc) | Compile C daemon | Yes (one-time) |
python3 | JSON parsing + Web UI server | Yes |
nc | Socket communication (IPC) | Yes |
ssh | OpenSSH client with ControlMaster | Yes |
sshpass | Password authentication support | No (only if using password auth) |
# Via CLI
./sshc add hidden [email protected] -i ~/.ssh/id_rsa --proxy "nc -X 5 -x 127.0.0.1:1080 %h %p"
# Via REST API (when daemon+web are running)
curl -X POST http://127.0.0.1:17375/api/profiles \
-H 'Content-Type: application/json' \
-d '{"name":"hidden","host":"10.0.0.5","user":"root","key":"~/.ssh/id_rsa","proxy":"nc -X 5 -x 127.0.0.1:1080 %h %p"}'./sshc exec prod "uptime" &
./sshc exec staging "df -h" &
./sshc exec db "systemctl status postgresql" &
wait./sshc health # all profiles
./sshc health prod # single profile
./sshc profiles # table format with status# Upload
./sshc upload prod ./app.tar.gz /opt/app.tar.gz
./sshc upload prod ./my-project/ /opt/my-project/ # directory → tar pipe
# Download
./sshc download prod /var/log/syslog ./syslog
./sshc download prod /opt/data/ ./data-backup/
# Via Web UI: click "上传" / "下载" buttons in server list# Add server with command safety
./sshc add safe-prod [email protected] -i ~/.ssh/id_rsa \
--allow "^(ls|df|uptime|systemctl|apt|docker).*" \
--deny ".*rm -rf.*|.*mkfs.*|.*dd if=.*"
# Update via REST API
curl -X PUT http://127.0.0.1:17375/api/profiles/safe-prod \
-H 'Content-Type: application/json' \
-d '{"allow":"^(ls|df|uptime|systemctl|apt|docker).*"}'
# Validation happens in daemon — bypassing impossible./sshc web
# Human opens http://127.0.0.1:17375
# - Drag key file onto drop zone
# - Fill host/user/port
# - Click "代理" in server list to configure proxy via modal
# - Click "测试" to verify connectivity# Daemon not responding
./sshc daemon # restart
# Master connection stale
./sshc reconnect prod # force re-establish
# Check if daemon is alive
./sshc health > /dev/null 2>&1 && echo "alive" || echo "dead"~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.