Fix/cert path validator exception - #3074
Merged
Merged
Conversation
StylianosGakis
approved these changes
Aug 20, 2026
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Background
We were seeing a steady stream of this in Datadog:
Three things came out of investigating it.
Our TLS setup is fine. No custom trust config anywhere in the app;
authlibuses the defaultplatform trust manager.
auth.prod.hedvigit.comandapollo-router.prod.hedvigit.comserve the samecertificate, rooted at
Amazon Root CA 1cross-signed byStarfield Services Root CA G2, which hasbeen in the Android trust store since long before our
minSdk 23. Not an infra incident, and not arelease regression.
The volume was misleading. 499 of 644 events over a month (77.5%) came from a single device:
one member, 24 sessions, six days, ~499 taps of Retry. Excluding them, the real member-facing rate is
about 4.8/day, flat. Group by
@usr.device_idbefore drawing conclusions from these counts.We were making it much worse. That member logged in successfully 14 times and was force-logged-out
13 times. Any network error during a background token refresh caused us to delete their tokens, which
dropped them at the BankID login screen and forced a full re-login over the same failing network.
Practical gain of this release
1. Members stop losing their session over a bad moment of network.
The only change with real user impact. Previously one failed background token refresh destroyed the
session; now the session survives and the next request retries. Directly measurable — see the query
below.
2. We stop writing members' access and refresh tokens into Datadog and Crashlytics.
A log line was interpolating the whole
AuthStatus.LoggedIn, which carries both tokens. Access tokenslive ~1h, refresh tokens ~24h, and a refresh token can mint new access tokens — so these were live
credentials in our log aggregators. Historical exposure has since expired. Invisible to users.
3. Members' network problems no longer count as our app errors.
Certificate-trust failures now log at WARN instead of ERROR, so the error rate reflects our bugs.
4. We may learn what actually causes the certificate failures. New diagnostic logging, below.
What this release does not do
It does not fix the certificate error. Nobody who currently cannot log in will suddenly be able to.
The gain is that far fewer members get pushed into that state, and that we stop leaking credentials
while it happens.
Queries to run
The lockouts we are now preventing
Every hit is a forced logout that would have happened before this release and now doesn't. On the one
device we investigated, this was 13 occurrences in six days.
Legitimate logouts (server actually rejected the token) are unchanged:
The old message
"Refreshing token failed. Invalidating present tokens"no longer exists, so itshould drop to zero.
The new diagnostic
Messages look like:
That is a real line captured on device, not an illustration.
msgcomes last and is quoted because itis the only field containing spaces, so the others stay cleanly parseable.
The fields are substrings in the message, so full-text search works but faceting does not. Count each:
A Datadog grok parser on this message would give proper facets — ops change, no code needed.
What should mostly disappear
What remains here is non-TLS failure (the
Unable to resolve hostDNS cases), which still log atERROR deliberately.
How to interpret the diagnostic
userCaCount1 or higheruserCaCount=0+authHost=loopback/unspecified/privateuserCaCount=0+authHost=public+transport=wifiuserCaCount=-1TlsDiaglines while errors persistField reliability, established by testing
userCaCount— trustworthy. Verified on an emulator: goes 0 → 1 when a CA is installed as auser certificate. This is the field that matters.
transport— trustworthy. Returns real values. Note that on-device ad-blockers use Android'sVPN API, so
vpnhere alongside a trust failure is a strong signal.authHost— narrow. Only catches DNS-level filtering (a local filter answering0.0.0.0or127.0.0.1). Our own lookup bypasses an HTTP proxy, so proxy-based interception readspublic.failure— expectjavax.net.ssl.SSLHandshakeException, always. Verified on device. The realCertPathValidatorExceptionis never a link in the cause chain; Android only names it inside thehandshake exception's message. Classification therefore matches on the message text ("trust anchor" /
"certification path"), not on the exception type.
msg— the wording the classification depends on. Captured verbatim (truncated at 200 chars) sowe can spot the provider changing it, or it differing across OEMs and API levels. If that wording
ever changes, these events stop being recognised and revert to the pre-existing
status:errorlinewe already monitor — so the regression is visible rather than silent.
the served chain on a trust failure, so
userCaCountandtransportare the only interceptionsignals we get.
Cautions when reading the results
fewer in practice.
@usr.device_idfirst. One stuck member produced 77% of the previous batch.the login screen, no further input, one event. The app never retries on its own, so every event
beyond the first is a real Retry tap — event counts are a fair proxy for how hard somebody was
trying, and sub-second gaps mean frantic tapping rather than a loop in our code.
Known follow-ups, not in this release
observeLoginStatusgives up on the firstIOExceptionmid-poll, so a brief network blip ends anin-progress BankID login permanently (26 occurrences on the investigated device).
AuthTokenResult.Error.IOError) carries no diagnostics; only the login pathis instrumented.