From 455e8f2dc72058ce71e1f0606d5cd32847d048a8 Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:46:55 -0400 Subject: [PATCH 1/2] consent-plane: enforce terminal surface envelope Adds consent-plane/surface.yaml (surface_id=terminal) + a verifier that FAILS CI if the envelope's containment is weakened (proven both ways), + the consent-plane-surface workflow. Conforms to socioprophet-agent-standards consent-plane/001 + sourceos-spec isolation-spaces-and-taints. --- .github/workflows/consent-plane-surface.yml | 14 ++++++ consent-plane/surface.yaml | 9 ++++ consent-plane/verify_surface.py | 55 +++++++++++++++++++++ 3 files changed, 78 insertions(+) create mode 100644 .github/workflows/consent-plane-surface.yml create mode 100644 consent-plane/surface.yaml create mode 100644 consent-plane/verify_surface.py diff --git a/.github/workflows/consent-plane-surface.yml b/.github/workflows/consent-plane-surface.yml new file mode 100644 index 00000000000..9b62c085dbe --- /dev/null +++ b/.github/workflows/consent-plane-surface.yml @@ -0,0 +1,14 @@ +name: Consent Plane Surface +on: + pull_request: + push: + branches: [main] +jobs: + verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: { python-version: '3.x' } + - run: pip install pyyaml + - run: python3 consent-plane/verify_surface.py diff --git a/consent-plane/surface.yaml b/consent-plane/surface.yaml new file mode 100644 index 00000000000..df67a754163 --- /dev/null +++ b/consent-plane/surface.yaml @@ -0,0 +1,9 @@ +# Consent-plane surface envelope. Conforms to socioprophet-agent-standards +# consent-plane/001 + sourceos-spec isolation-spaces-and-taints. Enforced by +# consent-plane/verify_surface.py (consent-plane-surface CI). +surface_id: terminal +conforms_to: socioprophet-agent-standards/standards/consent-plane/surfaces_v1.yaml#terminal +purposes: [discover, implement, verify] +deny_purposes: [egress, operate] # a terminal must not egress or operate live infra +data_classes: [source-and-config, first-party-source] +space_deny: [kernel-space, system-space] # no OS-core / infra ring from a shell diff --git a/consent-plane/verify_surface.py b/consent-plane/verify_surface.py new file mode 100644 index 00000000000..bdddf81f120 --- /dev/null +++ b/consent-plane/verify_surface.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +"""Enforce this repo's consent-plane surface envelope (fail-closed). + +Reads consent-plane/surface.yaml and asserts the hard invariants for its +surface_id, so CI FAILS if the surface's containment is weakened. Conforms to +socioprophet-agent-standards consent-plane/001 + sourceos-spec +isolation-spaces-and-taints. Proven both ways by consent-plane/self_test.py. +""" +from __future__ import annotations +import sys +from pathlib import Path +try: + import yaml # type: ignore +except Exception as exc: # pragma: no cover + raise SystemExit("PyYAML is required (pip install pyyaml)") from exc + +# Minimum containment each surface MUST assert (subset checks). +EXPECTED = { + "terminal": {"deny_purposes": {"egress", "operate"}, + "space_deny": {"kernel-space", "system-space"}}, + "notes": {"deny_purposes": {"egress", "operate"}, + "space_deny": {"kernel-space", "system-space", "data-namespace"}, + "consent_required": "per-purpose"}, + "browser": {"deny_purposes": {"implement", "operate"}, + "space_deny": {"kernel-space", "system-space", "user-space", "data-namespace"}, + "untrusted_input": True}, +} + +def main() -> int: + cfg = Path(__file__).resolve().parent / "surface.yaml" + cp = yaml.safe_load(cfg.read_text()) or {} + sid = cp.get("surface_id") + errors: list[str] = [] + if sid not in EXPECTED: + print(f"ERR: unknown surface_id {sid!r} (expected one of {sorted(EXPECTED)})", file=sys.stderr) + return 1 + exp = EXPECTED[sid] + for key, want in exp.items(): + got = cp.get(key) + if isinstance(want, set): + have = set(got or []) + if not want <= have: + errors.append(f"{key} must include {sorted(want)}; missing {sorted(want - have)}") + else: + if got != want: + errors.append(f"{key} must be {want!r}, got {got!r}") + if errors: + print(f"FAIL: {sid} surface envelope violated:", file=sys.stderr) + for e in errors: print(f" - {e}", file=sys.stderr) + return 1 + print(f"OK: {sid} surface envelope holds ({', '.join(exp)}).") + return 0 + +if __name__ == "__main__": + sys.exit(main()) From b73c408b7c29e506e6bbcd950d3cfab1db73de9f Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 2 Aug 2026 18:34:08 -0400 Subject: [PATCH 2/2] =?UTF-8?q?consent-plane:=20harden=20verifier=20(revie?= =?UTF-8?q?w)=20=E2=80=94=20pin=20surface,=20guard=20non-dict/non-list,=20?= =?UTF-8?q?add=20self=5Ftest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copilot: (1) pin EXPECTED_SURFACE so surface.yaml can't be switched to a weaker surface; (2) fail cleanly (not a traceback) on a non-mapping surface.yaml and non-list set-fields; (3) add consent-plane/self_test.py so the 'proven both ways' claim is real (passes on the envelope; fires on weakening + surface switch); (4) workflow uses 'python -m pip' + runs the self_test + least-privilege perms. --- .github/workflows/consent-plane-surface.yml | 7 ++- consent-plane/self_test.py | 21 +++++++ consent-plane/verify_surface.py | 63 +++++++++++++-------- 3 files changed, 66 insertions(+), 25 deletions(-) create mode 100644 consent-plane/self_test.py diff --git a/.github/workflows/consent-plane-surface.yml b/.github/workflows/consent-plane-surface.yml index 9b62c085dbe..901697660b3 100644 --- a/.github/workflows/consent-plane-surface.yml +++ b/.github/workflows/consent-plane-surface.yml @@ -3,6 +3,8 @@ on: pull_request: push: branches: [main] +permissions: + contents: read jobs: verify: runs-on: ubuntu-latest @@ -10,5 +12,6 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: { python-version: '3.x' } - - run: pip install pyyaml - - run: python3 consent-plane/verify_surface.py + - run: python -m pip install pyyaml + - run: python consent-plane/self_test.py + - run: python consent-plane/verify_surface.py diff --git a/consent-plane/self_test.py b/consent-plane/self_test.py new file mode 100644 index 00000000000..98b736230a3 --- /dev/null +++ b/consent-plane/self_test.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +"""Prove verify_surface fires both ways: passes on the real envelope, fires when +the containment is weakened or the surface_id is switched.""" +from __future__ import annotations +import copy, sys +from pathlib import Path +sys.path.insert(0, str(Path(__file__).resolve().parent)) +import verify_surface as v # noqa: E402 + +cp = v.load() +assert v.check(cp) == [], f"real envelope should pass: {v.check(cp)}" + +if cp.get("space_deny"): + weak = copy.deepcopy(cp); weak["space_deny"] = weak["space_deny"][:-1] + assert v.check(weak), "verifier did not fire on a weakened space_deny" + +switched = copy.deepcopy(cp) +switched["surface_id"] = "browser" if cp["surface_id"] != "browser" else "terminal" +assert v.check(switched), "verifier did not fire on a switched surface_id" + +print("OK: verify_surface fires both ways (holds on real; catches weakening + switch).") diff --git a/consent-plane/verify_surface.py b/consent-plane/verify_surface.py index bdddf81f120..1395500f767 100644 --- a/consent-plane/verify_surface.py +++ b/consent-plane/verify_surface.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 """Enforce this repo's consent-plane surface envelope (fail-closed). -Reads consent-plane/surface.yaml and asserts the hard invariants for its -surface_id, so CI FAILS if the surface's containment is weakened. Conforms to -socioprophet-agent-standards consent-plane/001 + sourceos-spec -isolation-spaces-and-taints. Proven both ways by consent-plane/self_test.py. +This repo IS the terminal surface; EXPECTED_SURFACE pins it so surface.yaml +cannot be silently switched to a weaker surface. Reads consent-plane/surface.yaml +and asserts the hard invariants. Proven both ways by consent-plane/self_test.py. +Conforms to socioprophet-agent-standards consent-plane/001 + sourceos-spec +isolation-spaces-and-taints. """ from __future__ import annotations import sys @@ -12,7 +13,9 @@ try: import yaml # type: ignore except Exception as exc: # pragma: no cover - raise SystemExit("PyYAML is required (pip install pyyaml)") from exc + raise SystemExit("PyYAML is required (python -m pip install pyyaml)") from exc + +EXPECTED_SURFACE = "terminal" # Minimum containment each surface MUST assert (subset checks). EXPECTED = { @@ -26,30 +29,44 @@ "untrusted_input": True}, } -def main() -> int: - cfg = Path(__file__).resolve().parent / "surface.yaml" - cp = yaml.safe_load(cfg.read_text()) or {} - sid = cp.get("surface_id") + +def check(cp: dict) -> list[str]: errors: list[str] = [] - if sid not in EXPECTED: - print(f"ERR: unknown surface_id {sid!r} (expected one of {sorted(EXPECTED)})", file=sys.stderr) - return 1 - exp = EXPECTED[sid] - for key, want in exp.items(): + sid = cp.get("surface_id") + if sid != EXPECTED_SURFACE: + return [f"surface_id must be {EXPECTED_SURFACE!r} for this repo, got {sid!r}"] + for key, want in EXPECTED[sid].items(): got = cp.get(key) if isinstance(want, set): - have = set(got or []) - if not want <= have: - errors.append(f"{key} must include {sorted(want)}; missing {sorted(want - have)}") - else: - if got != want: - errors.append(f"{key} must be {want!r}, got {got!r}") + if not isinstance(got, list): + errors.append(f"{key} must be a list, got {type(got).__name__}") + continue + missing = want - set(got) + if missing: + errors.append(f"{key} must include {sorted(want)}; missing {sorted(missing)}") + elif got != want: + errors.append(f"{key} must be {want!r}, got {got!r}") + return errors + + +def load() -> dict: + cfg = Path(__file__).resolve().parent / "surface.yaml" + cp = yaml.safe_load(cfg.read_text()) + if not isinstance(cp, dict): + raise SystemExit("consent-plane/surface.yaml top-level must be a mapping") + return cp + + +def main() -> int: + errors = check(load()) if errors: - print(f"FAIL: {sid} surface envelope violated:", file=sys.stderr) - for e in errors: print(f" - {e}", file=sys.stderr) + print(f"FAIL: {EXPECTED_SURFACE} surface envelope violated:", file=sys.stderr) + for e in errors: + print(f" - {e}", file=sys.stderr) return 1 - print(f"OK: {sid} surface envelope holds ({', '.join(exp)}).") + print(f"OK: {EXPECTED_SURFACE} surface envelope holds.") return 0 + if __name__ == "__main__": sys.exit(main())