0003 — cTrader CLI nodes are HTTP + JWT, no SSH/shell
Context
Backtest/run containers execute on remote hosts. The obvious approach — SSH in and run docker — gives the main app arbitrary remote code execution and long-lived credentials on every node. That is a large blast radius for a system that runs untrusted user cBots.
Decision
Each remote host runs a standalone CtraderCliNode HTTP agent with no SSH and no shell. The
main app calls the agent over HTTP; every request carries a short-lived HS256 JWT (5-minute,
iss=app-main / aud=app-node) signed with that node's secret. The agent:
- only runs images matching
AllowedImagePrefix(with a path boundary soghcr.io/spotwarecan't matchghcr.io/spotware-evil/...); - execs docker via
ArgumentList— never a shell string; - is stateless, finding containers by the
app.instancelabel; - self-registers and heartbeats to
POST /api/nodes/register; the main app upserts theCtraderCliNodeby name, so a node survives IP changes.
Consequences
- A leaked request token expires in minutes; there is no standing shell credential to steal.
- The agent's capability is bounded to "run an allowed image" — it can't be turned into a general remote shell.
- Node identity is name-based, so re-provisioning a node with a new IP doesn't orphan its history.