Dorian 4e54b8bd4d feat: add YAML frontmatter, bitcoin-conventions skill, path rules, and Gitea CI
- 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>
2026-03-15 12:35:17 +00:00

4.3 KiB

name, description
name description
polish-security 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:

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:

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):

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Fix CSP (remove unsafe-inline):

Replace:

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:

# 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):

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:

-e CORE_RPC_USERNAME=archipelago -e CORE_RPC_PASSWORD=archipelago123

With:

-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:

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:

.basic_auth("archipelago", Some("archipelago123"))

With:

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:

SSH_OPTS="-o StrictHostKeyChecking=no"

With:

SSH_OPTS="-o StrictHostKeyChecking=accept-new"

And add SSH key validation:

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:

./scripts/deploy-to-target.sh --live

Then sync to ISO recipe:

# Run /sync-configs skill