#+TITLE: bee-stack: a homelab for bug bounty research #+DATE: 2026-06-16 #+FILETAGS: :bash:networking:linux:python:kubernetes:homelab: #+DESCRIPTION: * The problem No broadband. Rural NC. One Android phone tethered over USB. * The build A Raspberry Pi 4 runs hostapd and dnsmasq, WiFi AP, DHCP, and NAT for the cluster. A TP-Link managed switch connects three nodes. All internet exits through the phone. #+begin_src shell Three nodes run Talos Linux (v1.12.6, Kubernetes v1.35.2): [ WAN / Internet ] │ (USB Tethering via Android / Mullvad) ┌──────▼────────────────────────────────────────┐ │ LoqArch Workstation (Arch Linux + Doom Emacs) │ └──────┬────────────────────────────────────────┘ │ (wlp8s0 / 10.10.10.243) ┌──────▼────────────────────────────────────────┐ │ Raspberry Pi 4 (AP + NAT + dnsmasq) │ └──────┬────────────────────────────────────────┘ │ (eth0 / 10.10.10.1) [ TP-Link Managed Switch ] │ ├─► talos-cp (10.10.10.10) ── k8s Control Plane │ ├─► talos-w1 (10.10.10.11) ── Ollama (mirror), Qdrant, Storage │ └─► talos-w2 (10.10.10.12) ── Ollama (primary), Gitea, Postgres, bee-tools #+end_src Everything uses local-path RWO PVCs. No cloud. No NFS. No CNI drama. Just pure pain and suffering. * bee-stack The "beeattack" namespace is the whole point. All services are internal; NodePorts forward through the control-plane IP. ** Ollama Two instances sharing model storage via PVCs. Primary on w2, mirror on w1. Custom model =bee-raw= — derived from a fine-tuned triage base, system-prompted for direct security research with no disclaimers, temperature 0.2, 8k context. Built inside the pod with a Modelfile. Also runs =gemma4= and =nomic-embed-text= for embeddings. ** Qdrant Three collections: =docs= (reference material, 768-dim cosine), =notes= (org-denote vault + research notes), =code= (gitea repo contents). All ingested via a Python chunker that calls the nomic embedding model through Ollama's API. Gotchas worth noting: Qdrant 1.17+ changed the upsert format. The old =points= key breaks silently on some operations. WAL corruption happens if you patch the deployment before the pod finishes terminating — wait for full pod termination before reapplying. ** Postgres Structured side of the research store: findings, targets, LLM sessions, research notes, doc full-text with a =tsvector= index, and a tool call log. Postgres FTS complements the Qdrant semantic search — exact-match queries go to Postgres, fuzzy conceptual queries go to Qdrant. Port-forwarded on 15432 locally. NodePort is technically open but firewalled by Talos. ** Gitea Self-hosted git. Registration disabled. Push webhook fires on every commit → bee-tools re-ingests changed files into =qdrant/code=. Token stored at =~/.config/bee-tools-token=. ** bee-tools FastAPI bridge. The connective tissue. Endpoints cover: RAG search (single collection and merged multi-collection), git file read/write/search, Postgres note and query operations, session save/load, doc ingest and FTS, bug bounty findings CRUD, targets, tool call logging, and a browser UI. Source lives in =~/Homelab/bee-stack/bee-tools.py=. Deployed via configmap — edit file, recreate configmap, rollout restart. One rule: all route definitions must appear before the =if __name__= block. FastAPI silently drops anything after it. * Emacs integration The workstation (LoqArch, Arch Linux) runs Doom Emacs. All of the above surfaces through =gptel= via a custom config at =~/.config/doom/custom/llm-gptel.el=. Keybinds live under =SPC l=: | Key | Action | |---------+-----------------------------------------------| | =SPC l l= | Open gptel chat buffer | | =SPC l B= | Switch to bug bounty mode | | =SPC l T= | Start recon on target domain | | =SPC l R= | Toggle RAG auto-inject | | =SPC l q= | Manual RAG search → =*bee-rag*= buffer | | =SPC l Q= | Postgres FTS → =*bee-docs*= buffer | | =SPC l w= | Web search → =*bee-web*= buffer | | =SPC l A= | Security analysis of current buffer/region | | =SPC l e= | Run shell command on LoqArch | | =SPC l F= | Save finding to Postgres | | =SPC l L= | List findings → =*bee-findings*= buffer | RAG auto-inject runs before every prompt: pulls the top-scoring chunks from Qdrant across all three collections, filters below a 0.72 score threshold, injects up to 5 chunks into the system context. The model sees relevant docs without being asked. gptel tools (callable by the model during a session): =rag_search=, =doc_search=, =web_search=, =url_fetch=, =shell_exec=, =file_read=, =file_write=, =git_read=, =git_write=, =git_search=, =db_note=, =db_query=, =vuln_finding=. * Bug bounty workflow The whole stack exists to support this: #+begin_example SPC l B → bug bounty mode (no-disclaimer system prompt) SPC l T → enter target → LLM recon analysis auto-starts SPC l w → search target intel SPC l W → fetch JS, pages, endpoints SPC l e → run local tools (curl, ffuf, nmap) SPC l A → analyze interesting code or responses SPC l F → confirmed finding → save to Postgres SPC l L → review all findings SPC X v → org-capture detailed report #+end_example Findings track: target, title, severity, description, PoC, impact, CVSS, status, and bounty paid. Status flow: =new → triaged → accepted → resolved= (or =duplicate= / =wontfix=). * What actually makes this interesting Most homelab LLM setups front-end a single Ollama instance. This one is built around a research loop: 1. Docs and notes are embedded and searchable semantically and via FTS. 2. Every LLM session has access to the full tool set — search, shell, git, DB — without any manual copy-paste. 3. Findings feed back into the knowledge base. A resolved vuln becomes a note, gets embedded, and shows up in future sessions on similar targets. 4. The whole thing runs on a tethered phone connection. Nothing about the stack assumes reliable or fast internet. The constraint shaped the design. Local-first wasn't a preference — it was the only option. * Code [[https://gitea.bee.local/yopi/bee-stack][bee-stack]] (self-hosted, not public) #+begin_src bash ~/Homelab/bee-stack/ ├── bee-tools.py # FastAPI service ├── bee-stack.sh # deploy postgres + bee-tools ├── bee-tools-bb.sh # deploy findings/targets schema + elisp └── fix-bee-tools.sh # clean rebuild + redeploy #+end_src