Skip to content

Add "source" field to user-agent header#3

Closed
shivampkumar wants to merge 2 commits into
mirror/pr-2230-basefrom
mirror/pr-2230
Closed

Add "source" field to user-agent header#3
shivampkumar wants to merge 2 commits into
mirror/pr-2230-basefrom
mirror/pr-2230

Conversation

@shivampkumar

@shivampkumar shivampkumar commented Jun 11, 2026

Copy link
Copy Markdown

Why?

Earlier this year, we removed the output of uname from the user-agent header for privacy reasons. It over-collected data we usually didn't need.

Since then, our tech support team has reached out asking us to re-add some of the information to help with complex debugging tasks: sometimes they need to help users identify exactly which machine produced an API call.

Rather than just revert the previous change and reintroduce the same privacy issues that caused its removal in the first place, we're taking a more privacy-focused approach. Rather than send all the PII in plaintext, we hash it first. This way it can still be used to confirm the source of an API call if a user generates the hashes themselves, but isn't sensitive in and of itself.

What?

  • add source key to user-agent hash. It's contents are an md5 hash of uname -a
  • add tests

See Also


Mirrored from stripe/stripe-java#2230 by xavdid for the VibeOps live demo. Real PR, real diff, reviewed by the same pipeline our customers run.

@vibeops-ai vibeops-ai 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.

⚠️ VibeOps Review — The PR has one blocker: the new 'source' field bypasses the telemetry opt-out mechanism, potentially violating user privacy expectations.

🟡 Human review required · verdict=request_changes

Blockers (1)

Source field bypasses telemetry opt-out mechanism (confirmed by logic, skeptic_agreed)
src/main/java/com/stripe/net/HttpClient.java:279

The UNAME_HASH is included in the X-Stripe-Client-User-Agent header unconditionally, even when users set Stripe.enableTelemetry = false to opt out of system information collection. The platform field correctly respects this setting (lines 261-269), but the source field does not. This creates inconsistent privacy behavior where users who explicitly disable telemetry still have their system fingerprint (OS, Java version, hostname hash) transmitted. Fix: wrap the source field insertion with the same telemetry guard: if (Stripe.enableTelemetry && !UNAME_HASH.isEmpty()) { propertyMap.put("source", UNAME_HASH); }

if (Stripe.enableTelemetry && !UNAME_HASH.isEmpty()) {
  propertyMap.put("source", UNAME_HASH);
}

What's good

  • Good defensive programming with comprehensive exception handling in computeUnameHash() and getHostname() methods, ensuring the system degrades gracefully if system properties or hostname resolution fails.
  • Well-structured test coverage that validates both the presence of the source field and the edge case where UNAME_HASH is empty, demonstrating thorough consideration of different runtime scenarios.

Context

  • The PR description mentions this is a partial restoration of system information that was previously removed for privacy reasons, with tech support requesting some data back for debugging. The telemetry opt-out mechanism appears to be the intended privacy control.

Reviewed by VibeOps

propertyMap.put("ai_agent", aiAgent);
}

if (!UNAME_HASH.isEmpty()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Blocker · Security · The UNAME_HASH is included in the X-Stripe-Client-User-Agent header unconditionally, even when users set Stripe.enableTelemetry = false to opt out of system information collection. The platform field correctly respects this setting (lines 261-269), but the source field does not. This creates inconsistent privacy behavior where users who explicitly disable telemetry still have their system fingerprint (OS, Java version, hostname hash) transmitted. Fix: wrap the source field insertion with the same telemetry guard: if (Stripe.enableTelemetry && !UNAME_HASH.isEmpty()) { propertyMap.put("source", UNAME_HASH); }

🔗 Backed by: agent reasoning

Suggested change
if (!UNAME_HASH.isEmpty()) {
if (Stripe.enableTelemetry && !UNAME_HASH.isEmpty()) {
propertyMap.put("source", UNAME_HASH);
}

Was this helpful? React with 👍 or 👎 to help VibeOps learn.

@vibeops-ai vibeops-ai 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.

⚠️ VibeOps Review — The source field bypasses the telemetry opt-out flag, creating a privacy inconsistency that must be fixed.

🟡 Human review required · verdict=request_changes

Blockers (1)

Source field bypasses telemetry opt-out flag (confirmed by logic)
src/main/java/com/stripe/net/HttpClient.java:279

The source field (MD5 hash including hostname) is added to X-Stripe-Client-User-Agent unconditionally, while the platform field (OS info only) respects Stripe.enableTelemetry. When users set Stripe.enableTelemetry = false to opt out of telemetry, they expect no system information to be transmitted, but the hostname-derived hash still gets sent. Fix: wrap the source field addition in if (Stripe.enableTelemetry) like the platform field at lines 261-268.

if (Stripe.enableTelemetry && !UNAME_HASH.isEmpty()) {
  propertyMap.put("source", UNAME_HASH);
}

💭 Heads-up · new code, no historical precedent (1)

Agent-confident findings on patterns we haven't seen reviewed before in this repo. Not blocking. Skim, ignore, or address.

UNAME_HASH is declared as a non-final, non-volatile package-private static field (static String UNAME_HASH)
src/main/java/com/stripe/net/HttpClient.java:30

UNAME_HASH is declared as a non-final, non-volatile package-private static field (static String UNAME_HASH). This has two problems: (1) Without volatile, reads/writes from multiple threads are not guaranteed to see a consistent value — a thread could observe a partially-initialized or stale value. (2) The field is mutable by any code in the same package (as demonstrated by the test directly assigning HttpClient.UNAME_HASH = ""), making it easy to accidentally corrupt shared state. Since the value is computed once at class-load time and should never change, it should be declared static final. The test that mutates it should instead use a test-specific override mechanism (e.g., a package-private setter or reflection) rather than relying on the field being mutable.

🔗 Backed by: agent reasoning

What's good

  • Good test coverage for the new source field functionality, including both positive and negative cases.
  • Clean implementation of MD5 hashing with proper exception handling and fallback to empty string.

Context

  • The PR description mentions re-adding system information for tech support debugging after privacy concerns led to its removal earlier this year.

Reviewed by VibeOps

propertyMap.put("ai_agent", aiAgent);
}

if (!UNAME_HASH.isEmpty()) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛑 Blocker · Security · The source field (MD5 hash including hostname) is added to X-Stripe-Client-User-Agent unconditionally, while the platform field (OS info only) respects Stripe.enableTelemetry. When users set Stripe.enableTelemetry = false to opt out of telemetry, they expect no system information to be transmitted, but the hostname-derived hash still gets sent. Fix: wrap the source field addition in if (Stripe.enableTelemetry) like the platform field at lines 261-268.

🔗 Backed by: agent reasoning

Suggested change
if (!UNAME_HASH.isEmpty()) {
if (Stripe.enableTelemetry && !UNAME_HASH.isEmpty()) {
propertyMap.put("source", UNAME_HASH);
}

Was this helpful? React with 👍 or 👎 to help VibeOps learn.

/** A value indicating whether the client should sleep between automatic request retries. */
boolean networkRetriesSleep = true;

static String UNAME_HASH = computeUnameHash();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 Heads-up · Bug · UNAME_HASH is declared as a non-final, non-volatile package-private static field (static String UNAME_HASH). This has two problems: (1) Without volatile, reads/writes from multiple threads are not guaranteed to see a consistent value — a thread could observe a partially-initialized or stale value. (2) The field is mutable by any code in the same package (as demonstrated by the test directly assigning HttpClient.UNAME_HASH = ""), making it easy to accidentally corrupt shared state. Since the value is computed once at class-load time and should never change, it should be declared static final. The test that mutates it should instead use a test-specific override mechanism (e.g., a package-private setter or reflection) rather than relying on the field being mutable.

No historical precedent in your repo — agent reasoning only. Not blocking. Skim, ignore, or address.

🔗 Backed by: agent reasoning

Suggested change
static String UNAME_HASH = computeUnameHash();
static final String UNAME_HASH = computeUnameHash();

Was this helpful? React with 👍 or 👎 to help VibeOps learn.

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