The gate
A forced command on your server that lets the device's SSH key run a short list of fixed shapes and nothing else, with every call logged.
Why a gate
The device needs an SSH key to reach your server. An ordinary key line in authorized_keys gives whoever holds the device a shell as you: the vault, the mail token, every credential on the machine. The gate turns that key into a remote control with a fixed set of buttons.
How it works
OpenSSH can force a command for a key. When the device connects, sshd ignores what it asked to run, runs the gate instead, and passes the original request in SSH_ORIGINAL_COMMAND. The gate compares that request against its list of shapes. A match runs; anything else gets a JSON error on the device and a line in the log.
The restrict option on the key line also removes port forwarding, agent forwarding, X11 and a terminal, so a refused command has nowhere else to go.
Install it
- Copy the script onto your server's PATH:
On the server
mkdir -p ~/.local/bin ~/.config/pennyos
install -m 755 tools/mini_gate.py ~/.local/bin/pennyos-gate
- Edit the constants at the top of
~/.local/bin/pennyos-gateto match your server. They are deliberately literal, so the list of what is allowed can be read in one place:
pennyos-gate, top of file
VAULT = "/home/you/vault"
GLANCE_URLS = {"http://server:8787/glance"}
USAGE_URLS = {"http://server:8787/usage": "usage",
"http://server:8787/usage/refresh": "usage"}
MAIL = "/home/you/.local/bin/pennyos-mail"
OSINT = "/home/you/.local/bin/pennyos-osint"
STOREFRONT = "/home/you/.local/bin/pennyos-storefront"
- Add the device's public key, which the installer printed, to
~/.ssh/authorized_keyson the server, in the gated form:
~/.ssh/authorized_keys
restrict,command="/home/you/.local/bin/pennyos-gate" ssh-ed25519 AAAA... pennyos@beepy
- On the device, set
mini_host,mini_userandvault_pathin/etc/pennyos.conf, and the paths that name server scripts:mail_reader,osint_search,storefront_dig,glance_urlandconsole_token_path. The gate checks them character for character. - Press s on Home. Vault should report a pull.
The log
Every call is appended to ~/.config/pennyos/gate.log by shape. A refusal is written with the full command. When a device app fails with a gate error, the log tells you exactly what it asked for, so you do not have to guess.
Test it
From the device, a plain shell must be refused:
On the Beepy
ssh you@server 'ls'
{"error": "refused by the gate on the mini: ..."}
If ls lists your home directory, the key line is missing its command= and the device has a full shell. Fix that before carrying it.
Rules for changing it
- A new app that needs the server adds its shape to the gate in the same change. The shapes are narrow on purpose: a path, a fixed set of flags, an argument that must look a certain way.
- Never widen a shape to "any command under this directory". The point is that the list can be read and reasoned about.
- Arguments that begin with a dash are refused where an option could be smuggled in.
What it does not do
A stolen device with an unlocked card can still read your wiki and ask Claude about it, because those are allowed shapes. The PIN and the encrypted card are what stop that. Revoking is one line: delete the key from authorized_keys. See Security.