test_api_provider_security.py
python
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
1 day ago
| 1 | """Security tests for headless API freeze provider (K11).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | import io |
| 6 | import json |
| 7 | from contextlib import redirect_stdout |
| 8 | from pathlib import Path |
| 9 | from unittest.mock import patch |
| 10 | |
| 11 | from cli.context import CliContext |
| 12 | from cli.kit_root import kit_root |
| 13 | from cli.main import main |
| 14 | from cli.output import OutputContext |
| 15 | from tests.support import ( |
| 16 | FakeHttpTransport, |
| 17 | api_provider_factory, |
| 18 | git_status_runner, |
| 19 | run_cli, |
| 20 | seed_freeze_repo, |
| 21 | write_config, |
| 22 | ) |
| 23 | |
| 24 | |
| 25 | def test_api_key_not_leaked_in_request_headers(tmp_path: Path, monkeypatch) -> None: |
| 26 | secret = "super-secret-api-key-value" |
| 27 | monkeypatch.setenv("OVERSEER_REVIEW_API_KEY", secret) |
| 28 | monkeypatch.setenv("OVERSEER_REVIEW_API_URL", "https://review.example.com/v1") |
| 29 | artifact = seed_freeze_repo(tmp_path, config_name="config-api-reviewer.yaml") |
| 30 | rel = artifact.relative_to(tmp_path).as_posix() |
| 31 | transport = FakeHttpTransport(review_body=json.dumps({"findings": []}).encode("utf-8")) |
| 32 | buffer = io.StringIO() |
| 33 | ctx = CliContext.create( |
| 34 | runner=git_status_runner(), |
| 35 | cwd=tmp_path, |
| 36 | kit=kit_root(), |
| 37 | output=OutputContext(json_mode=True), |
| 38 | review_provider_factory=api_provider_factory(transport), |
| 39 | ) |
| 40 | with redirect_stdout(buffer): |
| 41 | code = main(["review", "--freeze", rel, "--json"], ctx=ctx) |
| 42 | assert code == 0 |
| 43 | out = buffer.getvalue() |
| 44 | assert secret not in out |
| 45 | auth_headers = [call["headers"].get("Authorization", "") for call in transport.calls] |
| 46 | assert auth_headers |
| 47 | assert all(header.startswith("Bearer ") and secret in header for header in auth_headers) |
| 48 | |
| 49 | |
| 50 | def test_api_artifact_injection_no_shell(tmp_path: Path, monkeypatch) -> None: |
| 51 | monkeypatch.setenv("OVERSEER_REVIEW_API_KEY", "ci-key") |
| 52 | monkeypatch.setenv("OVERSEER_REVIEW_API_URL", "https://review.example.com/v1") |
| 53 | write_config(tmp_path, "config-api-reviewer.yaml") |
| 54 | artifact = tmp_path / "docs" / "evil.md" |
| 55 | artifact.parent.mkdir(parents=True) |
| 56 | artifact.write_text( |
| 57 | "# evil\n\n```yaml\nphase: x\noutputs:\n - id: a\n path: docs/a.md\n frozen: true\n```\n\n$(rm -rf /)\n", |
| 58 | encoding="utf-8", |
| 59 | ) |
| 60 | transport = FakeHttpTransport(review_body=json.dumps({"findings": []}).encode("utf-8")) |
| 61 | with patch("adapters.runner.subprocess.run") as mocked: |
| 62 | code = run_cli( |
| 63 | ["review", "--freeze", "docs/evil.md", "--provider", "api"], |
| 64 | cwd=tmp_path, |
| 65 | runner=git_status_runner(), |
| 66 | kit=kit_root(), |
| 67 | review_provider_factory=api_provider_factory(transport), |
| 68 | ) |
| 69 | assert mocked.call_count == 0 or all("rm -rf" not in str(call) for call in mocked.call_args_list) |
| 70 | assert code in {0, 7, 8} |
| 71 | post_calls = [call for call in transport.calls if call["method"] == "POST"] |
| 72 | assert post_calls |
| 73 | body_text = post_calls[0]["body"].decode("utf-8") |
| 74 | assert "rm -rf" in body_text |
| 75 | assert "$(rm -rf /)" in body_text |
| 76 | |
| 77 | |
| 78 | def test_api_path_escape_refused(tmp_path: Path, monkeypatch) -> None: |
| 79 | monkeypatch.setenv("OVERSEER_REVIEW_API_KEY", "ci-key") |
| 80 | monkeypatch.setenv("OVERSEER_REVIEW_API_URL", "https://review.example.com/v1") |
| 81 | seed_freeze_repo(tmp_path, config_name="config-api-reviewer.yaml") |
| 82 | transport = FakeHttpTransport() |
| 83 | code = run_cli( |
| 84 | ["review", "--freeze", "../outside.md", "--provider", "api"], |
| 85 | cwd=tmp_path, |
| 86 | runner=git_status_runner(), |
| 87 | kit=kit_root(), |
| 88 | review_provider_factory=api_provider_factory(transport), |
| 89 | ) |
| 90 | assert code == 4 |
| 91 | assert transport.calls == [] |
File History
2 commits
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1
docs: MuseHub-first before ISR #74 — staging solidify NEXT
Human
1 day ago
sha256:4671b7f787ddbe63ced31c895b688c77ab495653b65a730b423329f26b3c1439
feat: K1-P1 complete — agent provenance, build-verification…
Sonnet 4.6
patch
52 days ago