From f689be3475060826c9d7220c8e5911873ae8c134 Mon Sep 17 00:00:00 2001 From: Sam Rolfe Date: Wed, 14 Oct 2026 11:23:50 +1100 Subject: [PATCH] Initial: network security spec (fail-closed, forward-auth, CrowdSec, scanning) --- .gitignore | 5 ++ AGENTS.md | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++ NOTES.md | 41 ++++++++++++ 3 files changed, 224 insertions(+) create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 NOTES.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c5bdf13 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.env +*.local +__pycache__/ +node_modules/ +.pi/memory-vault/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..1ba598c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,178 @@ +# Network Security + +> Build spec for Paperclip. Follow the phases in section 11 in order. Do not skip a +> phase's Definition of Done. When something is unclear, ask Sam — do not guess. + +--- + +## 1. Purpose and outcome + +Make the lab **fail closed**. Today a new service is exposed to the internet the +moment it exists, and the only thing protecting it is remembering to add auth. +That is backwards. + +**Outcome (the single success statement):** + +> Exposing a service is a deliberate act. By default it is protected — and scans +> against the lab are blocked and visible, not absorbed. + +## 2. Scope + +**In scope** +- Move admin UIs off the public internet. +- One authentication layer in front of the whole `*.lab` wildcard. +- Intrusion detection and auto-banning. +- Secret scanning, code scanning, image scanning. +- A correct-by-default auth helper for our own FastAPI apps. + +**Out of scope** +- Rewriting application features. +- Changing what `*.lab` hosts *are* — only how they are reached. + +## 3. Why this exists — the incident + +On **2026-10-13** an automated scanner found `console.lab.audasmedia.com.au` on the +public internet and exploited a **critical authentication bypass** in the family +console: + +```python +if username is None and basic is not None: + username = basic.username # password never verified +``` + +`HTTPBasic` only parses the header. The code trusted the username alone, so +`curl -u sam:anything` authenticated as admin. The attacker used it to drive +Jervis (the house voice) with secret-exfiltration prompts for two days, unnoticed. + +The bypass is fixed. **This project fixes the conditions that made it matter.** + +## 4. Prerequisites + +| Need | Status / note | +|---|---| +| Caddy | `.35`, already the single reverse proxy for `*.lab` | +| Tailscale | Installed on `.13`, `.27`, `.51`, and the phone (`sams-pixel-8a`) | +| Docker | Everywhere it is needed | +| A decision | `*.lab` stays **public** for genuine public sites. Only admin UIs move. | + +## 5. Functional requirements + +| ID | Requirement | +|---|---| +| FR-1 | **Inventory.** Produce a table of every `*.lab` host: what it is, its backend, and whether it has auth. | +| FR-2 | **Admin UIs are not public.** Console, Portainer, Gitea, Paperclip, and every other admin surface are reachable **only over Tailscale**. | +| FR-3 | **Default-deny at Caddy.** Every `*.lab` host passes through **forward-auth**. A host is protected unless it is on an explicit public allowlist. | +| FR-4 | **SSO + 2FA** (Authelia) in front of the wildcard, with per-host policy. | +| FR-5 | **CrowdSec** reads the Caddy and sshd logs, detects scans, and **auto-bans** via a Caddy bouncer. | +| FR-6 | **Secret scanning.** `gitleaks` as a pre-commit hook on every repo; `trufflehog` over existing history. | +| FR-7 | **Code scanning.** `semgrep` in CI/pre-commit for our own code. | +| FR-8 | **Image scanning.** `trivy` over the running container images. | +| FR-9 | **A correct-by-default auth helper** for FastAPI apps, plus a test asserting **valid username + wrong password → 401**. | +| FR-10 | **A baseline scan** with `nuclei` against `.lab`, so we find issues before the bots do. | + +## 6. Screens and UX flow + +- **Authelia portal** — login + 2FA page in front of protected hosts. +- **CrowdSec dashboard** — who is being banned, and why. +- **The inventory table** (FR-1) — a document, not a UI. + +## 7. Look and feel + +Reuse the Family Home Lab design system for any page we build ourselves. + +- `/home/sam/Docker/Containers/family-home-lab/DESIGN.md` +- `/home/sam/Docker/Containers/family-home-lab/portal/static/tokens.css` + +Authelia and CrowdSec ship their own UIs — do not restyle them. + +## 8. Integrations + +| System | Use | +|---|---| +| **Caddy** (`.35`) | forward-auth, the CrowdSec bouncer, the public allowlist | +| **Tailscale** | admin access without public exposure | +| **Authelia** | SSO + 2FA | +| **CrowdSec** | log parsing + decisions + bouncer | +| **Apprise** (`.35:8210`) | alerts on bans and scan spikes | +| **Gitea** | pre-commit hooks, CI | + +## 9. Technology (locked) + +| Item | Value | +|---|---| +| Reverse proxy | **Caddy** (existing, `.35`) | +| Auth | **Authelia** (forward-auth) | +| IDS/IPS | **CrowdSec** + Caddy bouncer | +| Remote access | **Tailscale** | +| Secret scan | **gitleaks** (pre-commit), **trufflehog** (history) | +| SAST | **semgrep** | +| Image scan | **trivy** | +| DAST | **nuclei** | +| Container | One per service. Own folder, own compose file. | + +## 10. Rules + +1. **Fail closed.** A new host is protected by default; exposure is explicit. +2. **Never remove auth to "fix" a problem.** Fix the auth instead. +3. Do not break genuinely public sites — they go on the allowlist. +4. Alerts go through **Apprise**. +5. One commit and push per phase. +6. Bound every docker command (`timeout`, `--entrypoint sh` when running a shell + inside an image). + +## 11. Build order + +**Phase 1 — Inventory** +Table of every `*.lab` host: purpose, backend, auth, public or not. +*DoD:* the table exists and is complete. **Nothing changes yet.** + +**Phase 2 — Admin UIs off the public internet** +Move admin surfaces to Tailscale-only. Console, Portainer, Gitea, Paperclip first. +*DoD:* admin UIs are **unreachable from the public internet** and reachable over +Tailscale — proven both ways. + +**Phase 3 — CrowdSec + Caddy bouncer** +*DoD:* a deliberate scan from an external host is **banned**, and the ban is visible +in the dashboard. + +**Phase 4 — Authelia forward-auth + allowlist** +*DoD:* a **newly created** `*.lab` host is protected **without anyone remembering +to add auth**. Public sites still work. + +**Phase 5 — Secret scanning** +`gitleaks` pre-commit on every repo; `trufflehog` over history. +*DoD:* findings triaged; every real leak rotated. + +**Phase 6 — Code and image scanning** +`semgrep` + `trivy`. +*DoD:* both run in CI; findings triaged. + +**Phase 7 — Auth helper + test** +A shared FastAPI dependency that verifies the password, plus the test. +*DoD:* the test **fails** against the old bypass pattern and passes against the fix. + +**Phase 8 — Baseline scan + documentation** +`nuclei` against `.lab`; write it all up. +*DoD:* no high findings unreviewed; docs published. + +## 12. Acceptance criteria + +- No admin UI is reachable from the public internet. +- A new `*.lab` host is protected by default. +- A scan from outside is banned automatically and visible. +- Repo history is scanned for secrets and findings are rotated. +- Semgrep, trivy, and the auth test run in CI. +- A `nuclei` baseline exists. + +## 13. Decisions and open questions + +Resolved: +1. `*.lab` stays **public** for genuine public sites. Only admin UIs move. +2. Scanning is accepted as normal — the goal is **control**, not zero noise. +3. Authelia over Authentik (lighter, fits Caddy forward-auth). + +Remaining: +1. Which hosts are **genuinely public**? (Needs Sam's list for the allowlist.) +2. Should CrowdSec run in **ban** mode from the start, or observe first? +3. Where do the alerts go — Apprise to which channel? +4. Do we add `nuclei`/`semgrep` to Gitea CI, or run them by hand? diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 0000000..ec3701c --- /dev/null +++ b/NOTES.md @@ -0,0 +1,41 @@ +The principle to build around +Fail closed. A new service should be protected by default, not by remembering. Right now +.lab + is open-by-default — that's what made the console bypass matter. +Tier 1 — do these first (highest value, least work) +1. Move admin UIs to Tailscale, keep +.lab + for public sites only. +You already have Tailscale on +.13 +, +.27 +, +.51 + and the phone. Admin dashboards (console, Portainer, Gitea, Paperclip) don't need the public internet — you get remote access anyway, with no exposure. Zero new software. This alone removes most of your attack surface. +2. Put one auth layer in front of the whole wildcard. +Caddy forward-auth → Authelia (or Authentik). Every *.lab host goes through one policy: SSO, 2FA, default-deny, with an explicit allowlist for genuinely public sites. Then "I forgot to add auth" can't happen. +3. Add CrowdSec. +It tails your Caddy and sshd logs, detects scans like the one you just saw, and auto-bans via a Caddy bouncer. You already accept scanning — this turns it from noise into blocked traffic, and gives you a dashboard of who's probing. +Tier 2 — stop the two failure modes you've already hit +4. gitleaks + trufflehog. +You had a key leak on Gitea. gitleaks as a pre-commit hook stops new leaks; trufflehog scans existing repos and history for what's already there. Cheapest possible fix for a problem that already cost you once. +5. semgrep. +SAST for your own code — exactly the class of bug found today. It runs in seconds, has auth/security rulesets, and fits a pre-commit or CI step. +6. trivy. +Scans your container images and filesystems for known CVEs. With ~50 containers you're running a lot of images you never inspect. +Tier 3 — worth doing, not urgent +7. nuclei — run your own scans against +.lab + so you find the issues before the bots do. Also tells you what's actually exposed. +8. A correct-by-default auth helper + one test. +For your FastAPI apps: a shared dependency that verifies the password, and a test that asserts valid username + wrong password → 401. That exact test would have caught today's bug on the first run. +What you already have +• +npq, sfw, vet — npm supply-chain (on +.13 +). +• +Tailscale — underused; see Tier 1 item 1. +• +Caddy — the natural place for forward-auth and the CrowdSec bouncer.