"""Adversarial contracts for the Atlas-only draft pull-request client.""" from __future__ import annotations import base64 import copy import importlib.util import json import sys import urllib.request from email.message import Message from pathlib import Path import pytest ROOT = Path(__file__).parents[2] CLIENT_PATH = ROOT / "services/hermes/scm-common/scripts/gitea_api.py" HEAD_SHA = "465cf9146b05c174a2a8d310aff6c64be58277b6" if str(CLIENT_PATH.parent) not in sys.path: sys.path.insert(0, str(CLIENT_PATH.parent)) def _load(): spec = importlib.util.spec_from_file_location("safe_gitea_api", CLIENT_PATH) assert spec and spec.loader module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module spec.loader.exec_module(module) return module def _draft_payload(**updates): payload = { "base": "main", "body": "Review evidence", "head": "hermes/review-fix", "title": "WIP: Repair review findings", } payload.update(updates) return payload def _draft_response(**updates): response = { "number": 3, "state": "open", "draft": True, "merged": False, "html_url": "https://scm.bstein.dev/atlas/cassandra/pulls/3", "url": "https://scm.bstein.dev/atlas/cassandra/pulls/3", "title": "WIP: Focused fix", "body": "Review evidence", "base": {"ref": "main", "repo": {"full_name": "atlas/cassandra"}}, "head": { "ref": "hermes/fix", "sha": HEAD_SHA, "repo": {"full_name": "atlas/cassandra"}, }, } response.update(updates) return response class Response: def __init__(self, body: object, status: int | None = None): self.body = body if isinstance(body, bytes) else json.dumps(body).encode() self.status = status if status is not None else (201 if isinstance(body, dict) else 200) self.headers = Message() self.headers["Content-Type"] = "application/json" def __enter__(self): return self def __exit__(self, *_args): return False def read(self, limit=-1): return self.body if limit < 0 else self.body[:limit] def test_redirect_handler_rejects_cross_origin_with_sentinel_authorization(): client = _load() source = urllib.request.Request( "https://scm.bstein.dev/api/v1/repos/atlas/cassandra", headers={"Authorization": "token redirect-sentinel"}, ) with pytest.raises(client.PolicyError, match="redirects are not allowed") as exc: client.RejectRedirectHandler().redirect_request( source, None, 302, "Found", {}, "https://evil.example/collect", ) assert "redirect-sentinel" not in str(exc.value) assert any( isinstance(handler, client.RejectRedirectHandler) for handler in client._SAFE_OPENER.handlers ) @pytest.mark.parametrize( ("base_url", "path"), [ ("https://evil.example", "/api/v1/repos/atlas/cassandra"), ("http://scm.bstein.dev", "/api/v1/repos/atlas/cassandra"), ("https://scm.bstein.dev:443", "/api/v1/repos/atlas/cassandra"), ("https://scm.bstein.dev/", "/api/v1/repos/atlas/cassandra"), ("HTTPS://scm.bstein.dev", "/api/v1/repos/atlas/cassandra"), ("https://SCM.bstein.dev", "/api/v1/repos/atlas/cassandra"), ("https://user@scm.bstein.dev", "/api/v1/repos/atlas/cassandra"), ("https://scm.bstein.dev", "https://evil.example/api/v1/repos/atlas/cassandra"), ("https://scm.bstein.dev", "/api/v1/repos/evil/cassandra"), ("https://scm.bstein.dev", "/api/v1/repos/%61tlas/cassandra"), ("https://scm.bstein.dev", "/api/v1/repos/atlas/../admin"), ], ) def test_host_owner_and_path_escape_attempts_are_rejected(base_url: str, path: str): client = _load() with pytest.raises(client.PolicyError): client.build_request("GET", path, base_url=base_url, token="secret") @pytest.mark.parametrize( "path", [ "/api/v1/repos/atlas/cassandra/pulls/1\nHost: evil.example", "/api/v1/repos/atlas/cassandra/pulls/1\r\nX-Test: value", "/api/v1/repos/atlas/cassandra/pulls/1\tignored", "/api/v1/repos/atlas/cassandra/pulls/1\x00ignored", "/api/v1/repos/atlas/cassandra/pulls/1\x1fignored", "/api/v1/repos/atlas/cassandra/pulls/1\x7fignored", "/api/v1/repos/atlas/cassandra\\pulls\\1", "/api/v1/repos/atlas/cassandra/pulls/%31", "/api/v1/repos/atlas/cassandra/pulls/1", " https://scm.bstein.dev/api/v1/repos/atlas/cassandra", "https://scm.bstein.dev/api/v1/repos/atlas/cassandra", ], ) def test_raw_noncanonical_target_is_rejected_before_urlsplit_and_opener( path: str, monkeypatch ): client = _load() split_called = False opener_called = False original_urlsplit = client.urllib.parse.urlsplit def urlsplit(*args, **kwargs): nonlocal split_called split_called = True return original_urlsplit(*args, **kwargs) def opener(*_args, **_kwargs): nonlocal opener_called opener_called = True return Response(b"{}") monkeypatch.setattr(client.urllib.parse, "urlsplit", urlsplit) with pytest.raises(client.PolicyError): client.read(path, token="runtime", opener=opener) assert split_called is False assert opener_called is False @pytest.mark.parametrize( "path", [ "/api/v1/repos/atlas/cassandra/pulls/1?", "//scm.bstein.dev/api/v1/repos/atlas/cassandra", "/api/v1/repos/atlas/cassandra/./pulls/1", "/api/v1/repos/atlas/cassandra/../admin", "/api/v1/repos/atlas/cassandra//pulls/1", "/api/v1/repos/atlas/cassandra/pulls/1?limit=01", ], ) def test_noncanonical_round_trip_or_segments_never_reach_opener(path: str): client = _load() opener_called = False def opener(*_args, **_kwargs): nonlocal opener_called opener_called = True return Response(b"{}") with pytest.raises(client.PolicyError): client.read(path, token="runtime", opener=opener) assert opener_called is False @pytest.mark.parametrize( "path", [ "/api/v1/repos/atlas/cassandra", "/api/v1/repos/atlas/cassandra/pulls?state=open&limit=20&page=1", "/api/v1/repos/atlas/cassandra/pulls/7", "/api/v1/repos/atlas/cassandra/pulls/7/commits?limit=20", "/api/v1/repos/atlas/cassandra/pulls/7/files?page=1", "/api/v1/repos/atlas/cassandra/branches", "/api/v1/repos/atlas/cassandra/branches/main", "/api/v1/repos/atlas/cassandra/commits?limit=10", f"/api/v1/repos/atlas/cassandra/git/commits/{HEAD_SHA}", f"/api/v1/repos/atlas/cassandra/commits/{HEAD_SHA}/status", f"/api/v1/repos/atlas/cassandra/commits/{HEAD_SHA}/statuses?limit=10", f"/api/v1/repos/atlas/cassandra/statuses/{HEAD_SHA}?page=1", ], ) def test_explicit_read_allowlist_accepts_only_engineering_metadata(path: str): client = _load() request = client.build_request( "GET", path, base_url=client.CANONICAL_BASE_URL, token="runtime" ) assert request.method == "GET" @pytest.mark.parametrize( "path", [ "/api/v1/repos/atlas/cassandra/hooks", "/api/v1/repos/atlas/cassandra/actions/secrets", "/api/v1/repos/atlas/cassandra/actions/variables", "/api/v1/repos/atlas/cassandra/collaborators", "/api/v1/repos/atlas/cassandra/branch_protections", "/api/v1/repos/atlas/cassandra/keys", "/api/v1/repos/atlas/cassandra/pulls/7/reviews", "/api/v1/repos/atlas/cassandra/pulls/7/merge", "/api/v1/repos/atlas/cassandra/pulls/7.diff", "/api/v1/repos/atlas/cassandra/releases", ], ) def test_privileged_or_content_routes_are_denied_even_for_get(path: str): client = _load() with pytest.raises(client.PolicyError, match="outside the metadata read allowlist"): client.authorize_request("GET", path, None) @pytest.mark.parametrize( "path", [ "/api/v1/repos/atlas/cassandra/pulls?limit=51", "/api/v1/repos/atlas/cassandra/pulls?state=merged", "/api/v1/repos/atlas/cassandra/pulls?private=true", "/api/v1/repos/atlas/cassandra/pulls?limit=1&limit=2", "/api/v1/repos/atlas/cassandra?p=1", ], ) def test_read_query_is_bounded(path: str): client = _load() with pytest.raises(client.PolicyError): client.authorize_request("GET", path, None) @pytest.mark.parametrize( "query", [ "page=0", "page=10001", "page=01", "limit=0", "limit=51", "limit=01", "page=" + "9" * 4000, "page=0", "page=%EF%BC%90", "state=%6fpen", "p%61ge=1", "page=1&page=2", "page=1&" + "x" * 17 + "=1", "state=" + "x" * 17, "page=1&limit=2&state=open&extra=3", ], ) def test_read_query_requires_canonical_bounded_ascii(query: str): client = _load() with pytest.raises(client.PolicyError): client.authorize_request( "GET", f"/api/v1/repos/atlas/cassandra/pulls?{query}", None ) @pytest.mark.parametrize( "suffix", [ "pulls/{number}", "pulls/{number}.patch", "pulls/{number}.diff", "pulls/{number}/commits", "pulls/{number}/files", "commits/{number}/status", "commits/{number}/statuses", "statuses/{number}", "branches/{number}", ], ) def test_oversized_numeric_or_captured_path_never_reaches_opener(suffix: str): client = _load() called = False def opener(*_args, **_kwargs): nonlocal called called = True return Response(b"{}") path = "/api/v1/repos/atlas/cassandra/" + suffix.format(number="9" * 4000) with pytest.raises(client.PolicyError): client.read(path, token="runtime", opener=opener) assert called is False @pytest.mark.parametrize( "number", ["0", "01", "2147483648", "12", "%31", "12345678901"], ) @pytest.mark.parametrize("tail", ["", ".patch", ".diff", "/commits", "/files"]) def test_noncanonical_or_out_of_range_pr_number_never_reaches_opener( number: str, tail: str ): client = _load() called = False def opener(*_args, **_kwargs): nonlocal called called = True return Response(b"{}") with pytest.raises(client.PolicyError): client.read( f"/api/v1/repos/atlas/cassandra/pulls/{number}{tail}", token="runtime", opener=opener, ) assert called is False def test_maximum_bounded_pr_number_is_readable(): client = _load() called = False def opener(*_args, **_kwargs): nonlocal called called = True return Response(b"{}") assert ( client.read( "/api/v1/repos/atlas/cassandra/pulls/2147483647", token="runtime", opener=opener, ) == b"{}" ) assert called is True @pytest.mark.parametrize( ("method", "path", "data"), [ ("DELETE", "/api/v1/repos/atlas/cassandra/pulls/4", None), ("POST", "/api/v1/repos/atlas/cassandra/pulls/4/merge", {}), ( "POST", "/api/v1/repos/atlas/cassandra/pulls/4/reviews", {"event": "APPROVED"}, ), ("PATCH", "/api/v1/repos/atlas/cassandra/pulls/4", {"title": "WIP: x"}), ("PUT", "/api/v1/repos/atlas/cassandra/branches/main", {}), ], ) def test_merge_approve_close_delete_update_and_other_mutations_are_rejected( method: str, path: str, data: object ): client = _load() with pytest.raises(client.PolicyError): client.build_request( method, path, base_url=client.CANONICAL_BASE_URL, token="secret", data=data ) @pytest.mark.parametrize( "ref", [ "foo/.bar", "foo/bar.lock/baz", "foo..bar", "foo@{bar", "foo//bar", "-danger", "danger.", "danger~one", "danger^one", "danger:one", "danger one", ], ) def test_complete_git_ref_validation_rejects_invalid_names(ref: str): client = _load() with pytest.raises(client.PolicyError): client._validate_ref(ref, "head") def test_git_ref_validation_uses_fixed_trusted_binary(): client = _load() assert client._validate_ref.__globals__["GIT_BIN"] == "/usr/bin/git" assert client._validate_ref("hermes/valid-fix", "head") == "hermes/valid-fix" @pytest.mark.parametrize("field", ["base", "head"]) @pytest.mark.parametrize("oversized", ["r" * 100_000, "🧪" * 128]) def test_oversized_ref_never_invokes_git_request_or_opener( field: str, oversized: str, monkeypatch ): client = _load() git_called = False request_built = False opener_called = False original_build_request = client.build_request def git_run(*_args, **_kwargs): nonlocal git_called git_called = True raise AssertionError("Git must not receive an oversized ref") def build_request(*args, **kwargs): nonlocal request_built request_built = True return original_build_request(*args, **kwargs) def opener(*_args, **_kwargs): nonlocal opener_called opener_called = True return Response(_draft_response()) monkeypatch.setattr(client._validate_ref.__globals__["subprocess"], "run", git_run) client.build_request = build_request refs = {"base": "main", "head": "hermes/fix"} refs[field] = oversized with pytest.raises(client.PolicyError, match="branch-name limit"): client.create_draft( "cassandra", base=refs["base"], head=refs["head"], head_sha=HEAD_SHA, title="Focused fix", body="Review evidence", token="runtime", opener=opener, ) assert git_called is False assert request_built is False assert opener_called is False @pytest.mark.parametrize( ("field", "oversized"), [("repo", "r" * 101), ("title", "🧪" * 200), ("body", "🧪" * 9_000)], ) def test_other_text_bounds_fail_before_git_request_or_opener( field: str, oversized: str, monkeypatch ): client = _load() git_called = False request_built = False opener_called = False original_build_request = client.build_request def git_run(*_args, **_kwargs): nonlocal git_called git_called = True raise AssertionError("Git must not run before cheap input bounds") def build_request(*args, **kwargs): nonlocal request_built request_built = True return original_build_request(*args, **kwargs) def opener(*_args, **_kwargs): nonlocal opener_called opener_called = True return Response(_draft_response()) monkeypatch.setattr(client._validate_ref.__globals__["subprocess"], "run", git_run) client.build_request = build_request values = { "repo": "cassandra", "title": "Focused fix", "body": "Review evidence", } values[field] = oversized with pytest.raises(client.PolicyError): client.create_draft( values["repo"], base="main", head="hermes/fix", head_sha=HEAD_SHA, title=values["title"], body=values["body"], token="runtime", opener=opener, ) assert git_called is False assert request_built is False assert opener_called is False def test_create_forces_draft_title_and_same_repository_branch_names(): client = _load() assert ( client.authorize_request( "POST", "/api/v1/repos/atlas/cassandra/pulls", _draft_payload() ) == "create-draft" ) with pytest.raises(client.PolicyError, match="draft-title prefix"): client.authorize_request( "POST", "/api/v1/repos/atlas/cassandra/pulls", _draft_payload(title="Not a draft"), ) with pytest.raises(client.PolicyError): client.authorize_request( "POST", "/api/v1/repos/atlas/cassandra/pulls", _draft_payload(head="someone:branch"), ) def test_runtime_token_is_only_an_authorization_header(): client = _load() request = client.build_request( "POST", "/api/v1/repos/atlas/cassandra/pulls", base_url=client.CANONICAL_BASE_URL, token="do-not-leak", data=_draft_payload(), ) assert "do-not-leak" not in request.full_url assert b"do-not-leak" not in request.data assert request.get_header("Authorization") == "token do-not-leak" @pytest.mark.parametrize( "sensitive", [ "client_" + "secret=not-a-real-value", '{"client_' + 'secret":\n"synthetic-value"}', '{"client_' + 'secret"\n:\n"synthetic-value"}', '{"client\\u005f' + 'secret":"synthetic-value"}', "client_" + "secret:\n synthetic-value", '{"client\\q' + 'secret":"synthetic-value"}', '{"client\n' + 'secret":"synthetic-value"}', '"client_' + 'secret"\x0b:\n"synthetic-value"', "ACCESS_" + "TOKEN = 'not-a-real-value'", '{"refresh_' + 'token": "not-a-real-value"}', "private_" + "key: not-a-real-value", "AWS_SECRET_ACCESS_" + "KEY=not-a-real-value", "aws_access_key_" + "id: not-a-real-value", "AWS_SESSION_" + "TOKEN = 'not-a-real-value'", '{"AccessKey' + 'Id":"not-a-real-value"}', '{"SecretAccess' + 'Key":"not-a-real-value"}', '{"Session' + 'Token":"not-a-real-value"}', "aws-security-" + "token: not-a-real-value", "Account" + "Key=not-a-real-value", "SharedAccess" + "Signature: not-a-real-value", "AZURE_STORAGE_CONNECTION_" + "STRING='not-a-real-value'", "DefaultEndpointsProtocol=https;AccountName=fake;Account" + "Key=not-a-real-value;EndpointSuffix=example", "?sv=2024-11-04&ss=b&srt=sco&sp=rwdlac&se=2099-01-01&sig=" + "not-a-real-value", "DOCKER_AUTH_" + "CONFIG='not-a-real-value'", '{"auths":{"registry.example":{"auth":"bm90LXJlYWw="}}}', '{"identity' + 'token":"not-a-real-value"}', '{"type":"service_' + 'account","client_email":"fake@example.test"}', '{"private_key_' + 'id":"not-a-real-value"}', '{"client_' + 'email":"fake@example.test"}', "GOOGLE_CREDENTIALS" + "=not-a-real-value", "personal_access_" + "token: not-a-real-value", "GITEA_" + "TOKEN=not-a-real-value", "gitlab-token" + ": not-a-real-value", "pat" + "=not-a-real-value", "Authorization: " + "Bearer not-a-real-credential-value", "authorization = " + '"Basic not-a-real-credential-value"', "Bearer" + "=not-a-real-credential-value", "Basic" + ": not-a-real-credential-value", "pass" + "word=not-a-real-credential", "ghp_" + "notarealcredentialvalue123456", "github_pat_" + "notarealcredentialvalue123456", "glpat-" + "notarealcredentialvalue123456", "xoxb-" + "not-a-real-credential-value-123456", "sk-ant-" + "notarealcredentialvalue123456", "sk-proj-" + "notarealcredentialvalue123456", "sk_live_" + "notarealcredentialvalue123456", "ya29." + "notarealcredentialvalue123456", "gta_" + "notarealcredentialvalue123456", "whsec_" + "notarealcredentialvalue123456", "npm_" + "notarealcredentialvalue123456", "pypi-" + "notarealcredentialvalue123456789012345", "hf_" + "notarealcredentialvalue123456", "SG." + "notarealvalue1234" + ".notarealcredentialvalue123456", "SK" + "a" * 32, "https://hooks.slack.com/services/" + "T000/B000/notarealvalue123456", "https://discord.com/api/webhooks/123456789/" + "notarealcredentialvalue123456", "https://fake.webhook.office.com/" + "notarealcredentialvalue123456", "webhook_" + "url=https://example.test/not-real", "FutureCloudSigning" + "Credential=not-a-real-value", "future-client-signing-" + "key: not-a-real-value", "future_client_signing_" + "key='not-a-real-value'", '{"serviceAccountPrivate' + 'Key":"not-a-real-value"}', "CONTAINER_REGISTRY_" + "CREDENTIAL=not-a-real-value", "someWebhookSigning" + "Secret: not-a-real-value", "client" + "Key=not-a-real-value", "session" + "Key: not-a-real-value", "access" + "Id=not-a-real-value", "credentials" + ": {user: fake}", "private" + "Key: |", "nuget_api_" + "key=not-a-real-value", "oy2" + "a" * 44, "sk_test_" + "notarealcredentialvalue123456", "A1b2C3d4E5f6G7h8I9j0K_l-M+n/O=pQ2rS3tU4vW5xY6zZ7aB8cC9d", "AKIA" + "A" * 16, "AIza" + "a" * 35, "-----BEGIN OPENSSH " + "PRIVATE KEY-----", "ssh-ed25519 " + "bm90YXJlYWxjcmVkZW50aWFsdmFsdWU=", "eyJnotarealheader." + "notarealpayloadvalue." + "notarealsignature", ], ) @pytest.mark.parametrize("field", ["title", "body"]) def test_create_rejects_expanded_credential_shapes_before_network( sensitive: str, field: str ): client = _load() called = False request_built = False original_build_request = client.build_request def build_request(*args, **kwargs): nonlocal request_built request_built = True return original_build_request(*args, **kwargs) def opener(*_args, **_kwargs): nonlocal called called = True return Response(_draft_response()) client.build_request = build_request values = {"title": "Focused fix", "body": "Review evidence"} values[field] = sensitive with pytest.raises(client.PolicyError, match="credential material"): client.create_draft( "cassandra", base="main", head="hermes/fix", head_sha=HEAD_SHA, title=values["title"], body=values["body"], token="runtime-sentinel", opener=opener, ) assert request_built is False assert called is False def test_very_long_compact_body_is_rejected_before_request_or_network(): client = _load() request_built = False opener_called = False original_build_request = client.build_request def build_request(*args, **kwargs): nonlocal request_built request_built = True return original_build_request(*args, **kwargs) def opener(*_args, **_kwargs): nonlocal opener_called opener_called = True return Response(_draft_response()) client.build_request = build_request with pytest.raises(client.PolicyError, match="credential material"): client.create_draft( "cassandra", base="main", head="hermes/fix", head_sha=HEAD_SHA, title="Focused fix", body="a" * 300, token="runtime-sentinel", opener=opener, ) assert request_built is False assert opener_called is False @pytest.mark.parametrize( "safe_text", [ "AWS_SECRET_ACCESS_KEY is injected at runtime", "Token: reject empty values", "Authorization = preserve header behavior", "Password: add regression", "Review the Authorization header behavior", "Bearer authentication is required for this route", "Document Docker auths payload rejection", "The client_email field belongs to service accounts", "AccountKey assignments must be blocked", "This patch changes token validation without including a value", "FutureCloudSigningCredential handling needs a regression test", "The clientKey name is documented without an assigned value", "A SHA-256 digest 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef is evidence", ], ) def test_credential_policy_keeps_normal_engineering_prose_usable(safe_text: str): client = _load() assert client._validate_body(safe_text) == safe_text assert client._draft_title(safe_text) == f"WIP: {safe_text}" @pytest.mark.parametrize("field", ["title", "body"]) def test_create_rejects_exact_runtime_token_before_network(field: str): client = _load() called = False request_built = False original_build_request = client.build_request def build_request(*args, **kwargs): nonlocal request_built request_built = True return original_build_request(*args, **kwargs) def opener(*_args, **_kwargs): nonlocal called called = True return Response(_draft_response()) client.build_request = build_request runtime_token = "exact-random-runtime-sentinel-7b73ac61" values = {"title": "Focused fix", "body": "Review evidence"} values[field] = f"Accidental {runtime_token} value" with pytest.raises(client.PolicyError, match="runtime credential"): client.create_draft( "cassandra", base="main", head="hermes/fix", head_sha=HEAD_SHA, title=values["title"], body=values["body"], token=runtime_token, opener=opener, ) assert request_built is False assert called is False @pytest.mark.parametrize("field", ["repo", "base", "head", "title", "body"]) def test_every_public_field_rejects_exact_runtime_token_before_git_or_network( field: str, monkeypatch ): client = _load() git_called = False request_built = False opener_called = False runtime_token = "runtime-sentinel" values = { "repo": "cassandra", "base": "main", "head": "hermes/fix", "title": "Focused fix", "body": "Review evidence", } values[field] = runtime_token def git_run(*_args, **_kwargs): nonlocal git_called git_called = True raise AssertionError("Git must not run for a credential-bearing field") def build_request(*_args, **_kwargs): nonlocal request_built request_built = True raise AssertionError("a request must not be built") def opener(*_args, **_kwargs): nonlocal opener_called opener_called = True raise AssertionError("the opener must not be called") monkeypatch.setattr(client._validate_ref.__globals__["subprocess"], "run", git_run) monkeypatch.setattr(client, "build_request", build_request) with pytest.raises(client.PolicyError, match="runtime credential"): client.create_draft( values["repo"], base=values["base"], head=values["head"], head_sha=HEAD_SHA, title=values["title"], body=values["body"], token=runtime_token, opener=opener, ) assert git_called is False assert request_built is False assert opener_called is False @pytest.mark.parametrize( "sensitive", [ " ".join(["{}"] * 32) + ' {"client_secret":"synthetic-value"}', "prefix_ghp_notarealcredentialvalue123456_suffix", "client_secret: correct horse battery staple", "'client_secret':\n synthetic-value", ], ) @pytest.mark.parametrize("field", ["title", "body"]) def test_structured_scanner_closes_bounded_and_wrapped_token_bypasses( sensitive: str, field: str ): client = _load() values = {"title": "Focused fix", "body": "Review evidence"} values[field] = sensitive called = False def opener(*_args, **_kwargs): nonlocal called called = True return Response(_draft_response()) with pytest.raises(client.PolicyError, match="credential material"): client.create_draft( "cassandra", base="main", head="hermes/fix", head_sha=HEAD_SHA, title=values["title"], body=values["body"], token="runtime-sentinel", opener=opener, ) assert called is False @pytest.mark.parametrize("status", [200, 202, 204, 206]) def test_create_accepts_only_http_201(status: int): client = _load() with pytest.raises(client.PolicyError, match="unexpected HTTP status"): client.create_draft( "cassandra", base="main", head="hermes/fix", head_sha=HEAD_SHA, title="Focused fix", body="Review evidence", token="runtime", opener=lambda *_a, **_k: Response(_draft_response(), status=status), ) @pytest.mark.parametrize("status", [201, 202, 204, 206]) def test_read_accepts_only_http_200(status: int): client = _load() with pytest.raises(client.PolicyError, match="unexpected HTTP status"): client.read( "/api/v1/repos/atlas/cassandra", token="runtime", opener=lambda *_a, **_k: Response(b"{}", status=status), ) def test_successful_create_validates_each_ref_once(monkeypatch): client = _load() calls = [] def git_run(command, **_kwargs): calls.append(command) return type("Result", (), {"returncode": 0})() monkeypatch.setattr(client._validate_ref.__globals__["subprocess"], "run", git_run) client.create_draft( "cassandra", base="main", head="hermes/fix", head_sha=HEAD_SHA, title="Focused fix", body="Review evidence", token="runtime", opener=lambda *_a, **_k: Response(_draft_response()), ) assert [command[-1] for command in calls] == [ "refs/heads/main", "refs/heads/hermes/fix", ] def test_create_verifies_every_server_postcondition(): client = _load() calls = [] def opener(request, timeout): calls.append((request, timeout)) return Response(_draft_response()) result = client.create_draft( "cassandra", base="main", head="hermes/fix", head_sha=HEAD_SHA, title="Focused fix", body="Review evidence", token="runtime", opener=opener, ) assert json.loads(result) == _draft_response() payload = json.loads(calls[0][0].data) assert payload == { "base": "main", "body": "Review evidence", "head": "hermes/fix", "title": "WIP: Focused fix", } assert calls[0][1] == 30 def test_create_postcondition_rejects_every_material_mismatch(): client = _load() mutations = [ ("number", 0), ("number", 2_147_483_648), ("state", "closed"), ("draft", False), ("merged", True), ("html_url", "https://evil.example/pulls/3"), ("url", "https://evil.example/api/pulls/3"), ("title", "Focused fix"), ("body", "different"), ] documents = [] for key, value in mutations: document = _draft_response() document[key] = value documents.append(document) for path, value in [ (("base", "ref"), "master"), (("base", "repo", "full_name"), "evil/cassandra"), (("head", "ref"), "other"), (("head", "sha"), "0" * 40), (("head", "repo", "full_name"), "evil/cassandra"), ]: document = copy.deepcopy(_draft_response()) target = document for key in path[:-1]: target = target[key] target[path[-1]] = value documents.append(document) for document in documents: with pytest.raises(client.PolicyError): client._require_create_response( json.dumps(document).encode(), repo="cassandra", base="main", head="hermes/fix", head_sha=HEAD_SHA, title="WIP: Focused fix", body="Review evidence", ) def test_read_response_is_bounded(): client = _load() with pytest.raises(client.PolicyError, match="safe size limit"): client.read( "/api/v1/repos/atlas/cassandra", token="runtime", opener=lambda *_a, **_k: Response(b"x" * (client.MAX_RESPONSE_BYTES + 1)), ) def test_direct_api_rejects_unexpected_success_content_type(): client = _load() response = Response(b"{}") response.headers.replace_header("Content-Type", "text/html") with pytest.raises(client.PolicyError, match="unexpected response type"): client.read( "/api/v1/repos/atlas/cassandra", token="runtime", opener=lambda *_a, **_k: response, ) def test_output_redaction_covers_exact_token_and_authorization_header(): client = _load() raw = b'{"message":"do-not-leak","debug":"Authorization: token do-not-leak"}' redacted = client.redact_bytes(raw, "do-not-leak") assert b"do-not-leak" not in redacted assert redacted.count(b"[REDACTED]") >= 1 @pytest.mark.parametrize( "reflected", [ b"runtime-sentinel", base64.b64encode(b"runtime-sentinel"), base64.b64encode(b"hermes-automation:runtime-sentinel"), ], ) def test_direct_api_rejects_credential_reflection(reflected: bytes): client = _load() with pytest.raises(client.PolicyError, match="credential material"): client.read( "/api/v1/repos/atlas/cassandra", token="runtime-sentinel", opener=lambda *_a, **_k: Response( b'{"unexpected":"' + reflected + b'"}' ), )