Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ members = [
"crates/buzz-audit",
"crates/buzz-acp",
"crates/buzz-agent",
"crates/buzz-backend-ssh",
"crates/sprig",
"crates/buzz-test-client",
"crates/buzz-ws-client",
Expand Down
6 changes: 6 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,12 @@ test-unit:
# because nothing in CI runs `cargo test --workspace` — workspace
# membership alone buys clippy/check, not a single executed test.
cargo nextest run -p buzz-backend-kubernetes
# Remote-deploy provider (buzz-backend-ssh). Infra-free: the deploy
# tests execute the generated script against a local /bin/sh with a
# stubbed HOME, no network. This is the only place the shell-injection
# canary runs — the Windows job's copy of these tests is #[cfg(unix)]d
# out — so dropping this step lets an injection regression ship green.
cargo nextest run -p buzz-backend-ssh
else
./scripts/run-tests.sh unit
fi
Expand Down
39 changes: 39 additions & 0 deletions crates/buzz-backend-ssh/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[package]
name = "buzz-backend-ssh"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
description = "Buzz backend provider that deploys managed agents to a remote host over SSH"

# Deliberately binary-only and deliberately NOT bundled with the desktop app
# (not in `tauri.conf.json` externalBin, not in `scripts/bundle-sidecars.sh`).
# `discover_provider_candidates` prepends the app bundle's own directory to the
# provider search path, so shipping this inside the bundle would give every
# install an auto-discovered SSH-deploy capability and quietly undermine the
# "Only use providers from trusted sources" warning the desktop shows. It is a
# release artifact the user installs to `~/.local/bin`, which is already on the
# discovery path.
[[bin]]
name = "buzz-backend-ssh"
path = "src/main.rs"

[dependencies]
serde = { workspace = true }
serde_json = { workspace = true }
zeroize = { workspace = true }
# Deploy can install `buzz-acp` on the host by streaming it inside the script
# that already travels on the SSH stdin channel. base64 is what keeps raw bytes
# from corrupting that stream; sha2 is what lets the host refuse a payload that
# arrived damaged. Both are already workspace dependencies.
base64 = { workspace = true }
sha2 = { workspace = true }
# Step 0 of the deploy reconciliation loop (docs/remote-agents.md) requires the
# provider to derive the agent's identity from `private_key_nsec` rather than
# trust a caller-supplied pubkey. bech32 decodes the NIP-19 nsec; secp256k1
# turns those bytes into the x-only public key every host-side name is keyed on.
# Versions match the ones already resolved in the workspace lockfile
# (buzz-pair-relay pins the same secp256k1 major).
bech32 = "0.11"
secp256k1 = "0.31"
39 changes: 39 additions & 0 deletions crates/buzz-backend-ssh/assets/buzz-acp@.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
[Unit]
Description=Buzz agent %i
After=network-online.target
Wants=network-online.target
# A long-running agent must never be rate-limited into staying down: a unit
# held by the start limiter looks exactly like an agent that silently died,
# and only `systemctl reset-failed` clears it.
StartLimitIntervalSec=0

[Service]
Type=simple
# The agent runs arbitrary code by design, so the SSH user's own privileges are
# the intended ceiling — but without this the harness can climb past them
# through any setuid/setgid binary on the host, or through passwordless sudo
# granted to that user. A VPS pilot exercised the harness, the Buzz CLI,
# NIP-OA owner-reviewed draft creation and repository branch pushes with this
# set; all remained functional.
#
# Deliberately the whole hardening delta. ProtectSystem/ProtectHome would also
# apply here, but the agent has no modeled workspace yet (see the REPOS /
# working-directory limitation in docs/remote-agents.md) — so until writable
# paths are something the protocol states, those directives would be guessing
# at which of the user's home an agent legitimately needs.
NoNewPrivileges=true
# Holds the agent's minted nsec. Written by the provider with umask 077 and
# chmod 600; systemd reads it as the owning user.
EnvironmentFile=%h/.config/buzz-acp/%i.env
# Absolute path, substituted at install time from the host's resolved
# `buzz-acp` and double-quoted there — systemd splits an unquoted value on
# whitespace, and the configurable `buzz-acp path on the server` may name a
# directory that contains some. systemd does not expand environment variables
# in the program position, and the shell indirection that would work around
# that is not worth adding to a unit whose environment carries a private key.
ExecStart=@BUZZ_ACP_BIN@
Restart=always
RestartSec=5

[Install]
WantedBy=default.target
Loading