# Patch Analysis: Apache Kafka SASL/OAUTHBEARER Default JWT Validation

## Scope and sources reviewed

This analysis reviewed the parent reproduction evidence, the Apache Kafka security policy/model, Kafka 4.1.0 and 4.1.2 binary behavior, and a source diff between the Kafka 4.1.0 and 4.1.2 release tags for the OAuth bearer validator path.

Key reviewed artifacts:

- `bundle/repro/rca_report.md`
- `bundle/logs/evidence.log`
- `bundle/logs/vuln_variant/oauth_diff_4.1.0_4.1.2.diff`
- `bundle/logs/vuln_variant/oauth_callsite_scan_4.1.2.txt`
- `bundle/logs/vuln_variant/oauth_config_docs_scan_4.1.2.txt`
- Apache Kafka `SECURITY.md`
- Apache Kafka `docs/security/security-model.md`
- Apache Kafka `docs/security/security-overview.md`

Exact tested source identities:

- Vulnerable release tag: Apache Kafka 4.1.0, `13f70256db3c994c590e5d262a7cc50b9e973204`
- Fixed release tag: Apache Kafka 4.1.2, `c82fd9b934b4c1e6fa799e3f1dcc8f08d997740c`

## Target threat model / security scope

Apache Kafka's `SECURITY.md` points vulnerability triage to `docs/security/security-model.md`. The security model states that:

- Security is off by default; operators must explicitly configure authentication, authorization, and transport encryption before exposure to untrusted networks.
- Broker listeners using SASL are part of the network security boundary when security is configured.
- Trusted operators, classpath/JAR loading, broker shell access, development tooling, and deliberately unsecured configurations are generally outside the vulnerability boundary.
- OAUTHBEARER is suitable for integration with an identity provider, and production deployments must configure a JWKS endpoint and validator.

The variant tested here is in scope because an untrusted network peer sends attacker-controlled JWT bytes over a configured broker SASL/OAUTHBEARER listener. The analysis avoids treating deliberately insecure defaults or operator-chosen unsecured callback handlers as a bypass.

## What the fix changes

The relevant source change is in:

- `clients/src/main/java/org/apache/kafka/common/security/oauthbearer/DefaultJwtValidator.java`

Kafka 4.1.0 behavior:

```java
if (verificationKeyResolver.isPresent()) {
    delegate = new BrokerJwtValidator(verificationKeyResolver.get());
} else {
    delegate = new ClientJwtValidator();
}
```

Kafka 4.1.2 behavior:

```java
if (verificationKeyResolver.isPresent()) {
    delegate = new BrokerJwtValidator(verificationKeyResolver.get());
} else {
    ConfigurationUtils cu = new ConfigurationUtils(configs, saslMechanism);

    if (cu.containsKey(SaslConfigs.SASL_OAUTHBEARER_JWKS_ENDPOINT_URL)) {
        delegate = new BrokerJwtValidator();
    } else {
        delegate = new ClientJwtValidator();
    }
}
```

The fix adds imports for `SaslConfigs` and `ConfigurationUtils` and changes the default delegate selection when no resolver has been injected. If a JWKS endpoint is configured in the effective listener-prefixed SASL configuration, Kafka 4.1.2 creates `BrokerJwtValidator` rather than falling back to `ClientJwtValidator`.

## Code paths and functions covered by the fix

Covered production path:

1. TCP peer opens a Kafka broker connection.
2. Peer sends `SaslHandshake(OAUTHBEARER)`.
3. Peer sends `SaslAuthenticate` with `auth=Bearer <JWT>`.
4. `OAuthBearerValidatorCallbackHandler.handle()` receives `OAuthBearerValidatorCallback`.
5. `OAuthBearerValidatorCallbackHandler.handleValidatorCallback()` calls `jwtValidator.validate(callback.tokenValue())`.
6. The configured/default `DefaultJwtValidator` chooses its delegate.
7. On Kafka 4.1.2 with `sasl.oauthbearer.jwks.endpoint.url`, `DefaultJwtValidator` chooses `BrokerJwtValidator`.
8. `BrokerJwtValidator` uses jose4j validation with a verification key resolver, disallows `none`, requires `exp` and `iat`, and validates expected issuer/audience when configured.

Important files/functions:

- `clients/src/main/java/org/apache/kafka/common/network/SaslChannelBuilder.java`
  - Selects the broker SASL callback handler. For OAUTHBEARER, operators must configure the secured server callback handler; otherwise Kafka's default unsecured handler is used.
- `clients/src/main/java/org/apache/kafka/common/security/oauthbearer/OAuthBearerValidatorCallbackHandler.java`
  - Broker-side callback handler that receives client-provided JWTs and delegates to a `JwtValidator`.
- `clients/src/main/java/org/apache/kafka/common/security/oauthbearer/DefaultJwtValidator.java`
  - The fixed delegate-selection logic.
- `clients/src/main/java/org/apache/kafka/common/security/oauthbearer/BrokerJwtValidator.java`
  - Secure broker validator enforcing signature, `exp`, `iat`, issuer, audience, and claim extraction.
- `clients/src/main/java/org/apache/kafka/common/security/oauthbearer/ClientJwtValidator.java`
  - Lightweight validator retained for client-side parsing/backward compatibility; not sufficient for broker security validation.

## Fix assumptions

The fix assumes:

1. A production broker using OAUTHBEARER with default JWT validation will configure `sasl.oauthbearer.jwks.endpoint.url` in the relevant listener/mechanism-scoped configuration.
2. Presence of that configuration is the reliable signal that the default validator is running in a broker/OIDC validation context and should use `BrokerJwtValidator`.
3. Operators who intentionally omit JWKS or use the explicitly unsecured callback handler are outside the production-security expectation described by the Kafka security model.
4. `BrokerJwtValidator` correctly covers the actual JWT validation properties: signature trust, expiration, issued-at presence, expected issuer, and expected audience.

## Code paths / inputs not covered by the fix

The fix does not remove these code paths, but they are not fixed-version bypasses under the stated security model:

- `ClientJwtValidator` remains available and continues to parse tokens without verifying signature/issuer/audience/current-time expiry. It is documented and used for client-side login/token parsing compatibility rather than as the secured broker validator.
- `OAuthBearerUnsecuredValidatorCallbackHandler` remains available and is still the default OAUTHBEARER server callback when an operator does not configure a secured handler. This is a deliberately insecure/test configuration and not a bypass of the secured-handler fix.
- Brokers without `sasl.oauthbearer.jwks.endpoint.url` can still fall back to `ClientJwtValidator` through `DefaultJwtValidator`, but such a broker lacks the core JWKS material needed for production JWT signature validation.
- Custom `sasl.oauthbearer.jwt.validator.class` implementations remain trusted pluggable code. Kafka's security model treats pluggable interfaces and classpath code as trusted operator-supplied code.

## Behavior before and after the fix

Before the fix (Kafka 4.1.0):

- With `OAuthBearerValidatorCallbackHandler` and JWKS configured, `DefaultJwtValidator` still delegated to `ClientJwtValidator` unless a resolver had been injected by constructor.
- `ClientJwtValidator` validated presence/type of important claims and constructed `BasicOAuthBearerToken`, but did not verify current-time expiration, signature trust, issuer, or audience.
- Parent proof: expired JWT accepted.
- Variant proof: non-expired JWTs with wrong issuer, wrong audience, and untrusted signature accepted.

After the fix (Kafka 4.1.2):

- With `OAuthBearerValidatorCallbackHandler` and JWKS configured, `DefaultJwtValidator` delegates to `BrokerJwtValidator`.
- `BrokerJwtValidator` rejects expired JWTs and the tested invalid issuer/audience/signature variants.
- Valid control tokens from the trusted mock issuer are accepted, showing the rejection is not caused by a broken test setup.

## Variant search matrix

Runtime-backed candidates tested:

| Candidate | Difference from parent | Kafka 4.1.0 | Kafka 4.1.2 | Result |
|---|---|---:|---:|---|
| Wrong issuer | Valid future `exp`, correct audience, trusted signature, but `iss` mismatches expected issuer | Accepted | Rejected | Alternate trigger only |
| Wrong audience | Valid future `exp`, correct issuer, trusted signature, but `aud` mismatches expected audience | Accepted | Rejected | Alternate trigger only |
| Tampered/untrusted signature | Valid future `exp`, correct issuer/audience, but signed by a non-JWKS key using trusted `kid` | Accepted | Rejected | Alternate trigger only |

The matrix confirms the same root cause in 4.1.0 (default broker validator delegates to `ClientJwtValidator`) but also confirms the 4.1.2 fix covers these materially distinct JWT validation failures.

## Completeness assessment

For the secured broker callback path with JWKS configured, the fix appears complete for the tested family of JWT validation failures: expiration, issuer, audience, and signature trust. I did not find a fixed-version bypass in this path.

The main remaining risk is operator misconfiguration, not a patch gap: Kafka still permits intentionally unsecured OAUTHBEARER test handlers and custom validators. That is consistent with Kafka's security model, but production deployments should lint or warn on those settings.

## Recommendations

1. Keep the Kafka 4.1.2 delegate-selection fix.
2. Add or retain integration tests that send raw SASL/OAUTHBEARER frames directly to a broker for:
   - expired JWT,
   - wrong issuer,
   - wrong audience,
   - `alg=none`/unsigned JWT,
   - token signed by an untrusted key.
3. Add documentation or runtime warnings for broker OAUTHBEARER listeners configured without `OAuthBearerValidatorCallbackHandler` or without `sasl.oauthbearer.jwks.endpoint.url` when operators appear to expect production OAuth validation.
4. Treat use of custom validators as trusted-operator code, and document that a custom validator must enforce signature, issuer, audience, and expiration if used for production broker authentication.
