Tacavar
2026-06-21

Least-Privilege Cross-Server Command Dispatch Without Sharing Root SSH Keys

Cross-server automation gets safer when you whitelist command names instead of shells. At Tacavar, we ran into this when our Bailian droplet—a lightweight agent that triggers deploys, posts Instagram reels, and writes video briefs—needed to dispatch work to the primary droplet. The lazy path was copying an SSH key with full root access across machines, but that would have handed a broad set of capabilities to a process that only needed five concrete actions. We built a whitelist-based deploy gate instead, and it has become one of those patterns that makes an infrastructure team sleep better at night.

## The real problem with sharing root SSH keys between production servers

When you drop a root SSH key on a second host, you give that machine the ability to run any command, modify any file, and pivot to any other system the first server can reach. In practice, that means a compromised web service, a misconfigured cron job, or a supply-chain injection on Bailian could escalate straight into a full takeover of the primary production box. There is no internal blast-radius control; the boundary collapses to a single credential. Most teams try to offset the risk with jump hosts, separate key pairs, and key rotation, but those measures don’t solve the fundamental violation of **ssh least privilege**. They still grant the caller a shell—it’s just a shell arrived at through a few more hops. For a cross-machine RPC that only needs to invoke a handful of known commands, that over-permissioning is an operational and compliance headache that keeps getting worse as automation eats more of the stack.

## How Tacavar designed a whitelist-based deploy gate for cross-droplet actions

Instead of sharing keys that open an interactive session, we created `deploy_gate.sh`, a lightweight **whitelist dispatcher** that sits on the primary droplet and listens for incoming SSH connections from Bailian through a dedicated deploy key. The trick is that the connection never lands in a bash prompt. Tacavar’s `authorized_keys` entry for that key uses OpenSSH’s `command=` directive to force every session directly into the dispatcher script, passing through only the caller’s intended action name as an environment variable—something like `DEPLOY_ACTION=deploy` or `DEPLOY_ACTION=instagram_post`.

The dispatcher compares that action against an explicit allowlist file. If the name is present, the script runs the corresponding command under `sudo` with predefined arguments; if it’s absent, the connection is rejected before anything executes. When we needed to support posting Instagram reels from Bailian, we added `instagram_post` to the whitelist, not a new SSH key. When we later added `domain_pending` to verify DNS propagation status and a wildcard entry `pip_install_*` that accepts a curated set of package names, each addition remained a deliberate, reviewable change. The critical design choice here is that Bailian never gets to choose arbitrary shell strings—it only gets to select from a catalog of predefined verbs maintained on the target server.

## Why command names beat raw bash for blast-radius control

Allowing a remote caller to pass a command name rather than a free-form shell command fundamentally limits the attack surface. An adversary who compromises Bailian can’t drop into a shell, chain commands with `&&`, redirect output to arbitrary files, or spawn reverse shells. They can only call the nouns that already exist in Tacavar’s whitelist, each of which maps to a specific script with tightly scoped side effects. Even if the attacker enumerates all permitted names, the worst they can do is repeatedly trigger an allowed deploy or re-post the same Instagram content—annoying but not destructive—because the allowed commands don’t accept arbitrary parameters from the wire. This is the operational equivalent of an API with a fixed route table instead of a raw socket. It also makes it obvious when something unusual happens, because the set of valid invocations is small enough to remember without grepping.

## Audit logs, explicit allowlists, and the operational upside of friction

Every dispatch through the gate writes a timestamped log entry that captures the action name, the system user who ran it, and the exit code. That **devops audit logging** gives operators a single place to answer the question, “What did that automation actually do this morning?” When Bailian triggers a deploy, the audit trail shows exactly which command executed, when, and whether it succeeded. The allowlist doubles as living documentation; a new teammate can read `whitelist.txt` and immediately understand what cross-machine capabilities exist without tracing SSH configs across three repos.

There’s also a defensive benefit in the small bit of friction the whitelist introduces. Adding a new command name requires a commit—often with a brief justification and a review—which prevents a developer or an AI agent from silently expanding its own scope. Unlike a root shell that accumulates capabilities by osmose, Tacavar’s gate refuses to run anything it hasn’t been explicitly taught. That forced explicitness turns out to be exactly what you want when automation speed starts to outstrip manual oversight.

## Where this pattern fits in AI-native automation and agent orchestration

As teams start wiring LLM-driven agents into production automation, the same blast-radius problem reappears. An agent like Bailian, regardless of how smart its prompts are, should not be allowed to `curl | bash` or configure iptables on the primary server. Tacavar’s whitelist dispatcher becomes a secure execution harness for **cross server automation** driven by an AI orchestrator: the agent formulates its intent as a high-level action name, the gate validates it against a curated list, the command runs, and the outcome—including stdout snippets and status—flows back for the agent to ingest. The pattern keeps the scary probability space tiny while still letting the agent do useful work like triggering deploys, checking domain propagation, or posting content. When multiple agents need to coordinate across instances, a single gate with per-agent namespaces makes it trivial to enforce resource-level authorization without distributing SSH keys further.

## What teams should copy when building secure internal RPC paths

If you’re building a similar cross-machine dispatch layer inside your own infrastructure, there’s a repeatable recipe that borrows Tacavar’s approach. First, issue a distinct SSH key pair that is used only for this RPC channel, and configure the target server’s `authorized_keys` with `command="/path/to/dispatcher.sh"` and `restrict` options—no port forwarding, no PTY allocation. Inside the dispatcher, map a set of allowed action names to exact command paths and hard-code any fixed arguments; never shell out with user-supplied strings concatenated. Keep the allowlist in a version-controlled file that the dispatcher reads on each invocation, so adding a capability is a pull request, not a live edit. Log every invocation with an identity, a timestamp, and the action, and pipe those logs to an aggregated monitoring system so anomalies surface quickly. Finally, treat any new action as a privilege escalation for the calling system and subject it to the same review process you would use for granting a human operator a new sudo rule.

If you want secure automation patterns for AI-driven ops, see what Tacavar builds at tacavar.com.