content_marking: validate record.alg against record.hash prefix, reject unknown assertion fields, and check anchor agreement
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 35/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Stale
- Tech stack
- python
- Domain
- backend-api-design
Research direction
Read src/agentrust_trace/content_marking.py alongside sections 2 and 4 of spec/content-marking-v1.md; focus on verify_assertion and build_assertion. Reproduce the mismatched algorithm and hash prefix case described in the issue, then inspect the unknown-field and anchor cases. Done means the three validation gaps behave as specified, with malformed assertions raising ContentMarkingError and anchor disagreement raising RecordMismatch; pull request #621 is already open.
Written by the indexing model from the issue text.
Description
What is wrong
At main (a8e3559), src/agentrust_trace/content_marking.py has three consumer validation and error-classification gaps in verify_assertion and build_assertion:
-
verify_assertionraisesRecordMismatch(accusing the server atrecord.url) when the assertion's ownrecord.algandrecord.hashprefix disagree, and checksrecord_bytesbefore validatingrecord.alg:- In
src/agentrust_trace/content_marking.py:391-418,alg = ref.get("alg")is only checked forisinstance(alg, str)beforerecord_bytesis validated, andexpected = ref.get("hash")is matched against_DIGEST_RE(^sha(256:[0-9a-f]{64}|384:[0-9a-f]{96})$), which admits bothsha256:andsha384:. - When
assertion["data"]["record"]["alg"] = "sha256"andassertion["data"]["record"]["hash"] = "sha384:" + "a" * 96(oralg = "sha384"andhash = "sha256:" + "a" * 64),_digest(record_bytes, alg)computes asha256:digest, compares it to"sha384:...", and raisesRecordMismatch("the record at ... does not match the assertion ... The record changed after the asset was signed, or the URL is serving a different one") for a self-contradictory assertion that no server response could ever satisfy. - Similarly, when
alg = "md5"andrecord_bytes = b"",verify_assertionblamesrecord_bytesbefore checkingalg in _ALGS.
- In
-
verify_assertiondoes not reject unknown fields onassertion,assertion["data"], orassertion["data"]["record"]:spec/content-marking-v1.mdsection 2 defines the exact fields ofcom.agentrust-io.trace({"label", "data"},{"version", "record", "subject", "eat_profile", "anchor"}, and{"url", "hash", "alg"}), butverify_assertionignores arbitrary extra fields on all three objects.
-
verify_assertiondoes not checkanchoragreement when the resolved record carriestransparency/anchor:verify_assertion(lines 434-448) checksrecord_subject != data.get("subject")andrecord_profile != data.get("eat_profile"), and validatesif "anchor" in recordat line 434, but never checks whether a presentdata["anchor"]disagrees withrecord["anchor"]/record["transparency"](raisingRecordMismatchwhen they disagree).
Reproduction on a8e3559:
import json
from agentrust_trace.content_marking import build_assertion, verify_assertion
raw = json.dumps({
"eat_profile": "tag:agentrust-io.com,2026:trace-v0.2",
"iat": 1760000000,
"subject": "spiffe://example.org/agent/image-bot",
}).encode()
a = build_assertion(raw, url="https://registry.example/records/abc123.json", alg="sha384")
a["data"]["record"]["alg"] = "sha256" # hash is sha384:..., alg is sha256
# Raises RecordMismatch (blaming the server) instead of plain ContentMarkingError:
verify_assertion(a, raw)
Spec section or file
src/agentrust_trace/content_marking.py(verify_assertion,build_assertion)spec/content-marking-v1.mdsections 2 and 4
Expected behavior
- In
verify_assertion, validatealg in _ALGSandexpected.startswith(f"{alg}:")before inspectingrecord_bytes, raising plainContentMarkingError(notRecordMismatch) whenrecord.algis unsupported or disagrees with the prefix ofrecord.hash. - Reject unknown fields in
assertion({"label", "data"}),assertion["data"]({"version", "record", "subject", "eat_profile", "anchor"}), andassertion["data"]["record"]({"url", "hash", "alg"}) withContentMarkingError. - When
anchoris present inassertion["data"]and the resolved record also carriesanchor(ortransparency), raiseRecordMismatchif they disagree.
Impact
A malformed assertion with mismatched record.alg and record.hash prefix raises RecordMismatch (falsely accusing the remote server of modifying the record), and mismatched anchor values between the assertion and record pass verify_assertion.
- Dominant language
- Python
- Stars
- 37
- Forks
- 33
- Avg merge
- 1d 7h
- Merged PRs (30d)
- 111
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from agentrust-io/trace-spec
-
prototype(verifier_token,execution_appraisal): enforce canonical 32-byte nonces, validate RelyingParty.challenge/verify_proof inputs, and reject non-finite JWT JSONPossibly taken @drk-ram claimed this today. Openneeds-review:MEDIUM
Difficulty 4/5 3-5 days Newbie friendliness 35/100
agentrust-io/trace-spec#629 · 1 comment ·
Maintainers usually reply within 1 day
-
fix(platform_measurement): reject non-string or empty runtime.measurement across Python and TypeScriptPossibly taken @drk-ram claimed this today. Openneeds-review:MEDIUM
Difficulty 2/5 1-3 hours Newbie friendliness 30/100
agentrust-io/trace-spec#628 · 1 comment ·
Maintainers usually reply within 1 day
-
fix(provenance,validate): remove str() coercion on digest/hash fields in provenance and enforce strict JSON in schema loadersPossibly taken @drk-ram claimed this today. Openneeds-review:MEDIUM
Difficulty 3/5 1-2 days Newbie friendliness 35/100
agentrust-io/trace-spec#627 · 1 comment ·
Maintainers usually reply within 1 day
-
conformance(runner): reject boolean/float level, invalid fmt, and malformed caller options at run() entryPossibly taken @drk-ram claimed this today. Openneeds-review:MEDIUM
Difficulty 2/5 1-3 hours Newbie friendliness 35/100
agentrust-io/trace-spec#626 · 1 comment ·
Maintainers usually reply within 1 day
-
conformance(inclusion): enforce registry-anchor-v1 surrogate/key/cycle rules and validate leaf_index/entry fieldsPossibly taken @drk-ram claimed this today. Openneeds-review:MEDIUM
Difficulty 3/5 1-2 days Newbie friendliness 35/100
agentrust-io/trace-spec#625 · 1 comment ·
Maintainers usually reply within 1 day
All issues in agentrust-io/trace-spec
Similar issues
-
The changelog workflow excludes otelbot but not dependabot, so every Dependabot pull request fails the changelog checkPossibly taken @ocelotl claimed this today. Open
Difficulty 1/5 Under an hour Newbie friendliness 82/100
open-telemetry/opentelemetry-python#5825 ·
Maintainers usually reply within 2 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
cuga-project/cuga-agent#1020 ·
Maintainers usually reply within 2 days
-
Difficulty 1/5 Under an hour Newbie friendliness 70/100
microsoft/playwright#43640 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
stac-utils/stac-fastapi-elasticsearch-opensearch#965 ·
Maintainers usually reply within 2 days
-
from-review-extraction library privacy python severity:suggested
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
LearningCircuit/local-deep-research#7951 ·
Maintainers usually reply within 2 days