cardputer-signer

A hardware approval signer for AI agents, running on the M5 Cardputer ADV.

When an agent wants to do something risky (for example git push origin main), it sends the exact action text to the device. The device shows the text on its screen, and only a physical button press by a human produces a signature. The agent-side hook then verifies the signature with signcheck and proceeds only on an explicit “yes”. No answer, a timeout, or any verification failure means “no”.

How it works

Layout

Build and flash

cd firmware/signer
pio run -t upload --upload-port /dev/cu.usbmodemXXXX

Needs PlatformIO. Dependencies (M5Cardputer, M5Unified, ArduinoJson, NimBLE-Arduino, Crypto) are in platformio.ini.

Host tools

Create a Python 3.12 venv with cryptography (and pyserial, bleak for the device client) as .venv next to the wrappers, or edit the wrappers to point at your interpreter.

tools/cardputer-sign pubkey --save        # trust the device key once
tools/cardputer-sign sign --kind perm --id demo-1 --text "git push origin main"
signcheck/run-tests.sh                    # needs pytest in signcheck/.venv

deadhand: a dead man’s switch

A second firmware for the same hardware. A long-running process (an overnight agent run, a render, a trading bot) keeps going only while you press the side button every N minutes. Each press is a signed pulse; if the host stops seeing valid pulses, it stops the process and logs why. Because the key is on the device and only the button uses it, the watched process cannot extend its own leash.

# one-time: build, flash and trust the key
(cd firmware/deadhand && pio run -t upload --upload-port /dev/cu.usbmodemXXXX)
tools/deadhand/deadhand pair --source usb

# run anything under the switch: press the button every 40 minutes, 5 minutes of grace
tools/deadhand/deadhand run --interval 40m --grace 5m -- ./overnight.sh

Examples: an overnight coding-agent session, a long script or data migration, a trading bot that should not run unattended, a render farm job, a model training run on rented GPUs, a robot or lab rig, a “while I am at my desk” access window. For Claude Code there is also a PreToolUse hook that refuses tool calls when the last pulse is too old. Details: firmware/deadhand/README.md (device, settings) and tools/deadhand/README.md (host side).

Protocol

See signcheck/FORMAT.md for the signed message format and verification rules.

Limitations

This is a prototype. The private key is stored in ESP32 NVS in plain form: no flash encryption, no secure boot, no eFuse protection. Anyone with physical access and a USB cable can extract it. Do not treat it as a production trust anchor. Keys marked test: true are rejected for the go kind.

License

MIT, see LICENSE.