Skip to content

Fix remote address extraction in audit event origin string#3992

Merged
strehle merged 6 commits into
cloudfoundry:developfrom
gdgenchev:fix-audit-origin
Jul 24, 2026
Merged

Fix remote address extraction in audit event origin string#3992
strehle merged 6 commits into
cloudfoundry:developfrom
gdgenchev:fix-audit-origin

Conversation

@gdgenchev

@gdgenchev gdgenchev commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes an issue in the audit event origin builder where Authentication.getDetails() was unconditionally cast to String to extract the remoteAddress.

When details was an object type (e.g., UaaAuthenticationDetails or OAuth2AuthenticationDetails), this cast raised a ClassCastException. The exception was swallowed by a broad catch block that appended details.toString() directly to the audit log. This resulted in a possible issue:

Data Leakage / Contamination: Internal state (such as token values, request parameters, and map contents) was leaked into audit logs, often mixing unrelated sources into a single line.

Example (Before)

For an OAuth2AuthenticationDetails instance, the log line improperly included the full toString() dump along with appended token inspector fields:

Audit: GroupModifiedEvent ('{"role_collection":"evenRC1771046947","op":"add_saml_attribute","saml_entity":"iot-idp-1771046947","attribute":"Groups","value":"even"}'): principal=evenRC1771046947, origin=[client=admin, details=(remoteAddress=172.18.0.1, sessionId=<SESSION>, tokenType=Bearer, tokenValue=<TOKEN>, sub=admin, iss=http://localhost:8080/uaa/oauth/token)], identityZoneId=[uaa]

The remoteAddress/sessionId/tokenType/tokenValue fragment came from the details object's toString, then sub/iss were appended by the token inspector — a shape that mixed two unrelated sources into one line.


Changes

Replaced the aggressive cast-and-catch approach with pattern-matching switch logic that reads the remoteAddress field safely depending on the runtime type of details:

  • UaaAuthenticationDetails, OAuth2AuthenticationDetails, WebAuthenticationDetails: Directly invokes their respective accessor methods (getOrigin() / getRemoteAddress()).
  • Map: Reads the remoteAddress key (supports ResourceOwnerPasswordTokenGranter and ensures forward compatibility).
  • String: Parsed as JSON to look for remoteAddress. Non-JSON strings fall through safely.
  • Fallback / Unknown Types: Non-matching types, missing keys, and null fields gracefully fall through without emitting a field, preventing arbitrary state dumps.

Side effect: sessionId=<SESSION> marker is dropped

The sessionId=<SESSION> marker previously present in audit origins only appeared via the buggy toString()-dump path. Its value was a hardcoded literal — not a session identifier — so it carried no correlation or lookup signal. type=UaaAuthenticationDetails in the same origin line already conveys "session-shaped auth," making the marker redundant.

AuditCheckMockMvcTests is updated accordingly:

  • sessionId=<SESSION> assertions are removed.
  • assertLogMessageWithSession and assertLogMessageWithoutSession collapse into a single assertLogMessage helper (they are now equivalent).

Test Coverage

Added unit tests in AbstractUaaEventTest covering:

  • Supported Details Types: UaaAuthenticationDetails and OAuth2AuthenticationDetails — remoteAddress extraction via typed accessors.
  • Map Handling: Both present and absent remoteAddress key paths, verifying map contents are not leaked.
  • String Edge Cases: Valid JSON strings, non-JSON strings, and JSON with null remoteAddress.
  • Origin Shape: Verification of the combined client + user origin shape in audit output.

The audit event origin builder cast Authentication.getDetails() to String
unconditionally to parse a remoteAddress out of a JSON blob. When details
was not a String — a common case for UaaAuthenticationDetails and
OAuth2AuthenticationDetails, which expose the remote address directly —
the cast raised ClassCastException, which the broad catch swallowed by
appending the raw Object.toString() to the audit log. This leaked
internal state (map contents, request parameters, token fragments,
session markers) into the log line and made the origin's shape depend
on which details type happened to be attached to the Authentication.

For example, an OAuth2AuthenticationDetails instance would have its full
toString appended, producing a log line like:

  Audit: GroupModifiedEvent ('{"role_collection":"evenRC1771046947","op":"add_saml_attribute","saml_entity":"iot-idp-1771046947","attribute":"Groups","value":"even"}'): principal=evenRC1771046947, origin=[client=admin, details=(remoteAddress=172.18.0.1, sessionId=<SESSION>, tokenType=Bearer, tokenValue=<TOKEN>, sub=admin, iss=http://localhost:8080/uaa/oauth/token)], identityZoneId=[uaa]

where the remoteAddress/sessionId/tokenType/tokenValue fragment came from
the details object's toString, then sub/iss were appended by the token
inspector — a shape that mixed two unrelated sources into one line.

Replace the cast-and-catch with a pattern-matching switch that reads the
remote address from each known details type: UaaAuthenticationDetails,
OAuth2AuthenticationDetails, a Map (used by
ResourceOwnerPasswordTokenGranter — currently has no remoteAddress key,
but supported for forward compatibility), or a String parsed as JSON.
Non-JSON strings, maps without a remoteAddress key, and null-valued
fields fall through safely. Details types without a known accessor
produce no remoteAddress line rather than dumping their contents.

Add unit tests in AbstractUaaEventTest for each supported details type,
for the non-JSON String and null-remoteAddress regression cases, for the
Map paths (with and without a remoteAddress key), and for the
client+user origin shape.

Update AuditCheckMockMvcTests to reflect that audit origins no longer
carry the sessionId=<SESSION> marker: the marker was only ever emitted
via the buggy toString-leak path, carried no information (its value was
a hardcoded literal), and disappears now that the leak is fixed. Drop
the sessionId=<SESSION> assertions and collapse the
assertLogMessageWithSession / assertLogMessageWithoutSession helpers,
which are now equivalent, into a single assertLogMessage helper.
@gdgenchev

gdgenchev commented Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

@tack-sap, do you remember why sessionId=<SESSION> was pinned across the audit tests in 334a8cb? From the code it looks like it was added specifically to mask the raw session id in the log output due to security concerns — is that right, or was there a downstream consumer (SIEM / compliance / parser) relying on the literal sessionId=<SESSION> string being present?

Context: I'm implementing custom audit logging on our side and used cf-uaa as reference. AI noticed a ClassCastException in the origin builder where Authentication.getDetails() gets cast to String unconditionally — for UaaAuthenticationDetails / OAuth2AuthenticationDetails the cast fails, the broad catch swallows it, and the raw Object.toString() gets dumped into the audit line (possibly leaking all toString() details)

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

Pull request overview

Fixes audit event origin construction to safely extract remoteAddress from Authentication.getDetails() across multiple runtime shapes, avoiding ClassCastException and preventing audit-log contamination from details.toString() dumps.

Changes:

  • Replaces cast-and-catch parsing with type-based extraction for remoteAddress (including JSON-string and Map support).
  • Adds/updates unit tests to ensure details content is not leaked into audit origin strings.
  • Updates MockMvc audit tests to stop asserting the legacy sessionId=<SESSION> marker.

Reviewed changes

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

File Description
server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java Introduces type-based remoteAddress extraction and removes reliance on details.toString() fallbacks.
server/src/test/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEventTest.java Adds coverage for new origin-shaping behaviors across multiple details types.
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/audit/AuditCheckMockMvcTests.java Updates audit expectations to match the new origin format (removing sessionId=<SESSION> assumptions).

@strehle

strehle commented Jul 23, 2026

Copy link
Copy Markdown
Member

@gdgenchev we will release soon, so if you want cleanup your PR and then we can take it with release

…ion events

Replace details.toString() in AbstractUaaAuthenticationEvent.getOrigin()
with an explicit implementation that emits only remoteAddress and clientId,
so the session identifier is no longer included even in masked form.

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

server/src/main/java/org/cloudfoundry/identity/uaa/audit/event/AbstractUaaEvent.java:148

  • Logging a WARN for every unhandled Authentication.details type in getOrigin can generate a lot of noise in production (audit origin is built for many events, and new auth mechanisms may introduce other detail types). Consider lowering this to DEBUG or removing it to avoid log spam.
            default -> {
                logger.warn("Unhandled Authentication.details type in audit origin: {}", details.getClass().getName());
                yield Optional.empty();
            }

@github-project-automation github-project-automation Bot moved this from Inbox to Pending Merge | Prioritized in Foundational Infrastructure Working Group Jul 23, 2026
@strehle
strehle merged commit 787a5b9 into cloudfoundry:develop Jul 24, 2026
26 checks passed
@github-project-automation github-project-automation Bot moved this from Pending Merge | Prioritized to Done in Foundational Infrastructure Working Group Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

4 participants