fix(storage): harden portable path diagnostics

This commit is contained in:
2026-07-11 21:25:03 +02:00
parent eacb139e22
commit 7f3f6f7ce7
4 changed files with 100 additions and 6 deletions
+61 -1
View File
@@ -90,7 +90,67 @@ def test_doctor_json_does_not_echo_invalid_config_values(monkeypatch, tmp_path):
payload = json.loads(result.stdout)
assert payload["components"]["config"] == {
"status": "error",
"message": "configuration is invalid",
"message": "configuration is invalid or unreadable",
}
assert "super-secret" not in result.stdout
assert "pii_user" not in result.stdout
def test_doctor_json_normalizes_malformed_yaml(monkeypatch, tmp_path):
cfg = tmp_path / "demo.yaml"
cfg.write_text("password: super-secret\nroots: [unterminated")
monkeypatch.setenv("THT_DATA_ROOT", str(tmp_path / "data"))
result = runner.invoke(app, ["doctor", "--json", "--config", str(cfg)])
assert result.exit_code == 1
assert json.loads(result.stdout)["components"]["config"] == {
"status": "error",
"message": "configuration is invalid or unreadable",
}
assert result.stderr == ""
assert "super-secret" not in result.stdout
assert "Traceback" not in result.stdout
def test_doctor_json_normalizes_unreadable_config(monkeypatch, tmp_path):
cfg = tmp_path / "demo.yaml"
cfg.mkdir()
monkeypatch.setenv("THT_DATA_ROOT", str(tmp_path / "data"))
result = runner.invoke(app, ["doctor", "--json", "--config", str(cfg)])
assert result.exit_code == 1
assert json.loads(result.stdout)["components"]["config"] == {
"status": "error",
"message": "configuration is invalid or unreadable",
}
assert result.stderr == ""
def test_doctor_human_output_is_actionable_and_redacted(monkeypatch, tmp_path):
cfg = _config(tmp_path / "demo.yaml", absolute_sessions=tmp_path / "patient-private")
monkeypatch.delenv("THT_DATA_ROOT", raising=False)
result = runner.invoke(app, ["doctor", "--config", str(cfg)])
assert result.exit_code == 0
assert "data_root: warning - set THT_DATA_ROOT to enable portable storage" in result.stdout
assert "workspace_paths: warning - absolute legacy roots: sessions" in result.stdout
assert str(tmp_path) not in result.stdout
assert "patient_db" not in result.stdout
assert "super-secret" not in result.stdout
def test_doctor_human_config_error_is_actionable_and_redacted(monkeypatch, tmp_path):
cfg = tmp_path / "patient-private.yaml"
cfg.write_text("password: super-secret\nroots: [unterminated")
monkeypatch.setenv("THT_DATA_ROOT", str(tmp_path / "data"))
result = runner.invoke(app, ["doctor", "--config", str(cfg)])
assert result.exit_code == 1
assert "config: error - configuration is invalid or unreadable" in result.stdout
assert str(tmp_path) not in result.stdout
assert "super-secret" not in result.stdout
assert result.stderr == ""
+22
View File
@@ -57,6 +57,28 @@ def test_path_escape_is_rejected(tmp_path):
resolve_workspace_paths(cfg_path, cfg, tmp_path / "data")
def test_workspace_symlink_escape_is_rejected(tmp_path):
cfg_path = _write_config(tmp_path / "demo.yaml")
cfg = load_config(cfg_path)
data_root = tmp_path / "data"
(data_root / "workspaces").mkdir(parents=True)
(data_root / "workspaces" / "demo").symlink_to(tmp_path / "private", target_is_directory=True)
with pytest.raises(ConfigError, match="outside workspaces root"):
resolve_workspace_paths(cfg_path, cfg, data_root)
def test_nested_root_symlink_escape_is_rejected(tmp_path):
cfg_path = _write_config(tmp_path / "demo.yaml")
cfg = load_config(cfg_path)
workspace = tmp_path / "data/workspaces/demo"
workspace.mkdir(parents=True)
(workspace / "sessions").symlink_to(tmp_path / "private", target_is_directory=True)
with pytest.raises(ConfigError, match="outside workspace root"):
resolve_workspace_paths(cfg_path, cfg, tmp_path / "data")
def test_data_root_environment_activates_portable_paths(monkeypatch, tmp_path):
cfg_path = _write_config(tmp_path / "demo.yaml")
monkeypatch.setenv("THT_DATA_ROOT", str(tmp_path / "data"))