compute-guard: Hub conditions (90-day cap, named approver, CODEOWNERS, engine-port noise cut)

- non-structural exemptions need approved_by (windy-hub|windy-mind) and expire within 90 days;
  a longer amnesty simply does not apply and is reported (OVER-CAP). compute-door/guard-self: yearly.
- .github/CODEOWNERS on the allow-lists + guard.
- engine-port rule skips contracts/schemas/specs/openapi dirs and *.json (53 baseline hits, was 57).
- tests: findings carry kind+name never the value; shipped allow file obeys its own rules.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Kit OC5
2026-10-02 18:13:18 -04:00
parent 5fa1e652ae
commit 5347c11f69
4 changed files with 82 additions and 6 deletions

7
.github/CODEOWNERS vendored Normal file
View File

@@ -0,0 +1,7 @@
# Changes to the guard allow-lists / exemptions need review by the account owner on GitHub, AND an
# approved_by (windy-hub | windy-mind) on every non-structural entry, which the guard enforces itself
# (scripts/compute_guard.py: load_allow). A lane never approves its own exemption (Hub 10-02).
/ci/compute-guard-allow.yml @sneakyfree
/ci/secret-guard-allow.yml @sneakyfree
/ci/ci-hygiene-allow.yml @sneakyfree
/scripts/compute_guard.py @sneakyfree

View File

@@ -78,16 +78,18 @@ def test_allow_list_needs_a_reason_per_entry(tmp_path):
def _entry(**kw): def _entry(**kw):
base = dict(repo="x", paths=["*"], reason="r", exemption="owner-approved", expires="2099-01-01") base = dict(repo="x", paths=["*"], reason="r", exemption="owner-approved", expires="2099-01-01",
approved_by="windy-hub")
base.update(kw) base.update(kw)
lines = ["allow:", " - repo: x", " paths: ['*']", " reason: r"] lines = ["allow:", " - repo: x", " paths: ['*']", " reason: r"]
for k in ("exemption", "expires"): for k in ("exemption", "expires", "approved_by"):
if base.get(k) is not None: if base.get(k) is not None:
lines.append(f" {k}: {base[k]}") lines.append(f" {k}: {base[k]}")
return "\n".join(lines) + "\n" return "\n".join(lines) + "\n"
def test_allow_entries_need_a_named_exemption_and_an_expiry(tmp_path): def test_allow_entries_need_a_named_exemption_and_an_expiry(tmp_path):
from datetime import date
f = tmp_path / "a.yml" f = tmp_path / "a.yml"
f.write_text(_entry(exemption=None)) f.write_text(_entry(exemption=None))
with pytest.raises(ValueError): with pytest.raises(ValueError):
@@ -101,8 +103,8 @@ def test_allow_entries_need_a_named_exemption_and_an_expiry(tmp_path):
f.write_text(_entry(expires="someday")) f.write_text(_entry(expires="someday"))
with pytest.raises(ValueError): with pytest.raises(ValueError):
cg.load_allow(f) cg.load_allow(f)
f.write_text(_entry()) f.write_text(_entry(expires="2026-12-01"))
assert len(cg.load_allow(f)) == 1 assert len(cg.load_allow(f, today=date(2026, 10, 2))) == 1
def test_expired_exemption_stops_excusing_and_is_reported(tmp_path): def test_expired_exemption_stops_excusing_and_is_reported(tmp_path):
@@ -330,3 +332,39 @@ def test_ollama_in_added_pr_lines_only():
"+URL = 'http://veron:11434/api/chat'\n") "+URL = 'http://veron:11434/api/chat'\n")
got = cg.parse_added("some-repo", diff, []) got = cg.parse_added("some-repo", diff, [])
assert [(f.kind, f.line) for f in got] == [("veron ollama", 2)] assert [(f.kind, f.line) for f in got] == [("veron ollama", 2)]
def test_non_structural_exemptions_need_an_independent_approver_and_a_90_day_cap(tmp_path):
from datetime import date
f = tmp_path / "a.yml"
f.write_text(_entry(approved_by=None))
with pytest.raises(ValueError):
cg.load_allow(f, today=date(2026, 10, 2))
f.write_text(_entry(approved_by="windy-chat")) # a lane may not approve itself/another lane
with pytest.raises(ValueError):
cg.load_allow(f, today=date(2026, 10, 2))
f.write_text(_entry(expires="2026-12-31")) # exactly 90 days: fine
assert len(cg.load_allow(f, today=date(2026, 10, 2))) == 1
f.write_text(_entry(expires="2027-01-01")) # 91 days: does NOT apply, and is reported
assert cg.load_allow(f, today=date(2026, 10, 2)) == [] and cg.OVERCAP
f.write_text(_entry(exemption="compute-door", approved_by=None, expires="2027-10-02"))
assert len(cg.load_allow(f, today=date(2026, 10, 2))) == 1 # structural: yearly, no approver field
def test_shipped_allow_file_obeys_its_own_rules():
allow = cg.load_allow()
assert allow and not cg.EXPIRED and not cg.OVERCAP
for e in allow:
if e["exemption"] not in cg.STRUCTURAL:
assert e["approved_by"] in cg.APPROVERS
def test_findings_carry_kind_and_name_never_the_value_and_ports_skip_contracts():
for line, kind in [("ELEVENLABS_API_KEY=sk_live_SUPERSECRET123456789", "voice-ai key"),
('DEEPGRAM_API_KEY = "dg-VALUE-0123456789abcdef"', "voice-ai key")]:
hits = cg.scan_line("app/x.py", line)
assert [k for k, _ in hits] == [kind]
assert all("SUPERSECRET" not in m and "VALUE" not in m for _, m in hits)
assert cg.scan_line("engine/contracts/ops.mcp.v1.json", '"url": "http://h:8099/x"') == []
assert cg.scan_line("services/api/openapi/spec.json", '"url": "http://h:8099/x"') == []
assert [k for k, _ in cg.scan_line("deploy/docker-compose.yml", " - 8099:8099 # :8099")] == ["talk engine port"]

View File

@@ -4,7 +4,8 @@
# relative to the repo root. Owner of this file: Windy Git lane (13); changes # relative to the repo root. Owner of this file: Windy Git lane (13); changes
# go through the orchestrator. EVERY entry needs `exemption` (local-user-hardware | owner-approved | # go through the orchestrator. EVERY entry needs `exemption` (local-user-hardware | owner-approved |
# compute-door | guard-self) and `expires` (YYYY-MM-DD): nothing gets permanent amnesty (Mind 10-02); # compute-door | guard-self) and `expires` (YYYY-MM-DD): nothing gets permanent amnesty (Mind 10-02);
# an expired entry stops excusing code on that date and shows in the guard report. Source of the first entries: COMPUTE_BYPASS_AUDIT.md. # non-structural exemptions also need approved_by (windy-hub | windy-mind; a lane never approves its own)
# and expire within 90 days; an expired entry stops excusing code on that date and shows in the guard report. Source of the first entries: COMPUTE_BYPASS_AUDIT.md.
allow: allow:
- repo: windy-mind - repo: windy-mind
paths: ["*"] paths: ["*"]
@@ -19,18 +20,24 @@ allow:
Mind stays opt-in there, or every self-hosted user's inference lands on Mind stays opt-in there, or every self-hosted user's inference lands on
Grant's bill (no-cloud-cost-liability rule; audit #7). Grant's bill (no-cloud-cost-liability rule; audit #7).
exemption: owner-approved exemption: owner-approved
approved_by: windy-hub
approved_on: 2026-10-02
expires: 2026-12-31 expires: 2026-12-31
- repo: windy-code - repo: windy-code
paths: ["extensions/windy-ai/*"] paths: ["extensions/windy-ai/*"]
reason: "User BYOK AI extension: the user's own provider keys; Mind is one opt-in provider (audit #8)." reason: "User BYOK AI extension: the user's own provider keys; Mind is one opt-in provider (audit #8)."
exemption: owner-approved exemption: owner-approved
approved_by: windy-hub
approved_on: 2026-10-02
expires: 2026-12-31 expires: 2026-12-31
- repo: windy-connect - repo: windy-connect
paths: ["*writers/*"] paths: ["*writers/*"]
reason: "Writes client configs that NAME the user's own provider env vars; makes no provider calls (audit #11)." reason: "Writes client configs that NAME the user's own provider env vars; makes no provider calls (audit #11)."
exemption: owner-approved exemption: owner-approved
approved_by: windy-hub
approved_on: 2026-10-02
expires: 2026-12-31 expires: 2026-12-31
- repo: windy-pro - repo: windy-pro
@@ -41,12 +48,16 @@ allow:
the CSP line allows exactly those user-keyed hosts (audit #10). The the CSP line allows exactly those user-keyed hosts (audit #10). The
account-server is NOT covered: server-side calls go through Mind. account-server is NOT covered: server-side calls go through Mind.
exemption: owner-approved exemption: owner-approved
approved_by: windy-hub
approved_on: 2026-10-02
expires: 2026-12-31 expires: 2026-12-31
- repo: windy-pro - repo: windy-pro
paths: ["src/client/web/src/pages/panels/MindPanel.jsx"] paths: ["src/client/web/src/pages/panels/MindPanel.jsx"]
reason: "Validates the USER's own OpenRouter key for BYOK (audit #10); spends no house money." reason: "Validates the USER's own OpenRouter key for BYOK (audit #10); spends no house money."
exemption: owner-approved exemption: owner-approved
approved_by: windy-hub
approved_on: 2026-10-02
expires: 2026-12-31 expires: 2026-12-31
- repo: windy-pro - repo: windy-pro
@@ -56,6 +67,8 @@ allow:
matches: ['openrouter\.ai/auth\?', 'openrouter\.ai/api/v1/auth/keys'] matches: ['openrouter\.ai/auth\?', 'openrouter\.ai/api/v1/auth/keys']
reason: "BYOK key acquisition via OpenRouter OAuth PKCE; no inference; successor of MindPanel allow (ADR-064)." reason: "BYOK key acquisition via OpenRouter OAuth PKCE; no inference; successor of MindPanel allow (ADR-064)."
exemption: owner-approved exemption: owner-approved
approved_by: windy-hub
approved_on: 2026-10-02
expires: 2026-12-31 expires: 2026-12-31
- repo: windy-git - repo: windy-git

View File

@@ -31,7 +31,7 @@ import re
import subprocess import subprocess
import sys import sys
from dataclasses import dataclass from dataclasses import dataclass
from datetime import date from datetime import date, timedelta
from pathlib import Path from pathlib import Path
import yaml import yaml
@@ -73,6 +73,8 @@ PY_SDKS = r"anthropic|openai|groq|mistralai|cohere|google\.generativeai|google\.
JS_SDKS = (r"@anthropic-ai/sdk|openai|groq-sdk|@google/generative-ai|@google/genai|@mistralai/mistralai" JS_SDKS = (r"@anthropic-ai/sdk|openai|groq-sdk|@google/generative-ai|@google/genai|@mistralai/mistralai"
r"|cohere-ai|together-ai|@ai-sdk/(?:anthropic|openai|groq|google|mistral)") r"|cohere-ai|together-ai|@ai-sdk/(?:anthropic|openai|groq|google|mistral)")
# The engine-port rule is about CODE/CONFIG that calls the engine, not API contracts, schemas or specs.
PORT_SKIP = re.compile(r"(^|/)(contracts?|schemas?|specs?|openapi)/|\.json$", re.I)
WRANGLER = re.compile(r"(^|/)wrangler\.(toml|jsonc?)$") WRANGLER = re.compile(r"(^|/)wrangler\.(toml|jsonc?)$")
WRANGLER_AI = re.compile(r'^\s*\[ai\]\s*$|^\s*"ai"\s*:\s*\{') WRANGLER_AI = re.compile(r'^\s*\[ai\]\s*$|^\s*"ai"\s*:\s*\{')
@@ -121,7 +123,11 @@ class Finding:
EXEMPTIONS = {"local-user-hardware", "owner-approved", "compute-door", "guard-self"} EXEMPTIONS = {"local-user-hardware", "owner-approved", "compute-door", "guard-self"}
STRUCTURAL = {"compute-door", "guard-self"} # the door itself and the guard's own files: yearly review
APPROVERS = {"windy-hub", "windy-mind"} # a lane never approves its own exemption (Hub 10-02)
MAX_DAYS = 90 # every other exemption: 90 days max, then re-approve
EXPIRED: list[dict] = [] # entries dropped as expired on the last load_allow (reported, never silent) EXPIRED: list[dict] = [] # entries dropped as expired on the last load_allow (reported, never silent)
OVERCAP: list[dict] = [] # entries dropped because their expiry is further out than MAX_DAYS
def load_allow(path: Path = ALLOW_FILE, today: date | None = None, strict: bool = True) -> list[dict]: def load_allow(path: Path = ALLOW_FILE, today: date | None = None, strict: bool = True) -> list[dict]:
@@ -133,6 +139,7 @@ def load_allow(path: Path = ALLOW_FILE, today: date | None = None, strict: bool
entries = data.get("allow") or [] entries = data.get("allow") or []
active = [] active = []
EXPIRED.clear() EXPIRED.clear()
OVERCAP.clear()
for e in entries: for e in entries:
if not (e.get("repo") and e.get("paths") and str(e.get("reason", "")).strip()): if not (e.get("repo") and e.get("paths") and str(e.get("reason", "")).strip()):
raise ValueError(f"allow entry needs repo, paths and a reason: {e}") raise ValueError(f"allow entry needs repo, paths and a reason: {e}")
@@ -145,6 +152,12 @@ def load_allow(path: Path = ALLOW_FILE, today: date | None = None, strict: bool
exp = e["expires"] if isinstance(e.get("expires"), date) else date.fromisoformat(str(e.get("expires"))) exp = e["expires"] if isinstance(e.get("expires"), date) else date.fromisoformat(str(e.get("expires")))
except ValueError as err: except ValueError as err:
raise ValueError(f"allow entry needs expires: YYYY-MM-DD: {e.get('repo')} {e.get('paths')}") from err raise ValueError(f"allow entry needs expires: YYYY-MM-DD: {e.get('repo')} {e.get('paths')}") from err
if e["exemption"] not in STRUCTURAL:
if e.get("approved_by") not in APPROVERS:
raise ValueError(f"allow entry needs approved_by in {sorted(APPROVERS)}: {e.get('repo')} {e.get('paths')}")
if exp > today + timedelta(days=MAX_DAYS):
OVERCAP.append({**e, "expires": exp.isoformat()}) # a longer amnesty simply does not apply
continue
if exp < today: if exp < today:
EXPIRED.append({**e, "expires": exp.isoformat()}) EXPIRED.append({**e, "expires": exp.isoformat()})
else: else:
@@ -182,6 +195,8 @@ def scan_line(path: str, text: str) -> list[tuple[str, str]]:
continue continue
if kind == "workers ai binding" and not WRANGLER.search(path): if kind == "workers ai binding" and not WRANGLER.search(path):
continue continue
if kind == "talk engine port" and PORT_SKIP.search(path):
continue
m = rx.search(text) m = rx.search(text)
if m: if m:
hits.append((kind, m.group(0).strip()[:60])) hits.append((kind, m.group(0).strip()[:60]))
@@ -358,6 +373,9 @@ def status_for(findings: list[Finding], whole_tree: bool,
def report(repos: list[str]) -> int: def report(repos: list[str]) -> int:
allow = load_allow() allow = load_allow()
for e in OVERCAP:
print(f"## OVER-CAP exemption (> {MAX_DAYS} days, NOT applied): {e['repo']} {e['paths']} "
f"[{e['exemption']}] expires {e['expires']}")
for e in EXPIRED: for e in EXPIRED:
print(f"## EXPIRED exemption (no longer excuses anything): {e['repo']} {e['paths']} " print(f"## EXPIRED exemption (no longer excuses anything): {e['repo']} {e['paths']} "
f"[{e['exemption']}] expired {e['expires']}") f"[{e['exemption']}] expired {e['expires']}")