2026-03-15 12:35:17 +00:00
---
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".
---
2026-03-08 08:06:52 +00:00
# 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
```