cmdveil
A small, dependency-free HTTP gateway for running only explicitly approved commands.
Cmdveil turns a controlled set of local executables into authenticated HTTP endpoints. It is intended for home servers, internal automation, deployment hooks, monitoring integrations, and operational tasks where giving another system SSH or arbitrary shell access would be excessive.
Cmdveil does not execute request strings through a shell. Every action has an absolute executable path, a fixed argument vector, declared typed parameters, a timeout, output limits, and a concurrency limit.
Why it exists
Заголовок раздела «Why it exists»A webhook often needs to do one narrow thing: refresh a cache, inspect disk usage, restart a known service, run a backup, or trigger a deployment. Typical alternatives expose too much authority:
- unrestricted SSH credentials;
- a custom one-off web server for every operation;
sh -cwrappers vulnerable to quoting mistakes;- large automation platforms for a tiny local task.
Cmdveil provides a narrow, auditable boundary between HTTP and processes.
Features
Заголовок раздела «Features»- No third-party runtime or package dependencies.
- Linux and macOS support through Swift Package Manager.
- HTTP/1.1 server implemented inside the project.
- Bearer-token authentication with constant-time comparison.
- Strict action allowlist; absolute executable paths only.
- String, integer, boolean, and enum parameters.
- Length, range, and full-match regular-expression validation.
- Placeholders may occupy only a complete process argument.
- No implicit environment inheritance.
- Per-action and global concurrency limits.
- Bounded simultaneous client connections.
- Per-client fixed-window rate limiting.
- Process timeouts with whole process-group termination.
- Independent stdout/stderr capture and truncation.
- JSON Lines audit log with sensitive-parameter redaction.
- Configuration validation CLI.
- Secure token generator.
- Reusable
CmdveilCoreSwift library.
Requirements: Swift 5.9 or newer.
git clone https://github.com/localzet/cmdveil.gitcd cmdveilswift build -c releaseswift testThe binary is created at .build/release/cmdveil.
Install it locally:
sudo ./scripts/install.shQuick start
Заголовок раздела «Quick start»cp examples/cmdveil.json ./cmdveil.jsonexport CMDVEIL_TOKEN="$(.build/release/cmdveil token)".build/release/cmdveil check --config ./cmdveil.json.build/release/cmdveil serve --config ./cmdveil.jsonIn another terminal:
curl -sS http://127.0.0.1:7777/health
curl -sS \ -H "Authorization: Bearer $CMDVEIL_TOKEN" \ http://127.0.0.1:7777/v1/actions
curl -sS \ -X POST \ -H "Authorization: Bearer $CMDVEIL_TOKEN" \ -H "Content-Type: application/json" \ -d '{"arguments":{"message":"hello from cmdveil"}}' \ http://127.0.0.1:7777/v1/run/echoExample response:
{ "action": "echo", "duration_ms": 11, "exit_code": 0, "request_id": "b8fb9dd1-1c48-46fd-a9c0-3e8c7c7543d7", "stderr": "", "stderr_truncated": false, "stdout": "hello from cmdveil\n", "stdout_truncated": false, "timed_out": false}Configuration
Заголовок раздела «Configuration»{ "server": { "bind": "127.0.0.1", "port": 7777, "token_env": "CMDVEIL_TOKEN", "global_concurrency": 8, "max_connections": 128, "rate_limit_per_minute": 120, "audit_log": "./cmdveil-audit.jsonl" }, "actions": { "echo": { "description": "Echo a validated message", "executable": "/bin/echo", "arguments": ["{{message}}"], "parameters": { "message": { "type": "string", "required": true, "min_length": 1, "max_length": 200 } }, "timeout_seconds": 5, "max_output_bytes": 65536, "concurrency": 4, "environment": {"LC_ALL": "C"}, "inherit_environment": [] } }}A placeholder must be the entire argument:
"arguments": ["--mode", "{{mode}}", "--", "{{path}}"]This is rejected:
"arguments": ["--path={{path}}"]The restriction makes the final process argument vector mechanically obvious and prevents shell-style interpolation assumptions.
See configuration reference, HTTP API, and security model.
cmdveil serve --config <path>cmdveil check --config <path>cmdveil token [--bytes <16...1024>]cmdveil versioncmdveil helpDeployment
Заголовок раздела «Deployment»Cmdveil binds to 127.0.0.1 by default. Keep that default unless remote access is intentional. For remote access, place it behind a TLS reverse proxy or a private overlay network and add network-level access controls.
A hardened unit file is provided in systemd/cmdveil.service. The service expects:
- binary:
/usr/local/bin/cmdveil; - configuration:
/etc/cmdveil/config.json; - token environment file:
/etc/cmdveil/token.env; - service account:
cmdveil.
Design guarantees
Заголовок раздела «Design guarantees»Cmdveil guarantees that request-controlled values are passed as individual process arguments. It does not invoke sh, bash, or another command interpreter unless the administrator explicitly configures such an interpreter as the executable. Do not configure a shell if request input reaches shell source code.
Cmdveil also starts each action in a dedicated process group and kills remaining descendants when the action ends or times out.
Exit and HTTP semantics
Заголовок раздела «Exit and HTTP semantics»- A successfully launched action returns HTTP
200, even when the process exits non-zero. Inspectexit_code. - A timed-out action returns HTTP
504with a completeRunResultbody. - Validation failures return
400. - Missing or invalid credentials return
401. - Saturated action concurrency returns
409; saturated global concurrency returns503. - Rate-limit failures return
429.
License
Заголовок раздела «License»MIT. See LICENSE.
Исходный код · MIT