Why ctrl-exec
Thirty years of running Linux infrastructure, and the tool that kept not existing.
I have been building Linux infrastructure for businesses since the mid-1990s. This is the problem I kept running into, and why I eventually wrote the spec myself.
The Early Days
The first tools for running commands across multiple systems were the BSD r-commands — rsh, rcp, rexec. They worked. They also had no encryption and no meaningful access control. That became obvious quickly, and they were abandoned.
inetd offered another path: bind a custom application to a port, invoke it on connection. I experimented with this for remote command dispatch. It was flexible and it was a bad idea for the same reasons — no encryption, no identity, no controls. Anything that could reach the port could do anything the service allowed.
SSH solved the transport problem. Encrypted, authenticated, auditable at the connection level. For a single operator managing infrastructure alone, it is fine. It remains fine. The problem appears when you start delegating.
The Delegation Problem
When it is just you, SSH is manageable. You know what you are doing, you know which hosts you are on, and the blast radius of a mistake is bounded by your own judgment.
When you delegate — to a colleague, a contractor, a junior operator — that changes. SSH hands them a shell. A shell is not a set of permitted operations; it is everything the account can reach. Scoping that requires ForceCommand hacks, carefully constructed sudoers entries, and a lot of discipline from people who are under pressure. In practice, people get broad access because scoping it properly is too much work.
And then there is key management. SSH keys get copied around. They get emailed. They get committed to repositories. They sit in home directories on laptops that get lost. An emailed SSH key has defeated most of what SSH was supposed to provide. I could not believe this was not a solved problem — that the standard answer to "how do I give someone access to this server" was still "copy this file to their machine and add this line to authorized_keys." Editing key files by hand is not something anyone should be doing in 2025, and yet here we are.
The pairing model in ctrl-exec replaces all of that. A new agent generates its own key pair and submits a certificate signing request. Both sides display a verification code derived from the CSR. The operator checks the codes match and approves. No keys are copied. No files are emailed. The agent receives a CA-signed certificate over the pairing connection and stores it locally. The ceremony is deliberate, auditable, and does not require trusting any out-of-band channel.
2008: A Near Miss
In 2008 I built a highly available machine-to-machine connector for communicating during emergency events — infrastructure alerting and response, designed to keep working when things were going wrong. It worked well. But it was focused on communications and message passing, not command dispatch. It was not the right tool.
I kept looking at orchestration frameworks. Ansible, Salt, Puppet and their predecessors. They are well-built and solve real problems. They were always too large for what I actually needed. The overhead of getting them running, the conceptual model, the dependency footprint — all disproportionate to the task of running a specific script on a specific host with a structured result. I wanted something that would take five minutes to deploy, not five days.
I wanted agentless operation. After thinking it through, I accepted that is not actually possible if you want the execution to happen on the remote host with any meaningful control. An agent is necessary. The question is how minimal and how controlled it can be.
Writing the Spec
I wrote the specification several times over the years. The requirements were consistent each time:
- Run a named script on a remote host, get a structured result back
- The remote host controls what can be run — not the caller
- Delegation to others must be scoped: specific scripts, specific hosts, specific conditions
- Compromise of a credential should not hand an attacker the full system
- Human error should be contained — operators should not be able to run the wrong thing on the wrong host by accident
- The controls and buttons should be as close to the actual user as possible, so they can act without a human chain of approvals for routine operations
- Know what happened — a complete, tamper-evident log that survives the person who ran the command
That last logging requirement mattered from the start. When something goes wrong — and it does go wrong — the question is always "what actually happened, in what order, on which host." SSH logs that a connection was made. It does not log what was run. Bash history can be cleared by the person who ran the commands. ctrl-exec logs every invocation on both sides with a correlated request ID, the script name, the arguments, the exit code, and the identity of the caller. Reviewing an incident means searching for a request ID and seeing the complete picture across every host involved.
The execution boundary is the key design decision. Not a filter on top of broad access, but a defined set of named operations. Anything outside that set is structurally unreachable.
Design Decisions
Several decisions followed directly from the requirements.
- Portability
- The agent needed to run on almost anything — Debian servers, Alpine containers, and OpenWrt routers. Testing on OpenWrt specifically was important because it is where the constraints are most acute: limited flash, limited RAM, a stripped-down package ecosystem. If it works cleanly on OpenWrt, it works anywhere. The dependency list is intentionally minimal: Perl and openssl, both present on any Linux system worth managing.
- Interpreted code
- ctrl-exec is written in Perl rather than compiled to a binary. This was deliberate. When you deploy software to your infrastructure, you should be able to read what it does. A compiled binary is a black box; an interpreted script is not. Anyone competent to manage Linux infrastructure can read Perl. The full codebase is auditable without special tooling, and the security model can be verified by inspection rather than trust.
- Certificate-based identity
- mTLS was the right answer for transport security because it solves the identity problem at both ends simultaneously. The agent knows it is talking to the right ctrl-exec instance, and ctrl-exec knows it is talking to the right agent. No shared secrets. No password prompts. No ambient trust.
Why Now
The recent wave of infrastructure vulnerabilities made it clear that others could use this. What had been a private tool for managing my own deployments was worth documenting properly.
Then OpenClaw arrived, and NanoClaw, and the wider emergence of agentic AI operating against live infrastructure. That made the need obvious. An AI agent with shell access is the delegation problem at scale and speed — the agent will use everything available to it, it will not exercise judgment about what it should touch, and if it is manipulated via prompt injection it will do what it is told regardless of intent.
ctrl-exec is the right boundary for that. It is an application firewall at the execution level. The agent can only invoke what is on the allowlist. Arguments are validated before execution. Every invocation is logged. Revocation is immediate and centralised. The agent's capability surface is explicit, bounded, and auditable.
So: here it is. A newly built site, updated documentation, a completed security review, and the hope that others find it as useful as I have.
The source is at github.com/OpenDigitalCC/ctrl-exec. Commercial support and services are available at ctrl-exec.com.