You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Complete mTLS E2E parity and combined transport integration - #976
Combines #938, #939, #947, and #974, retaining their cross-feature transport integration and E2E coverage. Do not merge before those feature dependencies land. The diff against dev includes their inherited production changes.
Combined integration
Bearer-over-mTLS uses the certificate-scoped custom HTTP factory and endpoint protections while preserving mTLS PoP and FIC jwt-pop behavior. This introduces no additional public API or persisted-format redesign.
binding_certificate is returned only when the final access token is mtls_pop; a certificate-bound FIC assertion does not make a final Bearer token certificate-bound.
Graph PoP success and same-token rejection controls, regional Key Vault PoP, both cache orders, default/custom Bearer transport, and both FIC output modes.
Malformed user-grant requests through real default/custom transports, with strict endpoint, assertion, grant, and rejection checks.
Cross-feature factory, cache-isolation, lifecycle, and privacy regressions.
Single-feature coverage lives in its owning PRs. This PR retains the sibling-dependent scenarios and complete mandatory live inventory. Test setup and the reference-to-Python assertion mapping are documented in tests/README.md.
Exclusions
IMDSv2; non-exportable/VBS/provider-backed keys; managed identity; new SDK public APIs/features or lab provisioning; intentionally disabled reference successful user acquisitions; .NET-only callback options without a Python equivalent; and unrelated legacy E2E skips. This does not claim repository-wide skip elimination.
Allow a confidential-client app configured with a Subject Name + Issuer
(SN/I) certificate to obtain an mTLS-bound PoP access token from Entra ID,
using the same certificate as the client TLS certificate in the mutual-TLS
handshake to the token endpoint (token_type=mtls_pop, cnf/x5t#S256 binding).
- Add mtls_proof_of_possession kwarg to acquire_token_for_client, returning
a binding_certificate (public x5c + sha256 thumbprint) on success
- Add mTLS client-cert transport (msal/mtls.py) with endpoint transform and
sovereign-cloud / tenanted-authority / custom-http-client guardrails
- Isolate mtls_pop tokens in cache via key_id (Bearer unchanged)
- Add token-type telemetry, docs, and a confidential-client mTLS PoP sample
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The two SN/I-over-mTLS-PoP e2e tests used the generic lab app
(LAB_APP_CLIENT_ID) with the microsoft.onmicrosoft.com authority. That
combination works for Bearer SN/I but is not ESTS allow-listed for mTLS
PoP, so the token request fails with AADSTS700025. Point both tests at
the SN/I-allow-listed app 163ffef9-a313-45b4-ab2f-c7e2f5e0e23e in the
MSI-team tenant, mirroring the MSAL .NET/Java e2e config. The lab SN/I
cert and the MS Graph resource are unchanged.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Gate the ext_cache_key and key_id cache-isolation filters in
TokenCache.search() on a non-empty target. Scoped token retrieval keeps
the mtls_pop / FMI isolation intact, but broad target-less searches (used
by _sign_out and remove_account) now enumerate key-bound access tokens so
they are removed from the cache instead of being left behind.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ion build
Harden the mTLS Proof-of-Possession path with three fixes surfaced by review.
Fail closed on a token_type downgrade. A cert-bound request is driven by our
request flag, not the response, so if ESTS answers an mtls_pop request with a
non-cert-bound token we previously still attached binding_certificate and cached
it as bound. Now acquire_token_for_client returns token_type_mismatch when the
response token_type is not "mtls_pop", and _MtlsClient only re-injects key_id
(the cache binding) when the response is actually mtls_pop. A downgraded token
therefore caches as an ordinary token that the cert-bound silent query can never
match, so the flow re-fetches and fails closed every time. Mirrors MSAL .NET/Go.
Honor verify in the mTLS transport. A custom ssl_context bypasses requests' own
verify handling, so verify was silently ignored: verify=False raised a cryptic
ValueError and the default used the system store instead of certifi. The context
builder now matches requests' semantics (certifi default, CA file/dir path, or
disabled) so verify behaves as documented.
Serialize first session build. Wrap the lazy session construction in a
double-checked lock so a cold-start burst on a multi-threaded confidential
client no longer builds N transports (each writing the private key to a temp
file) and orphans all but one.
Also drop stray blank lines that had crept into the acquire_token_for_client
mTLS setup block.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Two review fixes for the mTLS PoP work.
Cache isolation (empty-scopes leak): the key_id and ext_cache_key
isolation filters in TokenCache.search() were gated on `target`, so an
empty-scopes for-use lookup (e.g. acquire_token_for_client([])) was
target-less and bypassed isolation -- returning a key-bound (mtls_pop)
or FMI-bound AT the caller never asked for. Decouple "removal intent"
from "target-less" via a keyword-only for_removal flag: isolation is now
unconditional for for-use lookups and only the two removal callers
(_sign_out, remove_tokens_for_client) opt out with for_removal=True to
enumerate and delete those ATs. Mirrors MSAL .NET, where token-type
isolation is never gated on scope state.
mTLS transport verify=None: _MtlsHttpClient forced CERT_NONE onto a
verifying ssl_context when verify was None, raising ValueError. Normalize
None to True (the safe default for a security library, verifying via
certifi) so session.verify and the ssl_context agree.
Adds regression tests for both empty-scope leak paths (key-bound and
ext-bound) and updates the removal-enumeration test to opt in via
for_removal=True.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Four small, low-risk fixes from the code review:
- application.py `_private_key_to_unencrypted_pem`: pass an explicit
`backend=default_backend()` to `load_pem_private_key` (required on the
supported `cryptography` floor, 2.5) and wrap the call so a bad or
encrypted-without-passphrase key surfaces a clear, actionable ValueError
instead of a cryptic low-level error. Mirrors the sibling
`_load_private_key_from_pem_str`.
- token_cache.py: coerce `key_id` to a str via a new `_key_id_to_str`
helper before building the AccessToken cache key, so a non-str key_id
never raises TypeError at the `"-" + key_id` concatenation. The normal
ASCII-str path is byte-identical, so existing mtls_pop cache entries are
unaffected. Defensive only: current callers already pass an ASCII str.
- test_application.py: hoist `import os` to the top of the module and drop
the mid-file `import os as _os` alias.
Adds regression tests: bytes/other key_id coercion (test_token_cache.py)
and the clearer private-key load errors (test_mtls_transport.py).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Stacked on top of the SN/I mTLS PoP base support. Enables a Federated
Identity Credential (FIC) two-leg exchange over mutual-TLS: a leg-1
cert-bound mtls_pop token is presented as a jwt-pop client assertion over
an mTLS connection bound by the same certificate to obtain the final token.
- Detect the client_credential mtls_binding_certificate sub-key (FIC leg 2)
and carry the leg-1 assertion as a jwt-pop client assertion over mTLS
- Route every FIC leg-2 request over the mTLS endpoint, so even a Bearer
token travels the cert-bound connection (implicit Bearer-over-mTLS)
- Add unit + E2E coverage, docs, and a two-leg sample
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
A confidential client configured for a Federated Identity Credential (FIC)
leg-2 exchange (a client_assertion plus an mtls_binding_certificate) is only
meaningful through acquire_token_for_client, which presents the binding
certificate over mutual-TLS and carries the leg-1 assertion as jwt-pop. If such
an app is instead used with any other flow (authorization code, on-behalf-of,
refresh token, username/password, or user-FIC), MSAL would send that cert-bound
leg-1 assertion as an ordinary jwt-bearer over a non-mTLS connection. ESTS
rejects it, but with a confusing server error.
Add a small _reject_if_mtls_binding_cert guard and call it at the top of those
flows so the misuse fails fast with a clear ValueError that points the caller to
acquire_token_for_client. The guard is a no-op for every non-FIC-leg-2 app, so
public clients and ordinary confidential clients are unaffected.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The _reject_if_mtls_binding_cert guard already covered the six
non-client-credential acquisition flows, but not acquire_token_silent /
acquire_token_silent_with_error. With a shared token cache holding user
refresh tokens, a FIC leg-2 app (client_assertion + mtls_binding_certificate)
could silently redeem one of those RTs, putting the cert-bound leg-1
assertion on a non-mTLS jwt-bearer request -- which ESTS rejects with a
confusing invalid_client rather than a clear message.
Add the guard to both public silent entry points. Both call the private
_acquire_token_silent_with_error directly (not each other), and
acquire_token_for_client also uses that private helper, so the guard lives
on the two public wrappers -- never on the private method -- to keep the
legitimate mTLS client-credential flow working. No-op for normal apps.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Port the msal-dotnet PR #6100 rigor to the SN/I-over-mTLS-PoP e2e tests:
- Add a reusable _call_graph_with_mtls_pop_token(token, cert) helper that
presents the acquired token to the mTLS Graph host
(mtlstb.graph.microsoft.com) over a TLS channel bound by the SAME
certificate and asserts HTTP 200. Acquiring a token is not proof it works;
a 401/403 (binding lost / wrong scheme) is a regression. The helper reuses
msal.mtls._create_ssl_context/_make_mtls_adapter and is shared with the FIC
e2e (follow-up PR).
- Rename the two PoP tests to the cross-SDK Credential_<X509|Fic>_Output_<Pop|
Bearer> matrix (snake_case): test_credential_x509_output_pop (+_regional),
both now call the resource-call helper.
- Add test_credential_x509_output_bearer: the same SN/I cert WITHOUT the flag
must still authenticate via a signed client_assertion and yield an ordinary
token (no binding_certificate) - the client_assertion is dropped only on the
mTLS path.
- Regional test: skip only on temporarily_unavailable. invalid_request is a
400 config error (a real regression), so it now fails loudly instead of
being masked as "endpoint not reachable".
- Docs/sample/code comments: azure_region is optional (global mtlsauth
fallback); clarify the classic cert path still signs a private_key_jwt
client_assertion while only mTLS PoP omits it; tighten the sample's
log-safety note (binding_certificate x5c is public, not a secret).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Per cross-SDK review feedback: make the Bearer contrast explicit. On the
non-mTLS path MSAL Python omits binding_certificate entirely, so assert both
that the key is absent and that a lookup yields None - proving the cert
binding is genuinely gone, not merely that the mtls_proof_of_possession flag
was skipped.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Per cross-SDK review: document that the FIC two-leg e2e (PR #939) reuses this
helper as-is, so it is not inlined later. Explains why it takes an explicit
cert_credential instead of reading self.app's cert.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Align with the region-split rule shared across the sibling SDKs (merged in
MSAL .NET, matched by Java/JS/Go): the regional slice intermittently
downgrades an mtls_pop request to Bearer by returning a Bearer *token* (not a
temporarily_unavailable error), so a regional PoP cell either fails its
token_type assert or forces us to mask a real downgrade - both wrong.
- DELETE test_credential_x509_output_pop_regional. The {region}.mtlsauth host
rewrite stays covered by the mtls transport/endpoint unit tests, so no unit
coverage is lost.
- Make test_credential_x509_output_bearer REGIONAL (azure_region="westus3").
A Bearer token_type is deterministic, so this safely RETAINS live
regional-endpoint E2E coverage. Comment explains region is correct here but
forbidden on the PoP cell.
- test_credential_x509_output_pop stays GLOBAL (Graph scope, resource-call
200, cache-hit) - unchanged.
Result mirrors MSAL .NET: pop (global) / bearer (regional westus3).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Bring the Task-1 SNI hardening from rginsburg/sni-mtls-pop (tip b9233c6)
into the FIC follow-up branch: the reusable _call_graph_with_mtls_pop_token
resource-call e2e helper, the cross-SDK naming matrix, the loud-fail on
invalid_request, and the region split (drop the regional mtls_pop cell that
intermittently downgrades to Bearer; make the Bearer cell regional westus3;
keep the pop cell global).
Conflicts resolved by keeping both sides:
- docs/index.rst: retain the SNI note that only the mTLS-PoP path drops the
signed client_assertion AND the FIC leg-2 configuration note.
- msal/application.py: retain the FIC leg-2 jwt-pop assertion branch AND fold
the SNI clarifying comment into the vanilla SN/I else path.
The FIC e2e test and its leg-1 skip-hatch are left untouched; porting the
resource-call helper into the FIC e2e is Task 2.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Bring the SNI mTLS PoP branch current with base dev (which had advanced with
PRs #911, #937, #940, #942), resolving the acquire_token_for_client overlap
between this branch's mtls_proof_of_possession feature and dev's new
forwarded_client_claims parameter (#937).
Conflicts resolved by integrating BOTH sides:
- msal/application.py acquire_token_for_client(): keep both the
mtls_proof_of_possession and forwarded_client_claims parameters, both request
setup blocks, and the fail-closed mtls_pop downgrade/binding_certificate
post-processing (result = _clean_up(...) form).
- msal/application.py _acquire_token_for_client(): keep the is_mtls_pop client
selection AND the client_claims merge into the OAuth claims parameter; the
single popped request_data carries token_type/key_id and client_claims on the
wire.
- tests/test_token_cache.py: union the imports (_key_id_to_str from this branch
plus _parse_claims_or_raise/_merge_claims from dev).
No Task-1 SNI hardening dropped: the mTLS PoP feature, the
test_credential_x509_output_{pop,bearer} matrix, the region split (pop GLOBAL /
bearer REGIONAL westus3, no regional pop cell), the _call_graph helper, and the
downgrade fail-closed all remain. No escape hatch reintroduced.
Verified: py_compile clean; pytest collects 196 tests (e2e matrix + upstream
suites); 17 mtls_pop + forwarded_client_claims unit tests pass; 49 token_cache
tests pass.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Bring the FIC branch current with base dev by merging the resync'd SNI branch
(rginsburg/sni-mtls-pop @ 4ec592f, which merged origin/dev), so #939 also
carries PRs #911, #937, #940, #942.
Single conflict in msal/application.py acquire_token_for_client(), resolved by
integrating BOTH sides: keep the FIC-aware mTLS condition
(mtls_proof_of_possession or self._mtls_is_fic_leg2) and its comment from this
branch, AND add dev's forwarded_client_claims request-setup block above it. The
private _acquire_token_for_client() auto-merged correctly, combining the
FIC/mtls client selection (is_mtls_pop or self._mtls_is_fic_leg2) with dev's
client_claims -> OAuth claims merge.
The FIC e2e test test_fic_two_leg_over_mtls_pop and its leg-1 skip-hatch are
left UNTOUCHED (de-hatch + resource-call port + Credential_Fic_Output_* naming
remain Task 2). Region split, x509 pop/bearer matrix, and downgrade fail-closed
are all preserved.
Verified: py_compile clean; e2e matrix (pop/bearer) + FIC test collect; 22
mtls_pop + forwarded_client_claims unit tests pass.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Introduce an app-level opt-in `send_certificate_over_mtls` on the
confidential-client `client_credential` dict. When set, MSAL presents the
SN/I certificate on the TLS handshake to the [region.]mtlsauth.* endpoint and
signs a private_key_jwt client_assertion (x5c forced), but requests a plain
Bearer token (no token_type=mtls_pop / no key_id / no req_cnf). The result is
an ordinary, non-cert-bound Bearer access token.
- Validate the flag at construction: it requires a certificate credential,
else ValueError naming the flag.
- Add _get_mtls_bearer_client(): reuses the Task-1 mTLS transport
(msal/mtls.py) + the login.*->[region.]mtlsauth.* endpoint transform, adds a
signed x5c-forced assertion, and caches a plain (non-key_id-fenced) token.
- Route acquire_token_for_client() to it when the flag is set and the request
is not mtls_pop. A per-request mtls_proof_of_possession=True always wins.
- Tests (RED-first) in TestSendCertificateOverMtls: config/builder (default
false, stored true, flag + non-cert credential raises) and CC behavior
(global/regional mtlsauth endpoint, client_assertion + jwt-bearer, x5c
forced, no PoP wire markers, plain-Bearer result, mtls_pop precedence).
Ports MSAL.NET CertificateOptions.SendCertificateOverMtls (client-credentials
leg). OBO/refresh/auth-code coverage follows.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Extend the app-level send_certificate_over_mtls opt-in beyond
client-credentials to every confidential-client user flow: on-behalf-of,
refresh-token, and authorization-code.
Add _client_for_confidential_request(): when the flag is set it returns the
Bearer-over-mTLS client (SN/I cert on the TLS handshake, routed to the
[region.]mtlsauth.microsoft.com endpoint, private_key_jwt client_assertion
with the x5c chain forced, plain Bearer token back), otherwise the regular
client. Swap it in at the three user-flow call sites. Client-credentials
routing stays in _acquire_token_for_client, where a per-request
mtls_proof_of_possession still wins over the app-level flag.
Tests (tests/test_application.py, TestSendCertificateOverMtls): OBO / refresh
/ auth-code with the flag hit the global mtlsauth endpoint, carry
client_assertion + client_assertion_type=jwt-bearer with no key_id/req_cnf,
and use the correct grant (on_behalf_of / refresh_token / authorization_code);
OBO with the flag + a configured region hits the regional mtlsauth host; OBO
without the flag stays on the regular login endpoint (negative control).
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Guard the .NET MtlsBearerUserFlowTests second-call regression, adapted to
msal-python's cache seam: a 2nd Bearer-over-mTLS client-credentials call is
served from cache with no 2nd network round-trip and no crash on
region/instance-metadata resolution, returning the cached plain Bearer AT
(token_type=Bearer, NOT cert-bound).
In .NET the 2nd call crashes because the AT is cached under the mtlsauth.*
host and instance-metadata discovery rejects that host. msal-python
structurally avoids this by decoupling the cache/metadata authority (login.*,
where the plain Bearer AT lives) from the transport endpoint (mtlsauth.*): the
2nd lookup resolves login.* -- the normal, always-safe path -- so the cached
Bearer AT is returned without feeding the mtlsauth.* host into discovery.
Only client-credentials auto-serves from cache in msal-python (OBO, refresh,
and auth-code have no silent-first lookup), so CC is the flow that exercises
this regression.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Cover the send_certificate_over_mtls feature in tests/test_e2e.py, mirroring
MSAL .NET's live + capture split and reusing the SNI allow-listed app/authority
constants already in PublicCloudScenariosTestCase.
- test_credential_x509_send_certificate_over_mtls_output_bearer: LIVE
client-credentials acquisition (allow-listed app, westus3, Key Vault resource)
that presents the SN/I cert on the TLS handshake to the mTLS endpoint and
asserts a PLAIN Bearer token back (token_type=Bearer, binding_certificate
absent) plus a normal flagless cache hit -- the caller-visible proof that the
AT is an ordinary Bearer entry, not a key-id-fenced mtls_pop entry.
- test_send_certificate_over_mtls_user_flows_capture_request: request-capture
for OBO, refresh-token, and auth-code over the real lab SN/I certificate --
asserts each builds the mtlsauth endpoint + a private_key_jwt client_assertion
+ the flow's own grant with none of the mtls_pop markers, without needing
those (non-mTLS-enabled) apps allow-listed.
- test_send_certificate_over_mtls_user_flows_live_acquire: skip-gated live
OBO/refresh placeholder documenting the pending server-side app
mTLS-enablement (AADSTS700027 / AADSTS392189), mirroring MSAL .NET's [Ignore].
All three are gated (lab cert / explicit skip), collect cleanly, and skip
locally; the live and capture cells run in the lab pipeline.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add a docstring admonition to the client_credential parameter describing the
Bearer-over-mTLS opt-in: set send_certificate_over_mtls=True on the certificate
credential dictionary to present the cert on the mTLS handshake and receive a
plain (non-cert-bound) Bearer token. Notes that it covers all four confidential
flows, defaults to False, requires a certificate credential, and that a
per-request mtls_proof_of_possession=True always takes precedence.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add a negative assertion to test_cc_second_call_is_bearer_cache_hit that the
cached plain Bearer AT is stored under the ORIGINAL login.* host and its
environment never contains "mtlsauth". This is the assertion that would fail if
the cache seam were ever switched to the .NET-style mtlsauth.* environment
(Option A), which would silently break msal-python's login.*-keyed cache lookup
and defeat the 2nd-call cache hit. Also assert result1 == result2 (the same
cached AT is returned) alongside the existing exactly-one-network-POST guard.
Mirrors the cross-SDK finding that a 2nd-call cache test can pass without
actually proving the cache environment; this makes the env dependence explicit
and deterministic.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Rethrow only the private recorder transport failure outside OAuth call frames. Exercise real application client-credential and OBO DNS failures with expanded pytest tracebacks. Keep cert=None cold/cache behavior covered without changing production code.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…aries
Retain all combined scenarios and wrap custom invalid-grant calls with the private transport-failure boundary.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sanitize token validation errors to prevent credential-bearing tracebacks
tests/mtls_e2e_helpers.py:184
When token_request_summary() rejects a response or client assertion, its AssertionError bypasses the outer handler and preserves credential-bearing Requests/OAuth frames. Argument-enabled pytest tracebacks can then expose client assertions or refresh tokens even with locals disabled. Route validation failures through the sanitized outer rethrow, and add an acquisition-level malformed-response traceback regression.
Sanitize custom acquisition validation failures and record transport errors
tests/mtls_http_e2e_helpers.py:60
During a custom acquisition, validation failures from token_request_summary() bypass record_custom_transport_failures() and retain transport frames containing the client assertion, including a live FIC leg-1 token. The direct child.post() regression misses those callers, so argument-enabled pytest tracebacks can still expose credentials. Route validation failures through a sanitized outer rethrow with a distinct validation message, and add an acquisition-level malformed-response traceback regression.
Python 3.9 renders the inline dummy assertion in the visible leaf source even after transport exceptions are sanitized. Keep only the acquisition call in a hidden helper; leave every validation assertion visible and preserve all full/prefix/suffix privacy checks. Reproduced before and verified after on Python 3.9.25 with pytest 8.4.2.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Preserve the equivalent hidden combined invalid-grant acquisition boundary and all strict existing validation; no combined tree change is required.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use the shared private validation sentinel only around token_request_summary and rethrow it outside OAuth frames with a static validation diagnostic, distinct from transport errors. Cover real-app bad JSON and signed-assertion validation in client credentials and OBO without masking unrelated assertions.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…capture
Retain all existing grant assertions and exercise the same private boundary in the new summary-validation regressions.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Security-sensitive transport and cache integration needs human sign-off after the prerequisite PRs merge and pending final-head live validation completes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Scope and dependencies
Combines #938, #939, #947, and #974, retaining their cross-feature transport integration and E2E coverage. Do not merge before those feature dependencies land. The diff against
devincludes their inherited production changes.Combined integration
Bearer-over-mTLS uses the certificate-scoped custom HTTP factory and endpoint protections while preserving mTLS PoP and FIC
jwt-popbehavior. This introduces no additional public API or persisted-format redesign.binding_certificateis returned only when the final access token ismtls_pop; a certificate-bound FIC assertion does not make a final Bearer token certificate-bound.Cross-feature coverage
Single-feature coverage lives in its owning PRs. This PR retains the sibling-dependent scenarios and complete mandatory live inventory. Test setup and the reference-to-Python assertion mapping are documented in tests/README.md.
Exclusions
IMDSv2; non-exportable/VBS/provider-backed keys; managed identity; new SDK public APIs/features or lab provisioning; intentionally disabled reference successful user acquisitions; .NET-only callback options without a Python equivalent; and unrelated legacy E2E skips. This does not claim repository-wide skip elimination.