diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 237ef27..bb2d917 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,8 @@ jobs: matrix: python-version: ["3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v4 + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v5.3.0 with: python-version: ${{ matrix.python-version }} - name: Install package and test deps diff --git a/schemas/trace-claim.json b/schemas/trace-claim.json index ced6a9e..3e4f81f 100644 --- a/schemas/trace-claim.json +++ b/schemas/trace-claim.json @@ -11,7 +11,7 @@ "properties": { "eat_profile": {"type": "string", "const": "tag:agentrust.io,2026:trace-v0.1"}, "iat": {"type": "integer", "minimum": 1700000000}, - "subject": {"type": "string", "pattern": "^spiffe://"}, + "subject": {"type": "string", "pattern": "^(spiffe://|did:)"}, "model": { "type": "object", "required": ["provider", "model_id"], diff --git a/src/trace_tests/__init__.py b/src/trace_tests/__init__.py index af7e308..df4daf9 100644 --- a/src/trace_tests/__init__.py +++ b/src/trace_tests/__init__.py @@ -1,3 +1,3 @@ """TRACE conformance test suite.""" -__version__ = "0.1.0" +__version__ = "0.2.0" diff --git a/src/trace_tests/modules/tr_env.py b/src/trace_tests/modules/tr_env.py index 2da4d33..0fc72f2 100644 --- a/src/trace_tests/modules/tr_env.py +++ b/src/trace_tests/modules/tr_env.py @@ -2,12 +2,14 @@ from __future__ import annotations +import re import time from typing import Any from trace_tests.result import Finding, Status _PROFILE = "tag:agentrust.io,2026:trace-v0.1" +_SUBJECT_RE = re.compile(r'^(spiffe://[^/]+/.+|did:[a-z0-9]+:.+)$') _IAT_MIN = 1_700_000_000 #: Default maximum record age (seconds). Records older than this fail freshness. @@ -45,7 +47,7 @@ def check(trace: dict[str, Any], max_age_seconds: int = DEFAULT_MAX_AGE_SECONDS) findings.append(Finding("TR-ENV-002", Status.FAIL, f"iat must be a Unix timestamp >= {_IAT_MIN}, got {iat!r}")) subject = trace.get("subject", "") - if isinstance(subject, str) and subject.startswith(("spiffe://", "did:")): + if isinstance(subject, str) and _SUBJECT_RE.match(subject): findings.append(Finding("TR-ENV-003", Status.PASS, f"subject is a valid workload identity URI ({subject!r})")) else: findings.append(Finding("TR-ENV-003", Status.FAIL, f"subject must be a SPIFFE URI (spiffe://) or DID URI (did:), got {subject!r}")) diff --git a/src/trace_tests/modules/tr_sig.py b/src/trace_tests/modules/tr_sig.py index 4657a8f..c9d40cc 100644 --- a/src/trace_tests/modules/tr_sig.py +++ b/src/trace_tests/modules/tr_sig.py @@ -102,6 +102,15 @@ def check(trace: dict[str, Any], record: dict[str, Any], fmt: str, level: int = jwk = trace.get("cnf", {}).get("jwk", {}) kty = jwk.get("kty") crv = jwk.get("crv") + x = jwk.get("x") + + if "d" in jwk: + findings.append(Finding( + rule="TR-SIG-002", + status=Status.FAIL, + message="cnf.jwk must not contain private key material ('d' field present in JWK)", + )) + return findings if kty in _SUPPORTED_KTY: label = f"kty={kty!r}" + (f", crv={crv!r}" if crv else "") diff --git a/tests/unit/test_loader.py b/tests/unit/test_loader.py index 9115603..0c21657 100644 --- a/tests/unit/test_loader.py +++ b/tests/unit/test_loader.py @@ -53,8 +53,11 @@ def test_stripping_cmcp_version_is_rejected_not_downgraded(tmp_path): load_record(_write(tmp_path, stripped)) -@pytest.mark.parametrize("leftover", ["trace", "gateway", "signature"]) +@pytest.mark.parametrize("leftover", ["trace", "gateway"]) def test_partial_envelope_with_single_cmcp_key_is_rejected(tmp_path, leftover): + # "signature" is intentionally excluded: plain TRACE records may carry an + # embedded signature field (agentrust-trace sign_record() output), so the + # loader allows it. Only "trace" and "gateway" are unambiguous cmcp markers. record = {**VALID_TRACE, leftover: VALID_CMCP[leftover]} with pytest.raises(LoadError, match="partial cmcp-runtime envelope"): load_record(_write(tmp_path, record))