Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/agentrust_trace/content_marking.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,11 @@ def verify_assertion(assertion: dict[str, Any], record_bytes: bytes) -> dict[str
ref = data.get("record")
if not isinstance(ref, dict) or not ref.get("url"):
raise ContentMarkingError("assertion carries no record reference")
alg = ref.get("alg", "sha256")
alg = ref.get("alg")
if not isinstance(alg, str):
raise ContentMarkingError(
f"unsupported digest algorithm {alg!r}; use sha256 or sha384"
)
expected = ref.get("hash")
if not _DIGEST_RE.match(str(expected or "")):
raise ContentMarkingError(f"record.hash {expected!r} is not a sha256:/sha384: digest")
Expand Down
57 changes: 57 additions & 0 deletions tests/test_content_marking_required_alg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
from __future__ import annotations

import json

import pytest

from agentrust_trace.content_marking import ContentMarkingError, build_assertion, verify_assertion

URL = "https://registry.example/records/abc123.json"


def _record_bytes() -> bytes:
return json.dumps(
{
"eat_profile": "tag:agentrust-io.com,2026:trace-v0.2",
"iat": 1760000000,
"subject": "spiffe://example.org/agent/image-bot",
"data_class": "public",
}
).encode()


def test_missing_record_alg_is_refused_instead_of_defaulting_to_sha256() -> None:
raw = _record_bytes()
assertion = build_assertion(raw, url=URL)
del assertion["data"]["record"]["alg"]

with pytest.raises(ContentMarkingError, match="unsupported digest algorithm None"):
verify_assertion(assertion, raw)


def test_null_record_alg_is_refused() -> None:
raw = _record_bytes()
assertion = build_assertion(raw, url=URL)
assertion["data"]["record"]["alg"] = None

# Pins the plausible near-miss where explicit null is treated as "unset"
# and silently replaced with the old SHA-256 default.
with pytest.raises(ContentMarkingError, match="unsupported digest algorithm None"):
verify_assertion(assertion, raw)


@pytest.mark.parametrize("bad_alg", [[1], [], {"a": 1}, {}])
def test_unhashable_record_alg_is_refused_without_type_error(bad_alg) -> None:
raw = _record_bytes()
assertion = build_assertion(raw, url=URL)
assertion["data"]["record"]["alg"] = bad_alg
assertion["data"]["record"]["hash"] = "nope"

with pytest.raises(ContentMarkingError, match="unsupported digest algorithm"):
verify_assertion(assertion, raw)


def test_explicit_sha256_and_sha384_still_verify() -> None:
raw = _record_bytes()
verify_assertion(build_assertion(raw, url=URL, alg="sha256"), raw)
verify_assertion(build_assertion(raw, url=URL, alg="sha384"), raw)
Loading