root 939dfddb93 llm_team_ui: 4 fixes from 2026-04-30 cross-lineage scrum
Cross-lineage scrum (Opus 4.7 + Kimi K2.6 + Qwen3-coder via the
local-review-harness chatd) on this codebase surfaced 5 BLOCK-class
issues from Opus + a convergent finding from the harness. This
commit lands the 4 surgical fixes; OB-3 (web app runs as root with
fail2ban-client + systemctl reload nginx + writes to
/etc/nginx/banned_ips.conf) needs an architectural split into a
non-root web tier + a privileged sudo wrapper, deferred for its
own session.

OB-1 — log file open at import crashes app on perm error
  Pre-fix: `_sec_handler = logging.FileHandler("/var/log/llm-team-
  security.log")` raised PermissionError at import time on any
  non-root or fresh-install run, killing the app before Flask
  started — failure was silent (no Flask process to inspect logs
  on).
  Fix: try/except, fall back to StreamHandler(sys.stderr) when
  the path is unwritable. App starts; sec_log events still land
  in journald via stderr. LLM_TEAM_SECURITY_LOG env var lets
  operators override the path.

OB-2 — DB password hardcoded in source (CONVERGENT FINDING)
  The `kbuser` Postgres credential
  `IPbLBA0EQI8u4TeM2YZrbm1OAy5nSwqC` was leaked in source here
  AND in voice-ai/audiosocket_bridge.py + voice-ai/sales_assistant.py.
  Caught independently by harness LLM phase (qwen3.5 local) on
  voice-ai earlier today AND Opus on this file just now. Same
  password, same DB (`knowledge_base`) shared between services,
  three reviewers converged.
  Fix: source from LLM_TEAM_DB_DSN env var, fail loud on unset.
  Operator follow-ups:
    1. Rotate the password in Postgres (still in git history;
       redacting source doesn't un-leak it).
    2. Set LLM_TEAM_DB_DSN in /etc/llm-team-ui.env (mode 0600,
       loaded via systemd EnvironmentFile=).
    3. Same DSN env-var pattern needs applying to
       voice-ai/audiosocket_bridge.py:47 once that branch's
       workspace_context WIP lands.

OB-5 — demo_mode default=True ships public access on first boot
  Pre-fix: `_demo_mode = {"active": True, ...}` + the demo branch
  in login_required let users through without a session. Combined
  with /api/run + /api/imagegen proxies, fresh installs were open
  LLM/compute abuse surface from first boot.
  Fix: default to False; LLM_TEAM_DEMO_MODE=1 env override exists
  for the public devop.live deployment systemd unit so the demo
  doesn't need a manual flip on every restart, but everywhere else
  defaults closed.

OB-4 — EXPLOIT_PATTERNS LAN/admin lockout
  Pre-fix: regex matched on `request.path` + query string against
  patterns like UNION / SELECT / ;-- / <script /admin.php. Admin
  URLs containing those keywords in legitimate ways (e.g. a team
  name "select-rebrand" or a docs link /admin/select_a_mode) hit
  3 violations in 60s and auto-banned the admin's IP. No allowlist.
  Fix: bypass the path-based check for authenticated admins from
  an ALLOWLIST_IPS source. Body/UA checks still apply (the prompt-
  injection-as-DoS WARN in the scrum is separate). Combination
  prevents self-ban without weakening the broader scanner defense.

Plus a .gitignore: /.memory/ — the local-review-harness writes
JSONL findings under <repo>/.memory/ when scanning; harness's own
gitignore is at the harness repo root, not here, so without this
the .memory/ dir would show up as untracked on every harness run
against this tree.

Other Opus WARNs deferred:
- Sentinel feeds attacker-controlled UA into LLM prompt → can
  steer ban verdicts. Fix needs prompt-template hardening or
  output-validation gate.
- CSP `'unsafe-inline'` defeats most XSS protection (would break
  inline scripts; needs HTML refactor).
- _rate_limit unbounded dict + per-worker (needs eviction loop or
  Redis-backed counter).
- auth_login first-time setup gated only by COUNT(*)==0 (needs
  network-source restriction or a setup token).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 03:14:08 -05:00
Description
LLM Team UI - Full-stack local AI orchestration platform
9.2 MiB
Languages
Python 97.4%
Shell 2.6%