test_muse_sync_security.py python
114 lines 4.2 KB
Raw
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 1 day ago
1 """Security tests for the muse-sync hard gate (§KH2.8 security tier)."""
2
3 from __future__ import annotations
4
5 from pathlib import Path
6
7 from adapters.types import StatusResult
8 from tests.support import (
9 FIXTURES,
10 load_fixture_config,
11 make_runner,
12 ok,
13 run_cli,
14 seed_muse_substrate,
15 )
16 from tools.muse_sync import check_muse_sync
17
18
19 def test_fails_closed_when_dirty_state_unreadable(tmp_path: Path) -> None:
20 """Rule §KH2.4: never optimistically report 'synced' when the flags could not be read."""
21 config = load_fixture_config(tmp_path, "config-muse-git-mirror.yaml")
22 status = StatusResult(regime="muse+git-mirror", dirty=False, branch="main", muse_dirty=None, git_dirty=None)
23 report = check_muse_sync(config, status)
24 assert report.state == "unreadable"
25 assert not report.ok
26
27
28 def test_remediation_text_is_a_static_string_never_shell_invoked(tmp_path: Path) -> None:
29 """The remediation hint must be advisory text only — the kit never executes it."""
30 config = load_fixture_config(tmp_path, "config-muse-git-mirror.yaml")
31 status = StatusResult(
32 regime="muse+git-mirror", dirty=True, branch="main", muse_dirty=True, git_dirty=False
33 )
34 report = check_muse_sync(config, status)
35 assert report.remediation == 'muse code add -A && muse commit -m "<message>"'
36 # No shell metacharacter beyond the literal, static advisory string is ever built from
37 # untrusted input — report.message/remediation never interpolate repo content.
38 assert "$(" not in report.remediation
39 assert "`" not in report.remediation
40
41
42 def test_muse_sync_payload_never_leaks_command_output_or_paths(tmp_path: Path) -> None:
43 """The muse_sync report/payload carries only fixed enum-like state + static text — no
44 raw command stdout, file paths, or secret-shaped values ever flow into it."""
45 root = str(tmp_path.resolve())
46 runner = make_runner(
47 {
48 f"muse -C {root} rev-parse --abbrev-ref HEAD": ok("main"),
49 f"muse -C {root} status --json": ok(
50 '{"dirty": true, "secret_token": "sk-should-never-surface-1234567890"}'
51 ),
52 "git rev-parse --abbrev-ref HEAD": ok("main"),
53 "git status --porcelain": ok(""),
54 }
55 )
56 assert (
57 run_cli(
58 ["init", "--from-config", str(FIXTURES / "config-muse-git-mirror.yaml"), "--non-interactive"],
59 cwd=tmp_path,
60 runner=runner,
61 )
62 == 0
63 )
64 seed_muse_substrate(tmp_path)
65
66 import json as _json
67 from io import StringIO
68 from unittest.mock import patch
69
70 from cli.context import CliContext
71 from cli.main import main
72 from cli.output import OutputContext
73
74 out = StringIO()
75 with patch("sys.stdout", out):
76 main(
77 ["status", "--json"],
78 ctx=CliContext.create(cwd=tmp_path, runner=runner, output=OutputContext(json_mode=True)),
79 )
80 payload = _json.loads(out.getvalue())
81 assert "sk-should-never-surface" not in _json.dumps(payload["muse_sync"])
82 assert set(payload["muse_sync"].keys()) == {"state", "ok", "remediation", "message"}
83
84
85 def test_review_freeze_refusal_does_not_run_review_provider(tmp_path: Path) -> None:
86 """Least privilege: when muse_sync refuses, the freeze-review provider must never be invoked."""
87 from cli.kit_root import kit_root
88 from tests.support import findings_provider_factory, seed_freeze_repo
89
90 root = str(tmp_path.resolve())
91 artifact = seed_freeze_repo(tmp_path, config_name="config-muse-git-mirror.yaml")
92 runner = make_runner(
93 {
94 f"muse -C {root} rev-parse --abbrev-ref HEAD": ok("main"),
95 f"muse -C {root} status --json": ok('{"dirty": true}'),
96 "git rev-parse --abbrev-ref HEAD": ok("main"),
97 "git status --porcelain": ok(""),
98 }
99 )
100 provider_calls: list[str] = []
101
102 def _tracking_factory(name: str):
103 provider_calls.append(name)
104 return findings_provider_factory([])(name)
105
106 code = run_cli(
107 ["review", "--freeze", str(artifact.relative_to(tmp_path))],
108 cwd=tmp_path,
109 runner=runner,
110 kit=kit_root(),
111 review_provider_factory=_tracking_factory,
112 )
113 assert code == 2
114 assert provider_calls == []
File History 1 commit
sha256:a78e7e5a8740e03315f325d19edeb3aa1b306b3337d04abbaa9a9e0f3bbeb7a1 docs: MuseHub-first before ISR #74 — staging solidify NEXT Human 1 day ago