- Added YAML frontmatter to all 8 polish-* skills and sweep skill so Claude can auto-invoke them - New bitcoin-conventions skill with PROUX UX methodology, sats display, address validation, Tor preferences, Lightning patterns - Path-specific rules for containers (security hardening) and frontend (Vue/glassmorphism conventions) - Gitea Actions: nightly security review and weekly dependency audit Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
163 lines
4.3 KiB
Markdown
163 lines
4.3 KiB
Markdown
---
|
|
name: polish-security
|
|
description: Security hardening for Archipelago systemd services, nginx headers, secrets management, and rate limiting. Use when user says "polish security", "harden services", "security headers", "rate limiting", or "secrets management".
|
|
---
|
|
|
|
# Skill: Polish Security
|
|
|
|
Security hardening pass for systemd, nginx, secrets management, and rate limiting.
|
|
|
|
## 1. Systemd Service Hardening
|
|
|
|
File: `image-recipe/configs/archipelago.service`
|
|
|
|
Add these directives to the `[Service]` section:
|
|
```ini
|
|
PrivateTmp=yes
|
|
NoNewPrivileges=true
|
|
ProtectSystem=strict
|
|
ProtectHome=yes
|
|
ReadWritePaths=/var/lib/archipelago
|
|
SystemCallFilter=@system-service
|
|
SystemCallFilter=~@privileged @resources
|
|
```
|
|
|
|
After editing, sync to server and verify:
|
|
```bash
|
|
ssh archipelago@192.168.1.228 "sudo systemd-analyze security archipelago"
|
|
```
|
|
|
|
## 2. Nginx Security Headers
|
|
|
|
File: `image-recipe/configs/nginx-archipelago.conf`
|
|
|
|
### Add HSTS (HTTPS block only):
|
|
```nginx
|
|
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
```
|
|
|
|
### Fix CSP (remove unsafe-inline):
|
|
Replace:
|
|
```nginx
|
|
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self' ws: wss:; frame-src 'self' http://localhost:* http://192.168.*:*;" always;
|
|
```
|
|
|
|
With CSP that uses nonces or hashes for inline scripts/styles. If inline scripts can't be removed yet, document which ones and plan their removal.
|
|
|
|
### Add rate limiting zones:
|
|
```nginx
|
|
# In http block:
|
|
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/s;
|
|
limit_req_zone $binary_remote_addr zone=auth:10m rate=5r/m;
|
|
|
|
# On login/auth endpoints:
|
|
limit_req zone=auth burst=3 nodelay;
|
|
|
|
# On API endpoints:
|
|
limit_req zone=api burst=50 nodelay;
|
|
```
|
|
|
|
### Custom log format (strip tokens):
|
|
```nginx
|
|
log_format no_tokens '$remote_addr - $remote_user [$time_local] "$request_method $uri $server_protocol" $status $body_bytes_sent "$http_referer"';
|
|
access_log /var/log/nginx/archipelago_access.log no_tokens;
|
|
```
|
|
|
|
## 3. Secrets Management
|
|
|
|
### Remove hardcoded RPC credentials from scripts
|
|
File: `scripts/deploy-to-target.sh`
|
|
|
|
Replace:
|
|
```bash
|
|
-e CORE_RPC_USERNAME=archipelago -e CORE_RPC_PASSWORD=archipelago123
|
|
```
|
|
|
|
With:
|
|
```bash
|
|
-e CORE_RPC_USERNAME=archipelago -e CORE_RPC_PASSWORD=$(cat /var/lib/archipelago/secrets/bitcoin-rpc-pass)
|
|
```
|
|
|
|
### Generate secrets on first boot
|
|
File: `scripts/first-boot-containers.sh`
|
|
|
|
Add at the top:
|
|
```bash
|
|
SECRETS_DIR="/var/lib/archipelago/secrets"
|
|
mkdir -p "$SECRETS_DIR"
|
|
chmod 700 "$SECRETS_DIR"
|
|
|
|
# Generate Bitcoin RPC password if not exists
|
|
if [ ! -f "$SECRETS_DIR/bitcoin-rpc-pass" ]; then
|
|
openssl rand -base64 24 > "$SECRETS_DIR/bitcoin-rpc-pass"
|
|
chmod 600 "$SECRETS_DIR/bitcoin-rpc-pass"
|
|
fi
|
|
```
|
|
|
|
### Remove hardcoded credentials from Rust backend
|
|
File: `core/archipelago/src/api/rpc/bitcoin.rs`
|
|
|
|
Replace:
|
|
```rust
|
|
.basic_auth("archipelago", Some("archipelago123"))
|
|
```
|
|
|
|
With:
|
|
```rust
|
|
let rpc_user = std::env::var("ARCHIPELAGO_BITCOIN_RPC_USER").unwrap_or_else(|_| "archipelago".into());
|
|
let rpc_pass = std::env::var("ARCHIPELAGO_BITCOIN_RPC_PASS").unwrap_or_else(|_| "archipelago123".into());
|
|
.basic_auth(&rpc_user, Some(&rpc_pass))
|
|
```
|
|
|
|
## 4. Rate Limiting on Backend
|
|
|
|
File: `core/archipelago/src/api/handler.rs`
|
|
|
|
Add rate limiting to unauthenticated endpoints:
|
|
- `/archipelago/node-message` — 10 req/min per IP
|
|
- `/electrs-status` — 30 req/min per IP
|
|
|
|
Use an in-memory `HashMap<IpAddr, (Instant, u32)>` with cleanup on access.
|
|
|
|
## 5. SSH Hardening
|
|
|
|
File: `scripts/deploy-to-target.sh`
|
|
|
|
Replace:
|
|
```bash
|
|
SSH_OPTS="-o StrictHostKeyChecking=no"
|
|
```
|
|
|
|
With:
|
|
```bash
|
|
SSH_OPTS="-o StrictHostKeyChecking=accept-new"
|
|
```
|
|
|
|
And add SSH key validation:
|
|
```bash
|
|
if [ ! -f "$SSH_KEY" ]; then
|
|
echo "ERROR: SSH key not found at $SSH_KEY"
|
|
exit 1
|
|
fi
|
|
```
|
|
|
|
## Verification Checklist
|
|
|
|
- [ ] `systemd-analyze security archipelago` score < 5.0 (lower is better)
|
|
- [ ] Nginx headers pass: `curl -I http://192.168.1.228 | grep -i 'strict-transport\|content-security\|x-frame'`
|
|
- [ ] No hardcoded passwords in scripts: `grep -rn 'archipelago123' scripts/ core/`
|
|
- [ ] Rate limiting works: rapid-fire requests get 429
|
|
- [ ] SSH key required (no password fallback)
|
|
|
|
## Deploy
|
|
|
|
After changes, sync configs and deploy:
|
|
```bash
|
|
./scripts/deploy-to-target.sh --live
|
|
```
|
|
|
|
Then sync to ISO recipe:
|
|
```bash
|
|
# Run /sync-configs skill
|
|
```
|