From 1b181703c6b00a03496e3e3e967f88c2af01f58d Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 2 Aug 2026 17:46:58 -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 0000000..9b62c08 --- /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 0000000..df67a75 --- /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 0000000..bdddf81 --- /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 ad9a69bd5134a93a1500b274d13190dbeb760d57 Mon Sep 17 00:00:00 2001 From: Michael Heller <21163552+mdheller@users.noreply.github.com> Date: Sun, 2 Aug 2026 18:34:10 -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 +- .../verify_surface.cpython-312.pyc | Bin 0 -> 3705 bytes consent-plane/self_test.py | 21 ++++++ consent-plane/verify_surface.py | 63 +++++++++++------- 4 files changed, 66 insertions(+), 25 deletions(-) create mode 100644 consent-plane/__pycache__/verify_surface.cpython-312.pyc 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 9b62c08..9016976 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/__pycache__/verify_surface.cpython-312.pyc b/consent-plane/__pycache__/verify_surface.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..585b3045b606c478c2112381332f7e96f1710b8f GIT binary patch literal 3705 zcma)8YitzP6~41G`x@`A*ETlBY0dC5UdY-Zv4}8;*6|~tU_&s_u!O9}JJ9*{sjp4W`=c= z!jd_vQi)(g-%F*Go}oBn#tlu*V2MtUVHr4ZW)f?xS5!Ja z2zo#lCTTSzYp&>FsrQ{zz1?Sedy<1^Pxp0o_ezGEp^|D!RCk%$vT|AzQ*tJwo6-;* zt7>3_mX+v~YNkf9BAL1*OH)`L$D|}AoyM~Ag2n9{78f|=Ks&|SaMHxoY%{WLVzCy`D3JOn zaROpiG@*O|3BlHxO`57FGA5Y-dzGY`F(ypd^s*DYkVjywe~9Q)peeOcD6oYPvjZ&W z)&&nM`w=w5<+mCCrGl~q1(x?Ld9Dj1+#oX9FE}ml z*#_g7e`8rOxFy_!E`Eg2yP$i9pAo*@jyzwo!)r26J1sAm4z#hw?G4+=RK>p6Wd3rR zyHEG!xv^@OS|0QK5>f=S#--#-;4xO~(#qo!x?Zw~LziW+goV{#~J>-01*o0y%3>F`HuyZhj_(; z1CZ85pi8PsuC6?n(vVyXkaAljsQWTdzATLZQswGP46>xD)Z8FPiexpD(k2uT77t5l zl~Oe`BISZ5k({r{9;>qX5p3Gg7lG0axD?x~LQO*Tsi1^`ObXjP1sQ>zYWrlvz!}9B z%&dX!Kr+dSFqyP{R3}gyF~99e8Bq9y=!Alxs@VKE&f3BhpdcKso3?+$K+2L;Dz@im z+s{Jv(Dt;N8kMm&iZux25%$MyT>gLC}cnL;FbWq5vgVer!U9KTvs zzo0C4uT)8^b-;YHW2LUO5N)^?z8bz!cf)J#>HMhU6aG(!o*eFfaJb*<9I%>Cu0&52 zqK$=yrscsKbxZGD)#u){s~ak$ZoK!$zPlZNIC1CbCrzLAS%ZmBPg-xiYc*ed6n(D{ zjxO|Ex=;w!Ug@9jUmRTQy|L|9$F0^oEqCkg`mLrPJ_>cM)uV>}YYiw=JKMhwzmHO^ za~lH?2cjOstL29<$gtOO%vJ^-R{dF9Sr zz3$mFoLK^sJZDBrd>e2?tIRr=rg&Vb4>+gj_<>x=%?Wlz5w;R#YI z9$895(nCeXM8e#~vVIs|4UqMIxZZSa65BpQCUB+L0@H5?sXUC6nl3ALkif$MF7;`X zFo3oNMNOHuIOyDbz0;}*_a2`b9w8F!Qua5JMD{wIOrCVWlznsnx>>X;MlMfZYI`C| z4@Bvqxbuk^dmzSc4LlG#XS-K};VaGa&5Qi6!aol$RX+^2&c4A?a%g$?O4at&NTapw z?T3-mR^W7@Dm;6f#6abyWMBbylHs2)*^~@GEw*n22IUbsZkAF*;mlGh0JeIr^Cr8t zo%U_v|2^;C7#wJVJ4%9-;VSeAc@Ay?&;PIG@=gI|y%idKn=JBI-YD4sAac&v*LD0v zr<8lieeRXtppz2dkGv;%^$BCou zH3+=9NCVIT8nYBo(^LQ|V;;iL$>#9p3`oNhjU0g;_R%5eAOpg+A9nog;H8VReP8&) ztAX%>Z)NLVtD)6u?Y1I4pS||i&cAd%3Y>ddvvsa(HB__EvskmpFYLSd+HX65-T64w zw#K8{#xJX*t3q(DXQgI`72Rp=?6kzgE5eaNRmjN-mg4O2WHKf=nMn>X`+NkO6C{%h zva8?%HOWP={3z@=+Z%J6hkuOK*#2Zv(Nj<)B9zMn$U(snmPh0e)8#kdd8w&GPDznA zMhf;$r?A1^sWy+NQ{+{~%$!BGvW|hc<44COq#2gBfT4tbha97eWfM4()|CkjA0=-B z1B(;=7`io{=dGQu7o@g3 z5o`Mop7C5w7x(3^{SMXM{&r!{tIxcinvOLe+TDI92(GiYCkwk<8FAmUa4_VV4Xigt zx!Uy>FBe&l2DmVQuMnEcea>$y2%*{TpC0@1vH3HX&OH*gKrDo&|E|5yp+L>Q5eLS6 uA#Snik3AHR-=~&v`fFzPAorXy2Ye`W?LhZ-c)L3Ido97PcK%*FoBt2;+pt*x literal 0 HcmV?d00001 diff --git a/consent-plane/self_test.py b/consent-plane/self_test.py new file mode 100644 index 0000000..98b7362 --- /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 bdddf81..1395500 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())