ops-warden/tests/test_agent_read_boundary_check.py
tegwick edb587851c Verify the OpenBao read-boundary live; ship the invariant
The operator token was not expired after all -- `bao policy read` succeeded, so
the deployed policy is now compared directly instead of the file. Three
corrections to RISK-F-0009, which was filed static:

1. Six high-risk lanes are uncovered, not eight. The finding counted
   openbao-api-key (a path pattern) and ops-warden-warden-sign-token (a broker
   grant, not KV) among the concrete uncovered paths, while its own prose said
   the first was a pattern. Five lanes have no address for a policy to deny.
2. Coverage holds at 6 of 17 against the live policy.
3. The deployed policy has drifted from the file: the file denies
   platform/workloads/core-hub/runtime, the server does not. No ops-warden lane
   maps there so our numbers are unchanged, but it proves the file was never a
   safe proxy for the server -- which is what the finding flagged as unconfirmed.

scripts/check_agent_read_boundary.py is the invariant RISK-F-0009 asked for
rather than a one-off audit: it fails when a high-risk lane has no corresponding
deny. Capabilities-only by construction -- it reads the policy document, never a
secret value, and never mints a token.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 00:50:00 +02:00

100 lines
3.8 KiB
Python

"""Tests for scripts/check_agent_read_boundary.py (WARDEN-WP-0032-T06).
This script is a control, not a report: it is the invariant RISK-F-0009 asked for
("a check that fails when a high-risk lane has no corresponding deny"). So the
parsing has to be right about the two things that would make it lie -- treating a
non-deny grant as a deny, and treating a path pattern as a concrete address.
"""
import importlib.util
from pathlib import Path
REPO = Path(__file__).resolve().parent.parent
spec = importlib.util.spec_from_file_location(
"check_agent_read_boundary", REPO / "scripts" / "check_agent_read_boundary.py"
)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
class TestDeniedDataPaths:
def test_extracts_denied_paths(self):
policy = """
path "platform/data/workloads/forgejo/forgejo-admin" {
capabilities = ["deny"]
}
"""
assert mod.denied_data_paths(policy) == {"platform/data/workloads/forgejo/forgejo-admin"}
def test_metadata_read_is_not_a_deny(self):
"""The policy permits metadata read alongside every data deny.
Counting those as denies would double the apparent coverage.
"""
policy = """
path "platform/metadata/workloads/forgejo/forgejo-admin" {
capabilities = ["read"]
}
"""
assert mod.denied_data_paths(policy) == set()
def test_deny_is_matched_exactly_not_by_substring(self):
"""A capability merely containing 'deny' must not register as a deny."""
policy = """
path "platform/data/workloads/x/y" {
capabilities = ["denylist-read"]
}
"""
assert mod.denied_data_paths(policy) == set()
def test_multiple_blocks(self):
policy = """
path "a/data/one" { capabilities = ["deny"] }
path "a/metadata/one" { capabilities = ["read"] }
path "b/data/two" { capabilities = ["deny"] }
"""
assert mod.denied_data_paths(policy) == {"a/data/one", "b/data/two"}
class TestToDataPath:
def test_inserts_kv_v2_data_segment(self):
assert (
mod.to_data_path("platform/workloads/forgejo/forgejo-admin")
== "platform/data/workloads/forgejo/forgejo-admin"
)
def test_tenant_mount(self):
assert (
mod.to_data_path("tenants/binky/company-email/imap")
== "tenants/data/binky/company-email/imap"
)
def test_placeholder_pattern_has_no_address(self):
"""`openbao-api-key` is a routing pattern, not one secret.
RISK-F-0009 counted it among the uncovered lanes; there is nothing for a
policy to deny, and reporting it as a gap overstates the exposure.
"""
assert mod.to_data_path("platform/workloads/<domain>/<workload>/<bundle>") is None
def test_non_kv_lane_has_no_address(self):
"""`ops-warden-warden-sign-token` is a broker grant, not a KV path."""
assert mod.to_data_path("credential-grants/catalog.yaml grant ops-warden/warden-sign") is None
class TestAgainstTheRealCatalog:
def test_every_high_risk_lane_resolves_or_is_explicitly_pattern(self):
"""No high-risk lane may fall through the classifier silently.
Each is either a concrete data path the policy can deny, or a pattern --
never an unhandled third case, which is the ADR-0007 failure mode.
"""
import yaml
entries = yaml.safe_load((REPO / "registry" / "routing" / "catalog.yaml").read_text())["entries"]
for entry in (e for e in entries if e.get("risk") == "high"):
template = entry.get("path_template")
if not template:
continue
resolved = mod.to_data_path(template)
assert resolved is None or resolved.count("/data/") == 1, entry["id"]