fix: close evidence credential disclosure gaps
This commit is contained in:
@@ -363,16 +363,16 @@ evidence:
|
||||
root: {tmp_path}
|
||||
max_bytes: 123
|
||||
- type: http
|
||||
urls: ['https://example.test/doc.md?token=transport-only']
|
||||
urls: ['https://example.test/doc.md']
|
||||
""")
|
||||
cfg = load_config(modern)
|
||||
assert "transport-only" not in repr(cfg.evidence)
|
||||
assert "transport-only" not in cfg.evidence.model_dump_json()
|
||||
assert "example.test" not in repr(cfg.evidence)
|
||||
assert "example.test" not in cfg.evidence.model_dump_json()
|
||||
assert cfg.evidence.sources[1].allow_private_hosts is False
|
||||
sources = build_evidence_sources(cfg)
|
||||
assert isinstance(sources[0], FilesystemEvidenceSource)
|
||||
assert isinstance(sources[1], HttpManifestEvidenceSource)
|
||||
assert "transport-only" not in repr(sources[1])
|
||||
assert "example.test" not in repr(sources[1])
|
||||
|
||||
legacy = tmp_path / "legacy.yaml"
|
||||
(tmp_path / "curated").mkdir()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import json
|
||||
import traceback
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
@@ -112,6 +113,33 @@ def test_signed_http_file_resolves_in_memory_and_preserves_provenance_order(tmp_
|
||||
assert_no_canaries(repr(adapter))
|
||||
|
||||
|
||||
def test_signed_http_file_requires_explicit_provenance_urls(tmp_path):
|
||||
secret_file = tmp_path / "signed-urls.json"
|
||||
secret_file.write_text(json.dumps([
|
||||
f"https://evidence.example.test/guide.md?token={SIGNED_CANARY}",
|
||||
]))
|
||||
path = write_config(tmp_path, {
|
||||
"type": "http", "signed_urls_file": str(secret_file),
|
||||
})
|
||||
|
||||
with pytest.raises(ConfigError) as caught:
|
||||
load_config(path)
|
||||
assert "provenance" in str(caught.value).lower()
|
||||
assert_no_canaries(caught.value)
|
||||
|
||||
|
||||
def test_public_http_rejects_inline_query_bearing_transport_urls(tmp_path):
|
||||
path = write_config(tmp_path, {
|
||||
"type": "http",
|
||||
"urls": [f"https://evidence.example.test/guide.md?token={SIGNED_CANARY}"],
|
||||
})
|
||||
|
||||
with pytest.raises(ConfigError) as caught:
|
||||
load_config(path)
|
||||
assert "evidence.sources.0" in str(caught.value)
|
||||
assert_no_canaries(caught.value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("contents", [
|
||||
"{malformed", json.dumps({"url": "https://evidence.example.test/guide.md"}),
|
||||
json.dumps([]), json.dumps(["https://evidence.example.test/guide.md", 3]),
|
||||
@@ -264,6 +292,7 @@ def test_validation_repr_cli_and_exception_output_never_disclose_transport_secre
|
||||
with pytest.raises(ConfigError) as caught:
|
||||
load_config(path)
|
||||
assert_no_canaries(caught.value)
|
||||
assert_no_canaries("".join(traceback.format_exception(caught.value)))
|
||||
|
||||
valid_file = tmp_path / "valid-signed-urls.json"
|
||||
valid_file.write_text(json.dumps([
|
||||
|
||||
@@ -60,6 +60,8 @@ def _resolve_http_signed_url_files(value: Any) -> Any:
|
||||
return resolved
|
||||
if "urls" in resolved:
|
||||
raise ConfigError("HTTP signed URL file cannot be combined with urls")
|
||||
if "provenance_urls" not in resolved:
|
||||
raise ConfigError("HTTP signed URL file requires provenance_urls")
|
||||
path_value = resolved.pop("signed_urls_file")
|
||||
if not isinstance(path_value, str):
|
||||
raise ConfigError("Invalid signed URL file reference")
|
||||
@@ -76,8 +78,8 @@ def _resolve_http_signed_url_files(value: Any) -> Any:
|
||||
if len(payload) > _MAX_SIGNED_URL_FILE_BYTES:
|
||||
raise OSError
|
||||
parsed = json.loads(payload.decode("utf-8"))
|
||||
except (OSError, UnicodeError, json.JSONDecodeError) as exc:
|
||||
raise ConfigError("Cannot read signed URL file") from exc
|
||||
except (OSError, UnicodeError, json.JSONDecodeError):
|
||||
raise ConfigError("Cannot read signed URL file") from None
|
||||
if (
|
||||
not isinstance(parsed, list)
|
||||
or not parsed
|
||||
@@ -307,6 +309,8 @@ class HttpEvidenceSourceConfig(BaseModel):
|
||||
):
|
||||
raise ValueError("HTTP transport URL must use http or https")
|
||||
if self.provenance_urls is None:
|
||||
if canonical != transport_urls or len(set(canonical)) != len(canonical):
|
||||
raise ValueError("Public HTTP URLs must be canonical query-free identities")
|
||||
return self
|
||||
try:
|
||||
provenance = [canonical_provenance_uri(url) for url in self.provenance_urls]
|
||||
@@ -532,7 +536,7 @@ def load_config(path: Path) -> Config:
|
||||
cfg = Config.model_validate(translated)
|
||||
except ValidationError as e:
|
||||
details = _format_validation_error(e)
|
||||
raise ConfigError(f"Configurazione non valida in {path}:\n{details}") from e
|
||||
raise ConfigError(f"Configurazione non valida in {path}:\n{details}") from None
|
||||
env_profile = os.environ.get("THT_PROFILE")
|
||||
if env_profile is not None:
|
||||
if env_profile not in ("server", "workstation"):
|
||||
|
||||
Reference in New Issue
Block a user