Skip to content

fix: WWW-Authenticate hardening, DPoP htu port, docs and conformance#7

Merged
muralx merged 1 commit into
mainfrom
fix/post-0.1.0-batch-2
May 19, 2026
Merged

fix: WWW-Authenticate hardening, DPoP htu port, docs and conformance#7
muralx merged 1 commit into
mainfrom
fix/post-0.1.0-batch-2

Conversation

@muralx

@muralx muralx commented May 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

Bundle of fixes and improvements on top of the post-0.1.0 cleanup,
covering WWW-Authenticate / http_status helpers, a DPoP htu host-header
bug, doc snippet corrections, and conformance-suite hygiene.

Linked Issue

Changes

WWW-Authenticate / http_status

  • Security: www_authenticate() now sanitizes CR, LF, ", and \
    from every interpolated value (realm, error_description, scope,
    resource_metadata), closing a header-injection path through
    attacker-influenced error messages.
  • DPoPNotSupportedError now emits WWW-Authenticate: Bearer (was
    DPoP). The resource is bearer-only by configuration; advertising
    the DPoP scheme misled clients into retries that fail the same way.
  • http_status(CircuitOpenError) returns 503 (was 500). The
    breaker is structurally equivalent to other temporary-AS-unavailability
    errors and should be retryable, not surfaced as an internal error.
  • www_authenticate() gains keyword-only resource_metadata_url=
    (RFC 9728 §5.1) and scope= (RFC 6750 §3). When scope= is omitted
    the helper auto-populates it from
    InsufficientScopeError.required_scopes.
  • New response_headers_for(error, *, realm, resource_metadata_url, scope)
    bundles status + WWW-Authenticate into one call.
  • New AuthplaneResource.prm_url() — symmetric with prm_response(),
    returns the RFC 9728 well-known URL.
  • Both adapter verifiers now emit a logging.DEBUG event
    authplane.token_verification_failed with structured error_class
    and error fields before returning None. Wire behaviour is
    unchanged.

DPoP htu host header

  • Outbound HTTP layer now preserves non-default ports in the Host
    header and brackets IPv6 hostnames per RFC 3986 §3.2.2, so
    DPoP-protected requests to authservers on non-standard ports
    (e.g. localhost:9000) verify under RFC 9449. Previously the port
    was stripped and the AS reconstructed http://host/<path>, returning
    invalid_dpop_proof.

Docs

  • Replace undefined run_query() with runnable stubs in 6 snippets
    (README, llm-full.txt, both adapter user guides).
  • Switch URL elicitation example to UrlElicitationRequiredError
    (with a fallback for the no-consent_url path).
  • Rewrite MCP adapter Quick Starts to a single asyncio.run(main())
    loop so JWKS / metadata refresh tasks share the loop that handles
    requests. The previous asyncio.run(authplane_mcp_auth(...)) +
    sync mcp.run() pattern bound the refresh tasks to a loop that
    closed before mcp.run() started a new one.
  • Build DPoPProvider with the required DPoPKeyMaterial in the demo
    client.
  • Document the MCP-vs-FastMCP PRM scopes_supported asymmetry.
  • Drop the bogus resource_signing_alg_values_supported line from the
    PRM example.

Conformance tests

  • Align the RFC 8693 issued_token_type test with the catalog
    (accept and surface access_token, not just reject jwt).
  • Enforce one-test-per-case_id at collection time and collapse three
    sibling pairs that previously violated this; prevents silent overwrites
    in the rolled-up report.
  • Unify env var to AUTHPLANE_CONFORMANCE_CATALOG across conftest.py,
    test_catalog_alignment.py, CONTRIBUTING.md, and the CI / release
    workflows.
  • Raise a clear, actionable RuntimeError when the catalog file is
    missing (was: silent fallback to a non-existent default path).
  • Cleanup: extract the duplicated 9-parameter SSRF stub into a single
    _stub_ssrf_post(...) helper (~120 lines of duplication removed);
    hoist test-local imports to module top.

Affected Packages

  • authplane-sdk (root)
  • authplane-mcp
  • authplane-fastmcp
  • None (infra / docs / CI only)

Test Plan

Verified locally before opening this PR:

  • ruff check . — clean
  • ruff format --check . — 85 files formatted
  • pyright — 0 errors
  • Root SDK pytest tests — 487 passed
  • authplane-mcp pytest tests — 29 passed
  • authplane-fastmcp pytest tests — 35 passed
  • pytest conformance-tests — 104 passed, 1 xfailed (xfail unchanged)

CI will re-run all of these plus coverage.

Checklist

  • ruff check . passes
  • ruff format --check . passes
  • pyright passes (SDK root)
  • pytest passes for affected packages
  • Coverage unchanged or improved (≥ 80%) — not run locally; CI gates this
  • Tests added for new functionality
  • Documentation updated (if applicable)
  • CHANGELOG.md entry added under [Unreleased]
  • No token values, secrets, or key material in logs or test fixtures

WWW-Authenticate / http_status
- Security: www_authenticate() sanitizes CR, LF, ", and \ from every
  interpolated value, closing a header-injection path through
  attacker-influenced error messages.
- DPoPNotSupportedError now emits WWW-Authenticate: Bearer (was DPoP).
- http_status(CircuitOpenError) returns 503 (was 500).
- www_authenticate() gains keyword-only resource_metadata_url= (RFC 9728)
  and scope= (RFC 6750), with scope= auto-populated from
  InsufficientScopeError.required_scopes.
- New helpers: response_headers_for() bundles status + WWW-Authenticate
  into one call; AuthplaneResource.prm_url() returns the RFC 9728
  well-known URL.
- Both adapter verifiers emit a logging.DEBUG event
  "authplane.token_verification_failed" with structured error_class /
  error fields before returning None.

DPoP htu host header
- Outbound HTTP layer preserves non-default ports in the Host header
  and brackets IPv6 hostnames, so DPoP-protected requests to authservers
  on non-standard ports verify under RFC 9449.

Docs
- Fix 6 snippets that referenced an undefined run_query(); switch URL
  elicitation example to UrlElicitationRequiredError; rewrite MCP
  adapter Quick Starts to a single asyncio.run(main()) loop so refresh
  tasks share the request loop; small inaccuracies removed.

Conformance tests
- Align RFC 8693 issued_token_type test with the catalog; enforce
  one-test-per-case_id at collection time (collapses 3 sibling pairs);
  unify env var to AUTHPLANE_CONFORMANCE_CATALOG; raise a clear error
  when the catalog file is missing; cleanup (extract repeated SSRF
  stub, hoist imports).

Tests: new coverage in tests/test_errors.py and tests/net/test_ssrf.py;
adapter tests cover the new debug log event.
@muralx muralx requested a review from a team as a code owner May 19, 2026 16:48
@muralx muralx requested a review from RobertoIskandarani May 19, 2026 16:49
@muralx muralx self-assigned this May 19, 2026
@muralx muralx merged commit 3f3ab40 into main May 19, 2026
10 checks passed
@muralx muralx deleted the fix/post-0.1.0-batch-2 branch May 19, 2026 16:53
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