Files
skills/tests/test_hub_settings.py
T
Kevin Veen-BirkenbachandClaude Opus 5.5 8ba45efd04 feat(hub): kvb-hub review hub with passkey-signed approvals
The kvb-hub is a local review hub in a container bound to 127.0.0.1: one page, JSON APIs and an MCP endpoint for the operator's inbox, asks, decisions, staged-commit reviews, the radio stream, the agent graph, the worktree tree, push-to-talk and read-aloud radio (Piper, whisper.cpp) and an optional LLM command line. The hub is only a relay. The host executors in hub/client (the runner user service, the PermissionRequest hook and the approval CLI the commit skills call) verify a WebAuthn passkey signature against keys pinned outside every sandbox, refuse replays and fail closed. scripts/hub.sh with make hub-install, hub-check, hub-pin, hub-runner and hub-hook installs and checks the hub; hub-hook writes ~/.claude/settings.json after a backup. make watch-check shows a stuck watch hook. The new kvb-git-diff skill and the commit skills route reviews through the hub; the test_hub_* suites replace test_hub.py.

Security review (kvb-think-criticize, three skeptics) fixed four holes: signed rules auto-approved chained shell commands; the client and LLM openers followed redirects, the LLM one with its bearer key; the worktree scan ran git fsmonitor and filter commands from agent-writable repos; compose set no memory, pids or health limits. Documented until core requirement 093, on the operator's word: the hub page is trusted while signing and pinning, local processes can forge the unsigned operator actions, and the container reads the host /tmp. Committed on the operator's word despite the gate: the passkey path is not yet verified live.

Accepted MEDUSA false positives (137 findings on 115 lines, rows in security-accepted.jsonl): EXA002 x17, MEDUSA-MM-SCAN-058 x12, B603 x8, MEDUSA-FTSC-SCAN-089 x7, MEDUSA-PRIV-SCAN-762 x6, AIC-EXFIL-005 x5, EXA006 x5, MEDUSA-FTSCAN-231 x5, WEB-SSRF x5, MEDUSA-INF-SCAN-2057 x3, RH005 x3, TC001 x3, LO002 x2, MEDUSA-AEXP-SCAN-096 x2, MEDUSA-CGS-SCAN-028 x2, MEDUSA-INF-SCAN-1350 x2, MEDUSA-INF-SCAN-2369 x2, MEDUSA-INF-SCAN-2440 x2, MEDUSA-INF-SCAN-2638 x2, MLSC-SERIAL-002 x2, PL002 x2, PQC005 x2, SAST-PATH-001 x2, TC004 x2, TC005 x2, TC006 x2, TC007 x2, AA-146, EXA007, FINE-050, GB-ENCODE-001, GB-ENCODE-003, MEDUSA-CGS-SCAN-029, MEDUSA-CGS-SCAN-103, MEDUSA-DP-SCAN-1034, MEDUSA-DP-SCAN-1916, MEDUSA-DP-SCAN-954, MEDUSA-EXF26-TOOL-004, MEDUSA-FT-TRIGGER-005, MEDUSA-INF-SCAN-2206, MEDUSA-INF-SCAN-2457, MEDUSA-INF-WEB-003, MEDUSA-INFRA-SCAN-030, MEDUSA-MP-SCAN-1135, MEDUSA-RLHF-POISON-002, MEDUSA-RT-SCAN-006, MM-ENCODE-001, MP-124, PQC001, PQC002, PQC006, RP001, TC009, TC010, WEB-FLASK-004.

🎯 Fix: 80 %
💥 Break: 4 %
💸 Cost: 129 %
⏱️ Time: –
🛡️ Security: 100 %
💰 Value: ?
🏆 Score: 60
🚦 Gate: ⛔ fix 80 % < 90; cost 129 % > 100: ask the operator before the commit

```
/home/kevinveenbirkenbach/Repositories/github.com/kevinveenbirkenbach/skills/.worktrees/feature/kvb-hub
```

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 18:46:40 +00:00

133 lines
7.3 KiB
Python

from __future__ import annotations
import json
import unittest
from hub_case import HubCase
from hub_support import STREAMS
from kvb_hub import settings
class TestHubSettings(HubCase):
def settings_payload(self, streams: list[dict]) -> dict:
colors = {"top": "#101820", "bottom": "#405060", "accent": "#ffaa00"}
speech = {"on": True, "classes": ["direct", "pan-pan", "mayday"], "muted": ["name:Docs", "s1"]}
space = {"graph": {"axis": "time", "yaw": -45.5, "pitch": 12.25, "zoom": 1.5, "pan_x": -30.0, "pan_y": 12.0}, "sky": {"azimuth": 225.0, "altitude": 20.0, "fov": 120.0}}
return {
**settings.SETTINGS, "latitude": 52.52, "longitude": 13.405, "theme": "custom", "colors": colors, "music": {"on": True, "volume": 0.3}, "streams": streams, "speech": speech,
"space": space,
}
def media_src(self) -> str:
csp = self.get("/")[1]["content-security-policy"]
return next(part.strip() for part in csp.split(";") if part.strip().startswith("media-src"))
def test_settings_are_guarded_validated_and_persisted(self):
self.assertEqual(self.snapshot()["settings"], settings.SETTINGS)
payload = self.settings_payload(STREAMS)
good = self.guard(self.host)
for headers in ({k: v for k, v in good.items() if k != "X-Kvb-Hub"}, {k: v for k, v in good.items() if k != "Origin"}, {**good, "Origin": "http://evil.example"}):
with self.subTest(headers=headers):
self.assertEqual(self.post_json("/api/settings", payload, headers)[0], 403)
self.assertFalse((self.state / "settings.json").exists())
stream = STREAMS[0]
for broken in (
{**payload, "latitude": 91},
{**payload, "theme": "neon"},
{**payload, "extra": 1},
{**payload, "colors": {**payload["colors"], "accent": "red"}},
{**payload, "music": {"on": "yes", "volume": 0.3}},
{**payload, "streams": [{**stream, "url": "javascript:alert(1)"}]},
{**payload, "streams": [{**stream, "url": "https://evil.example;script-src"}]},
{**payload, "streams": [{**stream, "url": "https://a.example 'unsafe-inline'"}]},
{**payload, "streams": [{**stream, "url": "https://user:pw@a.example/"}]},
{**payload, "streams": [{**stream, "days": [7]}]},
{**payload, "streams": [{**stream, "days": [1, 1]}]},
{**payload, "streams": [{**stream, "from": "09:00", "to": "09:00"}]},
{**payload, "streams": [{**stream, "to": "25:00"}]},
*({**payload, "speech": {**payload["speech"], **change}} for change in (
{"on": "yes"},
{"classes": ["direct", "pan-pan"]},
{"classes": ["mayday", "shout"]},
{"classes": ["mayday", "mayday"]},
{"classes": "mayday"},
{"muted": "s1"},
{"muted": ["s1", "s1"]},
{"muted": ["two\nlines"]},
{"muted": [""]},
{"muted": [7]},
{"muted": [f"s{n}" for n in range(settings.MUTED_LIMIT + 1)]},
{"extra": 1},
)),
{**payload, "speech": {"on": True, "classes": ["mayday"]}},
{key: value for key, value in payload.items() if key != "speech"},
*({**payload, "space": change} for change in (
"flat",
{"graph": payload["space"]["graph"]},
{**payload["space"], "extra": 1},
{**payload["space"], "sky": [180, 0, 180]},
)),
*({**payload, "space": {**payload["space"], "graph": {**payload["space"]["graph"], **change}}} for change in (
{"axis": "depth"},
{"axis": 1},
{"yaw": 180.5},
{"pitch": "up"},
{"zoom": True},
{"zoom": 0.2},
{"pan_x": 2001},
{"pan_y": float("nan")},
{"roll": 0},
)),
{**payload, "space": {**payload["space"], "graph": {key: value for key, value in payload["space"]["graph"].items() if key != "pan_y"}}},
*({**payload, "space": {**payload["space"], "sky": {**payload["space"]["sky"], **change}}} for change in (
{"azimuth": -1},
{"azimuth": 360.5},
{"altitude": 86},
{"fov": 10},
{"fov": None},
{"fov": float("inf")},
{"zoom": 1},
)),
{key: value for key, value in payload.items() if key != "space"},
):
with self.subTest(broken=broken):
self.assertEqual(self.post_json("/api/settings", broken, good)[0], 400)
status, _, body = self.post_json("/api/settings", payload, good)
self.assertEqual(status, 200, body)
self.assertEqual(json.loads(body), payload)
self.assertEqual(json.loads((self.state / "settings.json").read_text(encoding="utf-8")), payload)
self.assertEqual(self.snapshot()["settings"], payload)
self.assertEqual(settings.Settings(self.state / "settings.json").get(), payload)
self.assertEqual(self.get("/api/settings")[0], 405)
shuffled = {**payload, "speech": {"on": False, "classes": ["mayday", "broadcast", "security"], "muted": ["s1", "Operator", "name:Docs"]}}
status, _, body = self.post_json("/api/settings", shuffled, good)
self.assertEqual(status, 200, body)
self.assertEqual(json.loads(body)["speech"], {"on": False, "classes": ["broadcast", "security", "mayday"], "muted": ["Operator", "name:Docs", "s1"]})
def test_settings_written_before_the_radio_filter_keep_their_values(self):
older = {key: value for key, value in self.settings_payload(STREAMS).items() if key != "speech"}
(self.state / "settings.json").write_text(json.dumps(older), encoding="utf-8")
loaded = settings.Settings(self.state / "settings.json").get()
self.assertEqual(loaded, {**older, "speech": settings.SETTINGS["speech"]})
flat = {key: value for key, value in older.items() if key != "space"}
(self.state / "settings.json").write_text(json.dumps(flat), encoding="utf-8")
self.assertEqual(settings.Settings(self.state / "settings.json").get(), {**flat, "speech": settings.SETTINGS["speech"], "space": settings.SETTINGS["space"]})
(self.state / "settings.json").write_text(json.dumps({**older, "unknown": 1}), encoding="utf-8")
self.assertEqual(settings.Settings(self.state / "settings.json").get(), settings.SETTINGS)
def test_csp_media_src_lists_exactly_the_stream_origins(self):
self.assertEqual(self.media_src(), "media-src 'self'")
streams = [*STREAMS, {**STREAMS[0], "name": "Gleich", "url": "https://Radio.Example:443/zweiter?x=1"}]
status, _, body = self.post_json("/api/settings", self.settings_payload(streams), self.guard(self.host))
self.assertEqual(status, 200, body)
origins = ["http://10.0.0.5:8000", "https://other.example:8443", "https://radio.example"]
self.assertEqual(self.media_src(), " ".join(["media-src 'self'", *origins]))
csp = self.get("/")[1]["content-security-policy"]
self.assertIn("connect-src 'self';", csp)
self.assertEqual(csp.count("radio.example"), 1)
self.assertEqual(self.snapshot()["media"], origins)
if __name__ == "__main__":
unittest.main()