Skip to content

chore(xtest): DPoP nonce-challenge test coverage and CLI/CI hardening#529

Open
dmihalcik-virtru wants to merge 23 commits into
mainfrom
fix-dpop-nonce-challenge
Open

chore(xtest): DPoP nonce-challenge test coverage and CLI/CI hardening#529
dmihalcik-virtru wants to merge 23 commits into
mainfrom
fix-dpop-nonce-challenge

Conversation

@dmihalcik-virtru

@dmihalcik-virtru dmihalcik-virtru commented Jun 18, 2026

Copy link
Copy Markdown
Member

What

Wires up end-to-end DPoP (Demonstrating Proof-of-Possession) coverage in the
cross-client test suite, including the dpop_nonce_challenge capability, and
hardens the surrounding CLI shims and CI diagnostics.

Why

DPoP-bound clients were not actually exercised end-to-end: the shims read
CLIENTID but never enabled DPoP proof generation, so the tests passed
without verifying the bound flow. This branch makes the DPoP client provision
and emit proofs, validates the nonce-challenge path, and fixes the platform
action pins that carry the DPoP htu/htm validation fixes.

Changes

Tests

  • test_dpop.py: the autouse _dpop_client_env fixture now also sets
    XT_WITH_DPOP=ES256, so the DPoP-bound opentdf-dpop client actually
    generates proofs during encrypt/decrypt.

SDK CLI shims

  • sdk/js/cli.sh: wire CLIENTID, CLIENTSECRET, and DPoP into the shim.
  • sdk/java/cli.sh: delegate dpop_nonce_challenge detection to the binary;
    enable --verbose when available (checked on the root help, where it is
    ScopeType.INHERIT); cache the help capability probes by jar mtime to cut
    per-operation JVM startup overhead and keep JVM warnings out of logs.
  • tdfs.py: surface the dpop_nonce_challenge capability and log subprocess
    stdout/stderr on encrypt/decrypt failure for xdist visibility.

CI

  • xtest.yml: bump platform action SHA pins to pick up the DPoP htu/htm
    strict-validation fixes; add dpop-challenge boolean input (default false)
    to workflow_dispatch and workflow_call so callers opt in to the nonce
    challenge rather than having it hardcoded; pass OTDFCTL_HEADS to all pytest
    steps; upload platform and KAS server logs as artifacts on failure.

Test plan

  • Locally: uv run pytest test_dpop.py --sdks "go java js" -v against a platform built
    from the pinned action SHA (DPoP enabled).
  • CI:

Summary by CodeRabbit

  • New Features
    • Added optional DPoP challenge support to the test workflow and CLI tooling.
    • Improved SDK wrappers to pass DPoP-related settings when enabled.
  • Bug Fixes
    • Made nonce-challenge handling more reliable by retrying requests when a nonce is required.
    • Updated CLI output to better hide sensitive auth details.
  • Chores
    • Updated pinned workflow actions and refreshed test fixtures to allow the new DPoP-bound client.

@dmihalcik-virtru dmihalcik-virtru requested review from a team as code owners June 18, 2026 15:58
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds DPoP nonce-challenge support across the xtest suite: a new dpop-challenge CI workflow input wired into platform and KAS startup, cached help-based capability probing and DPoP CLI arguments in the JS/Java SDK wrappers, an expanded fixture subject condition set, and a nonce-retry helper for rewrap requests in the DPoP test.

Changes

DPoP Nonce-Challenge Test Infrastructure

Layer / File(s) Summary
CI: dpop-challenge input and wiring
.github/workflows/xtest.yml
Adds a dpop-challenge boolean input to workflow_dispatch/workflow_call and threads dpop-challenge-enabled into the platform startup action and each additional KAS start step, with pinned action revision bumps.
DPoP test fixture and subject condition set
xtest/test_dpop.py, xtest/fixtures/obligations.py
Sets XT_WITH_DPOP=ES256 alongside CLIENTID=opentdf-dpop in the autouse fixture, and adds "opentdf-dpop" to the otdf_client_scs subject condition set.
JS SDK CLI: DPoP and credential override support
xtest/sdk/js/cli.sh
Snapshots/restores caller-provided CLIENTID/CLIENTSECRET around test.env sourcing, derives --auth from those variables, conditionally appends --dpop/--dpop-key, adds an echo_redacted helper to mask secrets in logs, and updates assertion argument conditionals.
Java SDK CLI: jar_help caching and capability probes
xtest/sdk/java/cli.sh
Adds a cached jar_help() helper, switches the dpop_nonce_challenge and KAS-allowlist probes to use it, and conditionally appends --verbose when supported.
test_dpop.py: nonce-retry helper for rewrap requests
xtest/test_dpop.py
Adds _post_rewrap_with_nonce_retry to re-sign and retry a DPoP proof after a 401/DPoP-Nonce challenge, and updates Bearer/DPoP scheme assertions to use it.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Test
  participant _post_rewrap_with_nonce_retry
  participant KAS

  Test->>_post_rewrap_with_nonce_retry: request rewrap (auth_scheme)
  _post_rewrap_with_nonce_retry->>KAS: POST rewrap with nonce-free DPoP proof
  KAS-->>_post_rewrap_with_nonce_retry: 401 + DPoP-Nonce header
  _post_rewrap_with_nonce_retry->>_post_rewrap_with_nonce_retry: re-sign DPoP proof with nonce
  _post_rewrap_with_nonce_retry->>KAS: retry POST rewrap with new proof
  KAS-->>Test: final response
Loading

Possibly related PRs

  • opentdf/tests#505: Both PRs center on DPoP CI/testing — the retrieved PR adds xtest/test_dpop.py to the workflow, while this PR further updates that file with nonce-retry logic and DPoP env/fixtures plus the related xtest.yml wiring.

Suggested reviewers: pflynn-virtru, sujankota

Poem

A rabbit hops with nonce in paw,
DPoP proofs signed by CI's law,
jar_help caches, cli.sh hides secrets tight,
KAS challenges met, retried just right,
Through JS and Java, the burrow grows deep —
nonce-free no more, our tests now leap! 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: DPoP nonce-challenge coverage plus CLI and CI hardening.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-dpop-nonce-challenge

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces caching for the Java CLI help output to avoid JVM startup overhead, adds support for DPoP-related environment variables and client overrides in the JS CLI shim, improves subprocess error logging in Python tests, and updates DPoP test fixtures. Feedback highlights a potential race condition in the Java CLI caching mechanism under parallel test execution, where concurrent processes could read an incomplete cache file, and suggests using an atomic write-and-rename pattern to resolve it.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread xtest/sdk/java/cli.sh Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@xtest/sdk/java/cli.sh`:
- Around line 58-61: The cache file is being written to directly after checking
existence, creating a race condition where concurrent invocations can read a
partially written cache file. Instead of writing directly to the final cache
file location in the line that runs the java command, write to a temporary file
first (using a temp filename derived from the cache variable), then atomically
move that temporary file to the final cache location using the mv command. This
ensures that other processes reading the cache file will either see the old
complete version or the new complete version, never a partial write. Reference
the cache variable and the java jar invocation in your fix.

In `@xtest/sdk/js/cli.sh`:
- Around line 149-150: The --auth argument containing CLIENTSECRET is being
logged/echoed in subsequent command output, which exposes sensitive credentials
in CI logs. Locate where the command arguments are being printed or logged (the
echo statement that prints full args), and add logic to redact or mask the
--auth argument value before logging while keeping the actual command execution
intact with the real credentials. Ensure the secret portion of the auth
credentials is not visible in any log output or CI artifacts.
- Around line 139-140: Replace the single bracket conditionals `[ -n ... ]` with
double bracket syntax `[[ -n ... ]]` for the two conditional checks involving
$_pre_clientid and $_pre_clientsecret variables. Update both lines that check
$_pre_clientid and $_pre_clientsecret to use `[[ ... ]]` instead of `[ ... ]` to
comply with ShellCheck best practices and improve code robustness.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4855622a-9727-479f-a484-91316676a003

📥 Commits

Reviewing files that changed from the base of the PR and between 55c03e0 and a2f609c.

📒 Files selected for processing (5)
  • .github/workflows/xtest.yml
  • xtest/sdk/java/cli.sh
  • xtest/sdk/js/cli.sh
  • xtest/tdfs.py
  • xtest/test_dpop.py

Comment thread xtest/sdk/java/cli.sh Outdated
Comment thread xtest/sdk/js/cli.sh Outdated
Comment thread xtest/sdk/js/cli.sh
@dmihalcik-virtru dmihalcik-virtru changed the title DPoP nonce-challenge test coverage and CLI/CI hardening chore(xtest): DPoP nonce-challenge test coverage and CLI/CI hardening Jun 18, 2026
@dmihalcik-virtru dmihalcik-virtru marked this pull request as draft June 18, 2026 17:05
@dmihalcik-virtru dmihalcik-virtru force-pushed the fix-dpop-nonce-challenge branch from e61eb55 to 7746ba2 Compare June 24, 2026 13:42
dmihalcik-virtru and others added 13 commits June 26, 2026 13:35
Update start-up-with-containers and start-additional-kas action SHAs
to DSPX-3397-platform-service tip, and pass dpop-challenge-enabled: true
so the DPoP nonce challenge tests are not skipped.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
- Capture caller env vars before sourcing test.env (which unconditionally
  resets them), then restore them — allows pytest monkeypatch to override
  CLIENTID=opentdf-dpop for DPoP-specific tests.
- Replace hardcoded --auth opentdf:secret with ${CLIENTID:-opentdf}:${CLIENTSECRET:-secret}.
- Add XT_WITH_DPOP (algorithm, e.g. ES256) and XT_WITH_DPOP_KEY (PEM path)
  support, wired to --dpop / --dpop-key CLI flags.
- Update _dpop_client_env fixture to also export XT_WITH_DPOP=ES256 so
  DPoP proof generation is actually exercised in test_dpop.py.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
…st binary

load_otdfctl() in conftest.py reads OTDFCTL_HEADS to resolve sdk/go/dist/{tag}/otdfctl.sh.
Without it, every test step fell through to sdk/go/dist/main/otdfctl.sh or the non-dist
sdk/go/otdfctl.sh, which falls back to go run github.com/opentdf/otdfctl@latest instead
of the built branch binary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
…gnosis

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
… on the parent command)

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
…lidation)

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Running 'java -jar cmdline.jar help' on every encrypt/decrypt paid
150-500ms of JVM startup per probe (kas-allowlist and --verbose checks).
Add a jar_help() helper that caches help output to a tmpfile keyed by the
jar's mtime, and discards stderr to keep JVM warnings out of test logs.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
- java cli.sh: write jar_help cache to a temp file then mv, so concurrent
  xdist workers never read a partially written cache (gemini/coderabbit).
- java/js cli.sh: use [[ ]] for the new conditionals (SonarCloud SC2292).
- js cli.sh: mask the --auth secret in echoed commands so CI logs don't
  capture client credentials (coderabbit).

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
dmihalcik-virtru and others added 4 commits June 26, 2026 13:35
Pass dpop-challenge-enabled:true to the alpha/beta/gamma/delta/km1/km2
start-additional-kas steps and re-pin that action to the go-branch commit
(2305c4ab) that adds the input. Fixes test_dpop_rejects_tampered_nonce,
which targets the alpha KAS that previously never set require_nonce.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
…once-agnostic

- Repin start-up-with-containers and all start-additional-kas steps to
  opentdf/platform DSPX-3397-platform-go-sdk @4a19b297 (adds dpop enforce
  alongside require_nonce on additional KAS).
- test_dpop_bearer_scheme: route both rewrap calls through a new
  _post_rewrap_with_nonce_retry helper so the test passes whether or not the
  target KAS enforces require_nonce (satisfies the use_dpop_nonce challenge
  before asserting lenient 200 + WARN).

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
Platform action split DPoP enforcement into a separate dpop-enforce-required
input (default false); xtest only passes dpop-challenge-enabled, so require_nonce
stays on and global enforce stays off, restoring the non-DPoP suite.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
…et (DSPX-3397)

The DPoP tests authenticate as the opentdf-dpop client, but otdf_client_scs only
entitled opentdf/opentdf-sdk, so the DPoP rewrap reached authorization and was
denied (entitled:false -> 403). Add opentdf-dpop to the clientId allowlist so the
DPoP-bound client is entitled like the other test clients.

Signed-off-by: Dave Mihalcik <dmihalcik@virtru.com>
@dmihalcik-virtru dmihalcik-virtru force-pushed the fix-dpop-nonce-challenge branch from 6c8953e to 7bd3ad1 Compare June 26, 2026 17:36
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

X-Test Failure Report

Convert remaining single-bracket [ ] test conditionals to [[ ]] throughout
xtest/sdk/js/cli.sh to clear SonarCloud SC2292 findings and match the style
already applied to the DPoP credential-restore checks. shellcheck and shfmt
pass clean.
- js/cli.sh: add explicit `return 0` to echo_redacted()
- js/cli.sh: merge redundant nested ECDSA-binding if (== "true" implies -n)
- java/cli.sh: add explicit `return 0` to jar_help()

All behavior-preserving. shellcheck and shfmt pass clean.
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

X-Test Failure Report

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

X-Test Failure Report

@sonarqubecloud

sonarqubecloud Bot commented Jul 1, 2026

Copy link
Copy Markdown

@dmihalcik-virtru dmihalcik-virtru marked this pull request as ready for review July 1, 2026 15:28

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
xtest/sdk/java/cli.sh (1)

49-67: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Consider mktemp for the intermediate cache file.

tmp="${cache}.$$" is a predictable path in a shared /tmp; a co-resident process could pre-create it (symlink or file) before the java ... >"$tmp" write. Low practical risk in ephemeral CI runners, but mktemp removes the guesswork entirely.

♻️ Optional hardening
-    local tmp="${cache}.$$"
-    java -jar "$jar" help "$@" >"$tmp" 2>/dev/null
+    local tmp
+    tmp=$(mktemp "${cache}.XXXXXX")
+    java -jar "$jar" help "$@" >"$tmp" 2>/dev/null
     mv -f "$tmp" "$cache"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@xtest/sdk/java/cli.sh` around lines 49 - 67, The intermediate temp file in
jar_help is predictable because it is built from the cache name plus the PID,
which leaves a race window in shared /tmp. Update the jar_help flow to use
mktemp for the temporary help output file before the java -jar invocation, then
rename that secure temp file into the cache path after generation; keep the rest
of the caching logic in jar_help unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@xtest/sdk/java/cli.sh`:
- Around line 49-67: The intermediate temp file in jar_help is predictable
because it is built from the cache name plus the PID, which leaves a race window
in shared /tmp. Update the jar_help flow to use mktemp for the temporary help
output file before the java -jar invocation, then rename that secure temp file
into the cache path after generation; keep the rest of the caching logic in
jar_help unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cee8895d-8c59-4d24-b31e-4b2387690c29

📥 Commits

Reviewing files that changed from the base of the PR and between a2f609c and 92b902f.

📒 Files selected for processing (5)
  • .github/workflows/xtest.yml
  • xtest/fixtures/obligations.py
  • xtest/sdk/java/cli.sh
  • xtest/sdk/js/cli.sh
  • xtest/test_dpop.py

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants