A lightweight, self-hosted clipboard sharing tool for your local network. Share text snippets instantly between devices on your LAN.
- Web UI: Simple, responsive interface for pasting and viewing snippets
- Image support: Paste images directly (Ctrl+V), upload files, or send via API
- Real-time sync: SSE (Server-Sent Events) for instant updates across devices
- CLI-friendly: Full REST API for curl and scripts
- Local-only: Restricted to private network IPs by default (RFC1918)
- Secure: XSS protection, security headers, rate limiting
- Minimal: Single binary, no external dependencies
- Optional persistence: In-memory by default, SQLite for persistence
- Large files: Supports up to 10MB per snippet (configurable)
# Build
go build -o crosssite .
# Run (default: http://0.0.0.0:8787)
./crosssite
# With persistence
./crosssite --persist --db-path ./clips.db
# Custom port and admin token
./crosssite --port 9000 --admin-token mysecret# Build and run
docker compose up -d
# Or manually
docker build -t crosssite .
docker run -d -p 8787:8787 --name crosssite crosssite-
Build the image on Unraid:
cd /mnt/user/appdata git clone <repo-url> crosssite-src cd crosssite-src docker build -t crosssite:latest .
-
Add container in Unraid:
- Go to Docker tab → Add Container
- Use the
unraid-template.xmlas reference, or manually configure: - Repository:
crosssite:latest - Port:
8787→8787 - Path:
/mnt/user/appdata/crosssite→/data - Variables:
PERSIST=trueDB_PATH=/data/crosssite.dbTTL_HOURS=168(7 days)ADMIN_TOKEN=your-secret(optional)
-
Or use docker-compose:
cd /mnt/user/appdata/crosssite-src docker compose up -d
Access at http://<unraid-ip>:8787
| Env Variable | Flag | Default | Description |
|---|---|---|---|
PORT |
--port |
8787 |
Server port |
BIND |
--bind |
0.0.0.0 |
Bind address |
MAX_SNIPPET_BYTES |
--max-snippet-bytes |
65536 |
Max snippet size (64KB) |
MAX_SNIPPETS |
--max-snippets |
200 |
Max stored snippets |
PERSIST |
--persist |
false |
Enable SQLite persistence |
DB_PATH |
--db-path |
crosssite.db |
SQLite database path |
TTL_HOURS |
--ttl-hours |
0 |
Auto-delete after N hours (0=disabled) |
ADMIN_TOKEN |
--admin-token |
`` | Token for protected operations |
ALLOW_CIDRS |
--allow-cidrs |
`` | Additional allowed CIDRs |
TRUST_PROXY |
--trust-proxy |
false |
Trust X-Forwarded-For header |
# JSON
curl -X POST http://192.168.1.100:8787/api/snippets \
-H "Content-Type: application/json" \
-d '{"text":"Hello from curl!"}'
# Plain text
curl -X POST http://192.168.1.100:8787/api/snippets \
-d 'Hello from curl!'
# Pipe content
echo "clipboard content" | curl -X POST http://192.168.1.100:8787/api/snippets -d @-
# From file
curl -X POST http://192.168.1.100:8787/api/snippets -d @myfile.txt# Upload image file (multipart)
curl -X POST http://192.168.1.100:8787/api/snippets \
-F "file=@screenshot.png"
# JSON with base64 data
curl -X POST http://192.168.1.100:8787/api/snippets \
-H "Content-Type: application/json" \
-d "{\"type\":\"image\",\"mime_type\":\"image/png\",\"data\":\"$(base64 -i screenshot.png)\"}"
# Pipe from clipboard (macOS)
osascript -e 'get the clipboard as «class PNGf»' | xxd -r -p | base64 | \
xargs -I {} curl -X POST http://192.168.1.100:8787/api/snippets \
-H "Content-Type: application/json" \
-d '{"type":"image","mime_type":"image/png","data":"{}"}'Response:
{
"id": 1,
"text": "Hello from curl!",
"created_at": "2024-01-15T10:30:00Z"
}# Get last 50 (default)
curl http://192.168.1.100:8787/api/snippets
# Get last 10
curl http://192.168.1.100:8787/api/snippets?limit=10Response:
[
{"id": 2, "text": "newest", "created_at": "2024-01-15T10:31:00Z"},
{"id": 1, "text": "older", "created_at": "2024-01-15T10:30:00Z"}
]curl http://192.168.1.100:8787/api/snippets/latest# If ADMIN_TOKEN is set
curl -X DELETE http://192.168.1.100:8787/api/snippets \
-H "Authorization: Bearer your-token"
# Alternative header
curl -X DELETE http://192.168.1.100:8787/api/snippets \
-H "X-Admin-Token: your-token"
# Query parameter
curl -X DELETE "http://192.168.1.100:8787/api/snippets?token=your-token"# Stream new snippets as they arrive
curl -N http://192.168.1.100:8787/api/streamEvents:
event: connected
data: {"clients": 3}
event: snippet
data: {"id": 1, "text": "new snippet", "created_at": "..."}
curl http://192.168.1.100:8787/healthBy default, CrossSite only accepts connections from:
127.0.0.0/8- Localhost10.0.0.0/8- RFC1918 private172.16.0.0/12- RFC1918 private192.168.0.0/16- RFC1918 private::1/128- IPv6 localhostfc00::/7- IPv6 privatefe80::/10- IPv6 link-local
Public IPs will receive a 403 Forbidden response.
Add custom CIDRs with --allow-cidrs:
./crosssite --allow-cidrs "203.0.113.0/24,198.51.100.0/24"If running behind nginx/Caddy/Traefik, enable proxy trust:
./crosssite --trust-proxyThis will respect X-Forwarded-For and X-Real-IP headers.
Protect the clear endpoint with a token:
./crosssite --admin-token "your-secret-token"Built-in rate limiting: 10 requests/second with burst of 20 per IP.
All responses include:
X-Content-Type-Options: nosniffX-Frame-Options: DENYX-XSS-Protection: 1; mode=blockReferrer-Policy: strict-origin-when-cross-originContent-Security-Policy: default-src 'self'; ...
- Go 1.21+
- GCC (for SQLite CGO)
# Standard build
go build -o crosssite .
# With optimizations
go build -ldflags="-w -s" -o crosssite .
# Cross-compile (Linux)
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o crosssite-linux .
# Without SQLite (in-memory only)
go build -tags nosqlite -o crosssite .go test -v ./...- Open
http://192.168.1.100:8787on your phone - Paste a link or text
- Click "Share"
- On your laptop, the snippet appears instantly
- Click "Copy" to grab it
# .bashrc alias to share clipboard
alias share='pbpaste | curl -sX POST http://192.168.1.100:8787/api/snippets -d @- && echo "Shared!"'
# Get latest snippet
alias grab='curl -s http://192.168.1.100:8787/api/snippets/latest | jq -r .text'
# Watch for new snippets
alias watch-clips='curl -sN http://192.168.1.100:8787/api/stream'# Share clipboard
Get-Clipboard | Invoke-RestMethod -Method Post -Uri "http://192.168.1.100:8787/api/snippets" -Body $_
# Get latest
(Invoke-RestMethod -Uri "http://192.168.1.100:8787/api/snippets/latest").text | Set-Clipboardcrosssite/
├── main.go # Entry point, config
├── server.go # HTTP server, routing
├── handlers.go # API handlers
├── middleware.go # Access control, security, rate limiting
├── storage.go # In-memory ring buffer
├── sqlite.go # SQLite persistence
├── sse.go # Server-Sent Events
└── static/ # Embedded frontend
├── index.html
├── style.css
└── app.js
MIT