From c4180d298479485b3553516601b096ce7f99c89f Mon Sep 17 00:00:00 2001 From: Mary Dickson Date: Wed, 18 Mar 2026 10:39:52 -0700 Subject: [PATCH 1/3] fix(xtest): rename policy binding assertions to reflect tamper classification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Policy binding failures are integrity failures (tamper), not KAS request errors. KAS intentionally returns a generic "bad request" for these to avoid leaking secret key material information, and the SDK classifies them under ErrTampered. Rename assert_kas_request_error → assert_policy_tamper_error and update the docstring and expected patterns to match. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Mary Dickson --- xtest/test_tdfs.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/xtest/test_tdfs.py b/xtest/test_tdfs.py index 882d7e27..916698be 100644 --- a/xtest/test_tdfs.py +++ b/xtest/test_tdfs.py @@ -536,30 +536,27 @@ def assert_tamper_error( ) -def assert_kas_request_error( +def assert_policy_tamper_error( exc: subprocess.CalledProcessError, decrypt_sdk: tdfs.SDK ) -> None: - """Assert that a KAS request error was returned. + """Assert that a policy binding tamper error was returned. - Used for policy binding failures where KAS rejects the request (400). - Accepts both the new error classification (KAS request error) and the - legacy classification (tamper) for backward compatibility with older - SDK versions. + Policy binding failures (unbound or altered policy) are integrity + failures. KAS intentionally returns a generic "bad request" to avoid + leaking information about secret key computations, and the SDK + classifies this as a tamper error. """ expected_patterns = [ - # New classification: KAS request error - b"KAS request error", - b"rewrap request 400", + b"tamper", b"bad request", b"InvalidArgument", - # Legacy classification: tamper (older SDK versions) - b"tamper", + b"rewrap request 400", b"InvalidFileError", b"could not find policy in rewrap response", ] pattern = b"|".join(re.escape(p) for p in expected_patterns) assert re.search(pattern, exc.output, re.IGNORECASE), ( - f"Expected KAS request or tamper error, got: [{exc.output}]" + f"Expected policy tamper error, got: [{exc.output}]" ) @@ -597,7 +594,7 @@ def test_tdf_with_unbound_policy( decrypt_sdk.decrypt(b_file, rt_file, "ztdf", expect_error=True) assert False, "decrypt succeeded unexpectedly" except subprocess.CalledProcessError as exc: - assert_kas_request_error(exc, decrypt_sdk) + assert_policy_tamper_error(exc, decrypt_sdk) # Verify rewrap failure was logged (policy binding mismatch) # FIXME: Audit logs are not present on failed bindings @@ -631,7 +628,7 @@ def test_tdf_with_altered_policy_binding( decrypt_sdk.decrypt(b_file, rt_file, "ztdf", expect_error=True) assert False, "decrypt succeeded unexpectedly" except subprocess.CalledProcessError as exc: - assert_kas_request_error(exc, decrypt_sdk) + assert_policy_tamper_error(exc, decrypt_sdk) # Verify rewrap failure was logged (policy binding mismatch) # FIXME: Audit logs are not present on failed bindings From 8ace857d611329e62fcbd62149881305ac6935e3 Mon Sep 17 00:00:00 2001 From: Mary Dickson Date: Wed, 18 Mar 2026 10:56:09 -0700 Subject: [PATCH 2/3] docs(xtest): add instructions for running xtests against platform feature branches Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Mary Dickson --- xtest/README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/xtest/README.md b/xtest/README.md index 6bdfcc40..77dabb4a 100644 --- a/xtest/README.md +++ b/xtest/README.md @@ -116,6 +116,19 @@ uv sync --extra dev pytest ``` +### Running xtests against a platform feature branch (CI) + +To validate platform changes against the xtest suite without merging: + +1. Go to the **Actions** tab in [opentdf/tests](https://github.com/opentdf/tests/actions) +2. Find the **xtest** workflow +3. Click the **"Run workflow"** dropdown on the right +4. Set **"Use workflow from"** to the xtest branch you want to run (e.g. `main`, or a companion xtest branch) +5. Set **"platform ref branch"** to the HEAD commit SHA of your platform feature branch +6. Click **Run workflow** + +This builds the platform from your commit and runs the full xtest suite against it. Use this to validate SDK or KAS changes before merging. + #### Run TDF Tests ```shell From 92f942c01ad1fb8f93f4443e9eed19a38442b411 Mon Sep 17 00:00:00 2001 From: Mary Dickson Date: Wed, 18 Mar 2026 11:58:41 -0700 Subject: [PATCH 3/3] fix(xtest): tighten policy tamper assertion for SDKs with tamper-error-split For SDKs that support the tamper-error-split feature (Go SDK >= 0.14.0), assert that policy binding failures produce a tamper error specifically and do NOT produce a KAS request error. Older SDKs fall back to the permissive pattern match. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Mary Dickson --- xtest/sdk/go/cli.sh | 7 +++++++ xtest/tdfs.py | 3 +++ xtest/test_tdfs.py | 11 +++++++++++ 3 files changed, 21 insertions(+) diff --git a/xtest/sdk/go/cli.sh b/xtest/sdk/go/cli.sh index 172aa5b5..48c8ffea 100755 --- a/xtest/sdk/go/cli.sh +++ b/xtest/sdk/go/cli.sh @@ -94,6 +94,13 @@ if [ "$1" == "supports" ]; then "${cmd[@]}" --version --json | jq -re .sdk_version | awk -F. '{ if ($1 > 0 || ($1 == 0 && $2 > 3) || ($1 == 0 && $2 == 3 && $3 >= 18)) exit 0; else exit 1; }' exit $? ;; + tamper-error-split) + # KAS 400 split: generic "bad request" = tamper, descriptive = misconfiguration + # Introduced in go sdk 0.14.0 + set -o pipefail + "${cmd[@]}" --version --json | jq -re .sdk_version | awk -F. '{ if ($1 > 0 || ($1 == 0 && $2 >= 14)) exit 0; else exit 1; }' + exit $? + ;; *) echo "Unknown feature: $2" exit 2 diff --git a/xtest/tdfs.py b/xtest/tdfs.py index 0f2334bb..9df896a5 100644 --- a/xtest/tdfs.py +++ b/xtest/tdfs.py @@ -53,6 +53,9 @@ "mechanism-ec-curves-384-521", "ns_grants", "obligations", + # KAS 400 errors are split: generic "bad request" = tamper (ErrTampered), + # descriptive messages = misconfiguration (ErrKASRequestError). + "tamper-error-split", ] container_version = Literal["4.2.2", "4.3.0"] diff --git a/xtest/test_tdfs.py b/xtest/test_tdfs.py index 916698be..92245a70 100644 --- a/xtest/test_tdfs.py +++ b/xtest/test_tdfs.py @@ -546,6 +546,17 @@ def assert_policy_tamper_error( leaking information about secret key computations, and the SDK classifies this as a tamper error. """ + if decrypt_sdk.supports("tamper-error-split"): + # SDK distinguishes tamper from misconfiguration — assert tamper specifically + assert re.search(b"tamper", exc.output, re.IGNORECASE), ( + f"Expected tamper error, got: [{exc.output}]" + ) + assert not re.search(b"KAS request error", exc.output, re.IGNORECASE), ( + f"Policy binding failure must not be classified as KAS request error: [{exc.output}]" + ) + return + + # Older SDKs: accept any plausible error output expected_patterns = [ b"tamper", b"bad request",