pastelocal has four core components, plus one experimental component:
A long-running HTTP server on the local machine. It binds to 127.0.0.1:7331
(loopback only) and exposes three endpoints:
| Endpoint | Auth | Purpose |
|---|---|---|
GET /clipboard |
Bearer token | Reads the OS clipboard, returns base64-encoded PNG |
GET /health |
None | Returns {"ok": true} for liveness checks |
GET /version |
None | Returns protocol version and binary version |
The daemon enforces:
- Concurrency:
max_in_flightsemaphore (default 4) - Rate limit: token-bucket at
rate_limit_per_minute(default 60) - Size limit:
max_image_bytes(default 50 MiB) - Auth: Bearer token with constant-time comparison
- Loopback only:
ConnContextrejects any non-loopback connection - Audit: optional JSON-lines audit log with image SHA-256 hash
Signal handling:
SIGHUP— reload configuration and re-read token from storeSIGTERM— graceful shutdown with 5-second drain timeout
A one-shot CLI that runs on the remote host. It:
- Reads the auth token from
~/.config/pastelocal/token(mode 0600) - Checks protocol version via
GET /version - Fetches clipboard image via
GET /clipboardthrough the SSH tunnel - Base64-decodes the image and writes it to
~/.cache/pastelocal/(mode 0600) - Prints the absolute file path to stdout
Exit codes: 0 = success, 1 = tunnel not connected, 2 = auth failure,
3 = no image, 4 = format error, 5 = image too large, 10 = other error.
The token never appears in argv or environ; it is read from a file only.
The user-facing command-line tool built with Cobra. Subcommands:
| Command | Group | Purpose |
|---|---|---|
init |
Daemon | Generate token, write config, install service, start daemon |
start / stop / restart |
Daemon | Service lifecycle |
rotate-token |
Daemon | Generate new token, print hosts that need update |
uninstall |
Daemon | Stop, remove service, delete token and config |
add-host <alias> |
Host | Install binary + token + skill on remote, edit SSH config |
remove-host <alias> |
Host | Uninstall from remote, remove SSH config entry |
list-hosts |
Host | Show configured hosts (table or JSON) |
status |
Diagnostic | Daemon status, port, uptime, host reachability |
doctor |
Diagnostic | Run all diagnostic checks (--fix to auto-fix) |
logs |
Diagnostic | Tail daemon logs (launchd log or journalctl) |
A skill installed at ~/.claude/commands/paste.md on the remote host (works with Claude and other agentic coding tools).
It instructs the tool to:
- Run
pastelocal-remotevia Bash - Read the resulting image file
- Delete the file
- Confirm to the user: "Got it, image attached."
A standalone relay server (cmd/relay-server) and supporting client libraries that enable E2E-encrypted clipboard sharing between multiple devices without requiring direct SSH tunnels.
Components:
internal/crypto— X25519 keypairs, ECDH, HKDF + AES-GCM encryption helpersinternal/relay— Client for device registration, peer management, inbox-based encrypted blob exchangecmd/relay-server— Lightweight HTTP relay supporting device pairing, targeted uploads (/upload/{receiver}), and inbox consumption
Current Limitations (as of launch):
- Relay server is in-memory only (no persistence)
- Sending from the daemon is still placeholder/incomplete
- Background polling and real-time notifications are not yet implemented
- Intended for testing and non-critical use
The relay feature is under active development. The core SSH-based workflow remains the recommended production path.
┌───────────────────────────────────────────────────────────┐
│ TRUST BOUNDARY 1 │
│ Local Machine (fully trusted) │
│ │
│ ┌─────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ User │───▶│ clipbrid │ │ OS Keychain / │ │
│ │ │ │ ged │◀──▶│ token file │ │
│ └─────────┘ └────┬─────┘ └──────────────────┘ │
│ │ │
│ 127.0.0.1:7331 │
│ │ │
└──────────────────────┼────────────────────────────────────┘
│ SSH RemoteForward tunnel
│ (encrypted by SSH)
┌──────────────────────┼────────────────────────────────────┐
│ │ │
│ TRUST BOUNDARY 2 │
│ Remote Host (partially trusted) │
│ │ │
│ 127.0.0.1:7331 │
│ │ │
│ ┌─────▼──────┐ ┌──────────────────┐ │
│ │ pastelocal- │ │ token file │ │
│ │ remote │◀──▶│ (mode 0600) │ │
│ └─────┬──────┘ └──────────────────┘ │
│ │ │
│ ┌─────▼──────┐ │
│ │ ~/.cache/ │ │
│ │ pastelocal/│ │
│ └────────────┘ │
│ │
│ ┌──────────────────────────────────────┐ │
│ │ Agentic coding tools (trust level: │ │
│ │ you chose to run them) │ │
│ └──────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────┘
Trust boundary 1: The local machine is fully trusted. The daemon binds to loopback only, so no network-facing attack surface exists. The token is stored in the OS keychain with a file fallback.
Trust boundary 2: The remote host is partially trusted. We trust the host admin (you) but assume other users or processes on the remote host are untrusted. The token file is mode 0600 (owner-only), and the cached images are also mode 0600.
Between boundaries: The SSH tunnel provides encryption and integrity. The tunnel is established by the user's SSH client, not by pastelocal.
Protocol version: 1
All responses are JSON. Successful responses include "ok": true. Error
responses include "ok": false plus code, error, and fix_hint.
Request:
GET /clipboard HTTP/1.1
Host: 127.0.0.1:7331
Authorization: Bearer <token>
Success response (200):
{
"ok": true,
"image": "<base64-encoded PNG>",
"format": "png",
"byte_count": 123456,
"captured_at": "2025-01-15T10:30:00Z"
}Error response (4xx/5xx):
{
"ok": false,
"code": "CB2001",
"error": "Invalid auth token",
"fix_hint": "Re-run `pastelocal add-host <host>` to sync the token."
}Success response (200):
{"ok": true}Success response (200):
{
"ok": true,
"protocol_version": 1,
"binary_version": "0.1.0"
}User types /paste in their agentic coding tool on the remote host
│
▼
The tool reads ~/.claude/commands/paste.md
│
▼
The tool runs: pastelocal-remote
│
▼
pastelocal-remote reads token from ~/.config/pastelocal/token
│
▼
pastelocal-remote → GET /version → pastelocald (via SSH tunnel)
│ (check protocol compatibility)
▼
pastelocal-remote → GET /clipboard → pastelocald (via SSH tunnel)
│
├─ pastelocald: acquire semaphore slot
├─ pastelocald: check rate limit
├─ pastelocald: validate Bearer token (constant-time compare)
├─ pastelocald: lock mutex, read OS clipboard via platform tool
├─ pastelocald: check image size ≤ max_image_bytes
├─ pastelocald: base64-encode PNG
├─ pastelocald: write audit log (if configured)
└─ pastelocald: release mutex and semaphore
│
▼
pastelocal-remote receives JSON response
│
├─ base64-decode image
├─ write to ~/.cache/pastelocal/pastelocal-<ts>-<rand6>.png (0600)
└─ print absolute path to stdout
│
▼
The tool reads the image file via its Read tool
│
▼
The tool deletes the file via rm
│
▼
The tool confirms: "Got it, image attached."
File format: TOML
Default path: ~/.config/pastelocal/config.toml
Override: --config flag or PASTELOCAL_CONFIG_DIR env var
Env overrides: PASTELOCAL_PORT, PASTELOCAL_LOG_LEVEL
| Key | Default | Description |
|---|---|---|
port |
7331 |
Loopback listen port |
transport |
"tcp" |
Transport protocol (currently TCP only) |
log_level |
"info" |
Log level: debug, info, warn, error |
max_image_bytes |
52428800 (50 MiB) |
Maximum clipboard image size |
max_in_flight |
4 |
Max concurrent clipboard reads |
rate_limit_per_minute |
60 |
Token-bucket rate limit |
audit_log |
"" |
Path to JSON-lines audit log (empty = disabled) |
macos.pngpaste_path |
"" |
Override pngpaste binary path |
linux.clipboard_tool |
"auto" |
Clipboard tool: auto, wl-paste, xclip |
Each host is stored as a nested table:
[hosts.myserver]
added_at = 2025-01-15T10:30:00Z
remote_port = 7331
remote_user = "deploy"
remote_path = "~/.local/bin/pastelocal-remote"
termius = falseConfig is saved atomically (write to temp file, fsync, rename). SIGHUP triggers a live reload without restart.
- Plist path:
~/Library/LaunchAgents/com.pastelocal.daemon.plist - Label:
com.pastelocal.daemon - RunAtLoad: true
- KeepAlive: true
- Stdout log:
~/Library/Logs/pastelocal.log - Stderr log:
~/Library/Logs/pastelocal.err - Commands:
launchctl load/unload,launchctl printfor PID
- Unit path:
~/.config/systemd/user/pastelocal.service - Type:
simple - Restart:
on-failure(10s delay) - WantedBy:
default.target - Commands:
systemctl --user start/stop/restart/enable/status - Logs:
journalctl --user -u pastelocal.service
Both service types are installed and managed by pastelocal init,
pastelocal start, pastelocal stop, and pastelocal uninstall.