test_print_next_security.py
python
sha256:8d7f41aafae41deee70035a92f93602ef1722a290153597451e5932af503a42c
docs: queue board-identity follow-ups so they survive the session
Human
1 day ago
| 1 | """Security — path/lane refusal + honesty greps (§ONS.12 / §ONS.11).""" |
| 2 | |
| 3 | from __future__ import annotations |
| 4 | |
| 5 | import json |
| 6 | from pathlib import Path |
| 7 | |
| 8 | from cli.kit_root import kit_root |
| 9 | from tests.support import FIXTURES, git_status_runner, run_cli, write_config |
| 10 | |
| 11 | REQUIRED_SUBSTRINGS = ( |
| 12 | "## CURRENT NEXT — paste this", |
| 13 | "ok next", |
| 14 | "read from disk after write", |
| 15 | "Session incomplete without it", |
| 16 | "do not guarantee an accurate open tab", |
| 17 | ) |
| 18 | |
| 19 | HONESTY = ( |
| 20 | "Host niceties improve odds of tab refresh; they do **not** guarantee an accurate open" |
| 21 | ) |
| 22 | |
| 23 | |
| 24 | def test_config_outside_repo_exit_4(tmp_path: Path) -> None: |
| 25 | write_config(tmp_path, "config-git-only.yaml") |
| 26 | outside = tmp_path.parent / "outside-config.yaml" |
| 27 | outside.write_text( |
| 28 | (FIXTURES / "config-git-only.yaml").read_text(encoding="utf-8"), |
| 29 | encoding="utf-8", |
| 30 | ) |
| 31 | docs = tmp_path / "docs" |
| 32 | docs.mkdir(parents=True, exist_ok=True) |
| 33 | (docs / "OVERSEER-HANDOVER.md").write_text( |
| 34 | (FIXTURES / "print-next-handover.md").read_text(encoding="utf-8"), |
| 35 | encoding="utf-8", |
| 36 | ) |
| 37 | code = run_cli( |
| 38 | ["next", "--config", str(outside)], |
| 39 | cwd=tmp_path, |
| 40 | runner=git_status_runner(), |
| 41 | kit=kit_root(), |
| 42 | ) |
| 43 | assert code == 4 |
| 44 | |
| 45 | |
| 46 | def test_lane_path_escape_exit_2(tmp_path: Path) -> None: |
| 47 | write_config(tmp_path, "config-git-only.yaml") |
| 48 | docs = tmp_path / "docs" |
| 49 | docs.mkdir(parents=True, exist_ok=True) |
| 50 | (docs / "OVERSEER-HANDOVER.md").write_text( |
| 51 | (FIXTURES / "print-next-handover.md").read_text(encoding="utf-8"), |
| 52 | encoding="utf-8", |
| 53 | ) |
| 54 | code = run_cli( |
| 55 | ["next", "--lane", "../x"], |
| 56 | cwd=tmp_path, |
| 57 | runner=git_status_runner(), |
| 58 | kit=kit_root(), |
| 59 | ) |
| 60 | assert code == 2 |
| 61 | |
| 62 | |
| 63 | def test_git_only_zero_muse_argv(tmp_path: Path) -> None: |
| 64 | write_config(tmp_path, "config-git-only.yaml") |
| 65 | docs = tmp_path / "docs" |
| 66 | docs.mkdir(parents=True, exist_ok=True) |
| 67 | (docs / "OVERSEER-HANDOVER.md").write_text( |
| 68 | (FIXTURES / "print-next-handover.md").read_text(encoding="utf-8"), |
| 69 | encoding="utf-8", |
| 70 | ) |
| 71 | runner = git_status_runner() |
| 72 | code = run_cli(["next"], cwd=tmp_path, runner=runner, kit=kit_root()) |
| 73 | assert code == 0 |
| 74 | assert runner.calls == [] |
| 75 | |
| 76 | |
| 77 | def test_skill_rule_required_substrings() -> None: |
| 78 | root = kit_root() |
| 79 | skill = (root / "cursor" / "skills" / "print-next" / "SKILL.md").read_text(encoding="utf-8") |
| 80 | rule = (root / "cursor" / "rules" / "print-next-closeout.mdc").read_text(encoding="utf-8") |
| 81 | for needle in REQUIRED_SUBSTRINGS: |
| 82 | assert needle in skill, needle |
| 83 | assert needle in rule, needle |
| 84 | |
| 85 | |
| 86 | def test_print_next_doc_honesty_sentence() -> None: |
| 87 | text = (kit_root() / "docs" / "PRINT-NEXT.md").read_text(encoding="utf-8") |
| 88 | assert HONESTY in text |
| 89 | |
| 90 | |
| 91 | def test_stop_hook_fail_closed_false() -> None: |
| 92 | raw = (kit_root() / "cursor" / "hooks" / "print-next-stop.json").read_text(encoding="utf-8") |
| 93 | data = json.loads(raw) |
| 94 | stop = data["hooks"]["stop"][0] |
| 95 | assert stop["failClosed"] is False |
| 96 | |
| 97 | |
| 98 | def test_fake_secret_printed_as_data_not_executed(tmp_path: Path, capsys) -> None: |
| 99 | write_config(tmp_path, "config-git-only.yaml") |
| 100 | docs = tmp_path / "docs" |
| 101 | docs.mkdir(parents=True, exist_ok=True) |
| 102 | token = "sk-test-not-a-real-secret-ons" |
| 103 | (docs / "OVERSEER-HANDOVER.md").write_text( |
| 104 | "# H\n\n### Paste-ready prompt\n\n```text\n" |
| 105 | f"Token {token}\nModel: Auto\n```\n", |
| 106 | encoding="utf-8", |
| 107 | ) |
| 108 | runner = git_status_runner() |
| 109 | code = run_cli(["next"], cwd=tmp_path, runner=runner, kit=kit_root()) |
| 110 | out = capsys.readouterr().out |
| 111 | assert code == 0 |
| 112 | assert token in out |
| 113 | for cmd, _cwd in runner.calls: |
| 114 | assert token not in cmd |
| 115 | |
| 116 | |
| 117 | def test_provenance_leaks_no_secrets(tmp_path: Path, capsys) -> None: |
| 118 | """§NXP.8 security: provenance emits name/path/lane/timestamp only.""" |
| 119 | from tools.print_next.extract import set_read_at_clock |
| 120 | |
| 121 | write_config(tmp_path, "config-git-only.yaml") |
| 122 | # Inject a fake token into config to ensure it is not echoed in provenance. |
| 123 | cfg = tmp_path / ".overseer" / "config.yaml" |
| 124 | cfg.write_text( |
| 125 | cfg.read_text(encoding="utf-8") + "\n# secret: sk-config-should-not-leak\n", |
| 126 | encoding="utf-8", |
| 127 | ) |
| 128 | docs = tmp_path / "docs" |
| 129 | docs.mkdir(parents=True, exist_ok=True) |
| 130 | (docs / "OVERSEER-HANDOVER.md").write_text( |
| 131 | (FIXTURES / "print-next-handover.md").read_text(encoding="utf-8"), |
| 132 | encoding="utf-8", |
| 133 | ) |
| 134 | (docs / "ROADMAP.md").write_text("# Roadmap\n", encoding="utf-8") |
| 135 | set_read_at_clock(lambda: "2026-09-04T12:00:00Z") |
| 136 | try: |
| 137 | code = run_cli(["next"], cwd=tmp_path, runner=git_status_runner(), kit=kit_root()) |
| 138 | out = capsys.readouterr().out |
| 139 | assert code == 0 |
| 140 | # Provenance line only — isolate the Source line. |
| 141 | prov = [ln for ln in out.splitlines() if ln.startswith("**Source:**")][0] |
| 142 | assert "sk-config-should-not-leak" not in prov |
| 143 | assert "token" not in prov.lower() |
| 144 | assert "github.com" not in prov |
| 145 | assert "remote" not in prov |
| 146 | assert "test-git" in prov |
| 147 | assert tmp_path.resolve().as_posix() in prov |
| 148 | assert "read `" in prov |
| 149 | finally: |
| 150 | set_read_at_clock(None) |
File History
1 commit
sha256:8d7f41aafae41deee70035a92f93602ef1722a290153597451e5932af503a42c
docs: queue board-identity follow-ups so they survive the session
Human
1 day ago