Skip to content

feat(client): add native mTLS transport recipe - #828

Merged
jbeckwith-oai merged 8 commits into
mainfrom
codex/mtls-native-jsse
Jul 31, 2026
Merged

feat(client): add native mTLS transport recipe#828
jbeckwith-oai merged 8 commits into
mainfrom
codex/mtls-native-jsse

Conversation

@jbeckwith-oai

Copy link
Copy Markdown
Contributor

Summary

  • document API-key mTLS using Java's native JSSE APIs and the SDK's existing OkHttp TLS hooks
  • expose followRedirects(boolean) to Java callers so mTLS clients can avoid presenting their client identity to redirect targets
  • add a compilable PKCS#12 example plus hermetic tests for complete certificate chains and missing intermediates
  • link the OpenAI Mutual TLS Beta Program opt-in guide

Design

This intentionally does not add a first-class certificate model or mTLS state to the SDK. Callers build a KeyStore, KeyManagerFactory, and SSLContext with native JSSE, then pass the resulting socket factory and the separately configured server trust manager through the existing OkHttp builder hooks.

The caller explicitly selects the global, EU, or custom mTLS endpoint. Certificate rotation rebuilds the SSL context, HTTP transport, and SDK client. API-key authentication remains required; certificate-only WIF and Realtime/WebSocket support are outside this change.

Validation

  • ./gradlew :openai-java-client-okhttp:test --tests com.openai.client.okhttp.OpenAIOkHttpClientNativeMutualTlsTest :openai-java-example:compileJava
  • ./gradlew lint :openai-java-client-okhttp:test :openai-java-example:compileJava
  • thermo-nuclear code-quality review
  • git diff --check

Copilot AI review requested due to automatic review settings July 31, 2026 20:00
@jbeckwith-oai
jbeckwith-oai requested a review from a team as a code owner July 31, 2026 20:00
@openai-sdks

openai-sdks Bot commented Jul 31, 2026

Copy link
Copy Markdown

OkTest Summary

237/237 SDK tests passed in 17.666s for Java SDK PR #828.

Test results — 42 files
Test Result Time
tests/chat-completions-complex-body.test.ts ✅ Passed 815ms
tests/chat-completions-create.test.ts ✅ Passed 684ms
tests/chat-completions-stream.test.ts ✅ Passed 369ms
tests/files-content-binary.test.ts ✅ Passed 241ms
tests/files-create-multipart.test.ts ✅ Passed 325ms
tests/files-list-pagination.test.ts ✅ Passed 362ms
tests/initialize-config.test.ts ✅ Passed 193ms
tests/instance-isolation.test.ts ✅ Passed 269ms
tests/models-list.test.ts ✅ Passed 200ms
tests/responses-background-lifecycle.test.ts ✅ Passed 343ms
tests/responses-body-method-errors.test.ts ✅ Passed 690ms
tests/responses-cancel-timeout.test.ts ✅ Passed 246ms
tests/responses-cancel.test.ts ✅ Passed 420ms
tests/responses-compact-retries.test.ts ✅ Passed 572ms
tests/responses-compact.test.ts ✅ Passed 413ms
tests/responses-create-advanced-stream.test.ts ✅ Passed 559ms
tests/responses-create-advanced.test.ts ✅ Passed 1.402s
tests/responses-create-disconnect.test.ts ✅ Passed 1.123s
tests/responses-create-errors.test.ts ✅ Passed 341ms
tests/responses-create-malformed-api-responses.test.ts ✅ Passed 318ms
tests/responses-create-retries.test.ts ✅ Passed 316ms
tests/responses-create-stream-failures.test.ts ✅ Passed 210ms
tests/responses-create-stream-timeout.test.ts ✅ Passed 244ms
tests/responses-create-stream-wire.test.ts ✅ Passed 6.504s
tests/responses-create-stream.test.ts ✅ Passed 134ms
tests/responses-create-terminal-states.test.ts ✅ Passed 425ms
tests/responses-create-timeout.test.ts ✅ Passed 236ms
tests/responses-create.test.ts ✅ Passed 279ms
tests/responses-delete.test.ts ✅ Passed 292ms
tests/responses-input-items-errors.test.ts ✅ Passed 258ms
tests/responses-input-items-list.test.ts ✅ Passed 319ms
tests/responses-input-items-options.test.ts ✅ Passed 208ms
tests/responses-input-tokens-count-timeout.test.ts ✅ Passed 204ms
tests/responses-input-tokens-count.test.ts ✅ Passed 419ms
tests/responses-malformed-inputs.test.ts ✅ Passed 5.438s
tests/responses-not-found-errors.test.ts ✅ Passed 523ms
tests/responses-parse.test.ts ✅ Passed 689ms
tests/responses-retrieve-retries.test.ts ✅ Passed 1.133s
tests/responses-retrieve.test.ts ✅ Passed 338ms
tests/responses-stored-method-errors.test.ts ✅ Passed 942ms
tests/retry-behavior.test.ts ✅ Passed 3.571s
tests/sdk-error-shape.test.ts ✅ Passed 440ms

View OkTest run #30667654507

SDK merge (4eadca4ff1a8) · head (00dcd3d386cf) · base (bb08dd9a306c) · OkTest (91635c6a2723)

Copilot AI 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.

Pull request overview

This PR adds documentation and runnable/tested examples showing how to use mutual TLS (mTLS) with the OkHttp-based OpenAI Java SDK via native JSSE (SSLContext/KeyStore), and exposes followRedirects(boolean) to Java callers to help prevent client-certificate disclosure to redirect targets.

Changes:

  • Documented an end-to-end “native JSSE mTLS” setup recipe in README.md, including endpoint selection and redirect guidance.
  • Added a compilable MutualTlsExample Java example demonstrating PKCS#12 loading and JSSE + SDK wiring.
  • Added hermetic tests verifying full client certificate chains are presented and that missing intermediates fail closed; added OkHttp test dependencies.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
README.md Adds the documented “native JSSE mTLS” recipe and links to the beta opt-in guide.
openai-java-example/src/main/java/com/openai/example/MutualTlsExample.java Adds a runnable Java example for configuring mTLS via JSSE and SDK OkHttp hooks.
openai-java-client-okhttp/src/test/kotlin/com/openai/client/okhttp/OpenAIOkHttpClientNativeMutualTlsTest.kt Adds hermetic MockWebServer-based tests for full-chain mTLS and missing-intermediate failures.
openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OpenAIOkHttpClientAsync.kt Exposes followRedirects(boolean) to Java (removes @JvmSynthetic) and documents default.
openai-java-client-okhttp/src/main/kotlin/com/openai/client/okhttp/OpenAIOkHttpClient.kt Exposes followRedirects(boolean) to Java (removes @JvmSynthetic) and documents default.
openai-java-client-okhttp/build.gradle.kts Adds mockwebserver and okhttp-tls test dependencies for the new mTLS tests.
Suppressed comments (1)

README.md:1734

  • This path construction will throw a NullPointerException if OPENAI_MTLS_KEYSTORE is unset. Using an explicit null check (or Objects.requireNonNull) makes the sample fail with a clearer message.
            Files.newInputStream(Paths.get(System.getenv("OPENAI_MTLS_KEYSTORE")))) {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md Outdated
Comment thread README.md Outdated
Copilot AI review requested due to automatic review settings July 31, 2026 20:35

@HAYDEN-OAI HAYDEN-OAI 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.

Reviewed exact head 98eb8d9. The native JSSE/OkHttp transport integration, default server trust and hostname verification, redirect disabling, rotation guidance, and Java runtime compatibility are sound, but the endpoint-selection, cross-provider credential, and certificate-chain enablement issues called out inline should be addressed before merge.

Comment thread openai-java-example/src/main/java/com/openai/example/MutualTlsExample.java Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated

Copilot AI 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.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Copilot AI review requested due to automatic review settings July 31, 2026 20:39

Copilot AI 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.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (2)

README.md:1740

  • Same eager-evaluation issue as the API key: System.getenv("OPENAI_BASE_URL") is read even when openai.baseUrl is set, which can break the recipe in restricted environments and is inconsistent with the SDK’s System.getProperty(...) ?: System.getenv(...) precedence style.
String baseUrl = System.getProperty("openai.baseUrl", System.getenv("OPENAI_BASE_URL"));
if (baseUrl == null || baseUrl.isEmpty()) {
    baseUrl = "https://mtls.api.openai.com/v1";
}

README.md:1736

  • In this snippet, System.getenv("OPENAI_API_KEY") is evaluated eagerly as the default argument to System.getProperty(...), even when openai.apiKey is present. This can cause the recipe to fail in environments where env access is restricted (e.g., SecurityManager / sandboxing) despite a correctly set system property. It also diverges from the SDK’s established precedence pattern (System.getProperty(...) ?: System.getenv(...), e.g. openai-java-core/src/main/kotlin/com/openai/core/ClientOptions.kt:585-590).

This issue also appears on line 1737 of the same file.

String apiKey = System.getProperty("openai.apiKey", System.getenv("OPENAI_API_KEY"));
if (apiKey == null || apiKey.isEmpty()) {
    throw new IllegalStateException(
        "openai.apiKey or OPENAI_API_KEY must be set for OpenAI mTLS");
}

Copilot AI review requested due to automatic review settings July 31, 2026 21:07
@jbeckwith-oai
jbeckwith-oai force-pushed the codex/mtls-native-jsse branch from 254c928 to e876c5c Compare July 31, 2026 21:07

Copy link
Copy Markdown
Contributor Author

Addressed the two suppressed Copilot findings in e876c5cc: the README recipe now reads openai.apiKey / openai.baseUrl first and only queries the corresponding environment variable when the system property is absent, matching the SDK’s established precedence and avoiding eager environment access. I also rebased the branch onto current main. Local lint, the focused mTLS tests, Java example compilation, the thermo-nuclear review, and git diff --check all pass.

Copilot AI 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.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@HAYDEN-OAI HAYDEN-OAI 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.

Re-reviewed exact head e876c5c. The three findings from review 4832028958 are resolved: the recipes explicitly require an OpenAI bearer key, preserve configured EU/custom endpoints, and document certificate-chain enablement. One substantive project/organization-scope regression remains, described inline.

Copilot AI review requested due to automatic review settings July 31, 2026 21:25

Copilot AI 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.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1b2a895688

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread openai-java-example/src/main/java/com/openai/example/MutualTlsExample.java Outdated
Copilot AI review requested due to automatic review settings July 31, 2026 21:33

Copilot AI 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.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@HAYDEN-OAI HAYDEN-OAI 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.

Re-reviewed exact head 1fb0ebb. The prior credential-selection, EU/custom endpoint, certificate-chain, organization/project-scope, and empty-endpoint findings are fixed. One newly validated P1 remains: a configured HTTP endpoint bypasses TLS/client authentication and sends the OpenAI bearer key in plaintext; details inline.

Copilot AI review requested due to automatic review settings July 31, 2026 21:38

Copilot AI 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.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Suppressed comments (3)

openai-java-client-okhttp/src/test/kotlin/com/openai/client/okhttp/OpenAIOkHttpClientNativeMutualTlsTest.kt:36

  • The test name suggests it validates that the full PKCS#12 chain is presented, but the test only asserts that a request succeeds when the intermediate is included. Renaming the test to reflect what it actually verifies will make failures easier to interpret.
    fun nativeMutualTlsPresentsFullPkcs12Chain() {

openai-java-example/src/main/java/com/openai/example/MutualTlsExample.java:119

  • requireHttpsBaseUrl discards the IllegalArgumentException cause when parsing the URI fails, which makes it harder to diagnose invalid URLs. Preserve the original exception as the cause.
        try {
            baseUri = URI.create(baseUrl);
        } catch (IllegalArgumentException ignored) {
            throw new IllegalStateException("OpenAI mTLS requires a valid HTTPS base URL");
        }

README.md:1756

  • The README recipe discards the IllegalArgumentException cause when parsing baseUrl fails, which makes debugging misconfigured URLs harder. Preserve the original exception as the cause.
try {
    baseUri = URI.create(baseUrl);
} catch (IllegalArgumentException ignored) {
    throw new IllegalStateException("OpenAI mTLS requires a valid HTTPS base URL");
}

Copilot AI review requested due to automatic review settings July 31, 2026 21:44
@jbeckwith-oai

Copy link
Copy Markdown
Contributor Author

Addressed the latest suppressed Copilot feedback in 00dcd3d3: the full-chain test now directly asserts that the server observed the client leaf followed by its intermediate certificate. I intentionally did not retain URI parsing exceptions as causes because Java includes the rejected endpoint value in those exceptions and custom URLs may contain credentials; both the README and example now document that security rationale beside the generic fail-closed error. Local lint, focused mTLS tests, example compilation, diff hygiene, and the thermo-nuclear review pass.

Copilot AI 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.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@jbeckwith-oai
jbeckwith-oai requested review from a team and HAYDEN-OAI July 31, 2026 22:03

@HAYDEN-OAI HAYDEN-OAI 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.

Re-reviewed exact head 00dcd3d. All prior findings are resolved: explicit OpenAI bearer-key selection, EU/custom endpoint and organization/project scope preservation, fail-closed empty/non-HTTPS endpoint handling, redirect suppression, and complete client-certificate-chain documentation plus presented-chain verification. No substantive issues found.

@jbeckwith-oai
jbeckwith-oai merged commit b40ba7a into main Jul 31, 2026
14 checks passed
@stainless-app stainless-app Bot mentioned this pull request Jul 31, 2026
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.

3 participants