You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When ~/.m2/settings.xml has multiple <server> entries that resolve to the same host, and one PAT is valid while another is stale, dropping the stale entry doesn't produce an Entra fallback for that feed. The plugin's credMap is keyed by host, so the still-valid entry occupies the host slot and the dropped feed inherits its credentials. Fetches against the dropped feed will 401 because the valid PAT isn't scoped to it.
Found during PR #6 self-review (round 4) — pre-existing latent bug that v0.0.10's probe-then-drop behavior makes user-visible with a misleading "falling through to Entra token acquisition" log.
resolvers +="FeedA" at "https://pkgs.dev.azure.com/myorg/_packaging/FeedA/maven/v1"
resolvers +="FeedB" at "https://pkgs.dev.azure.com/myorg/_packaging/FeedB/maven/v1"
addSbtPlugin("dev.chungmin"%"sbt-azure-devops-credentials"%"0.0.10")
sbt update:
Probe of FeedA → 200 → kept as Credentials("realm", "pkgs.dev.azure.com", "azure", VALID-pat)
Probe of FeedB → 401 → dropped → buildCredentialsFromMavenSettings returns None for FeedB
Log: [info] pkgs.dev.azure.com: settings.xml credentials returned 401; falling through to Entra token acquisition. ← misleading
But:
buildCredentialsWithAccessToken: credMap = {pkgs.dev.azure.com -> FeedA cred}; for FeedB resolver, credMap.contains(host) is true → no Entra cred generated
updateCoursierConf: looks up FeedB resolver's host → finds FeedA's cred → registers Authentication(azure, VALID-pat) against resolver id FeedB
Coursier sends FeedA's PAT when fetching from FeedB → feed returns 401 → user sees confusing failure
Why the bug is pre-existing, not introduced by 0.0.10
Even on 0.0.9 with the same shape (FeedA settings entry, no FeedB entry), credMap = {host -> FeedA cred} and FeedB ends up with FeedA's PAT applied. The host-keying is sbt/Ivy's native credential matching model — Credentials carries a host, not a resolver name. The plugin's credMap reflects that.
0.0.10 didn't introduce the bug; it just made the user-visible failure mode worse by adding a misleading "falling through to Entra" log over the same broken substrate.
updateCoursierConf — same credMap keying and same credMap.get(host) lookup, then registers repo.name -> Authentication(...) with the host-matched credential.
Proposed fix
Switch from host-keyed to per-resolver-keyed credential matching, OR maintain a separate (host, resolverName) index so dropped-settings entries don't shadow each other via the host slot. Concrete design needs to decide:
Coursier-only support? The clean fix lives in authenticationByRepositoryId (per-repo.name). Ivy matches by host and can't carry two credentials for the same host with reliable precedence.
Per-resolver Entra cred generation? A fresh Entra token applied to N resolvers on the same host needs N Credentials instances each tagged with the right host (and ideally per-resolver Coursier auth entry).
Backward compat? Existing users relying on the host-keyed precedence (one settings entry → shared across same-host resolvers) shouldn't break.
Reasonable v1 scope: fix the Coursier path, document the Ivy limitation, leave the existing host-key behavior as the default fallback when settings entries are valid.
Current workarounds (documented in README "Limitations")
Consolidate to one PAT per host scoped to all your feeds (works with current code).
Set azureDevOpsValidateExistingCredentials := "never" to disable the probe — pre-0.0.10 "trust both, let fetch fail on the stale one" surfaces a clean 401 instead of the misleading "falling through" log.
Use legacy <org>.pkgs.visualstudio.com URLs so different orgs land on different hostnames (within-org multi-feed still collides).
Repro test (would land with the fix)
it should "generate per-resolver Entra creds when a sibling resolver has a valid settings entry" in {
// FeedA: valid settings entry; FeedB: stale settings entry (dropped by probe).// After buildCredentialsWithAccessToken: assert FeedB gets an Entra cred,// not silently inheriting FeedA's PAT.// After updateCoursierConf: authenticationByRepositoryId("FeedB") must use// the Entra token, not FeedA's PAT.
}
Severity
User-visible impact: build fails with 401 on the dropped feed; log says "falling through to Entra" so user thinks plugin handled it.
Real-world frequency: uncommon. Most ADO users have one consolidated PAT or no settings entries.
Summary
When
~/.m2/settings.xmlhas multiple<server>entries that resolve to the same host, and one PAT is valid while another is stale, dropping the stale entry doesn't produce an Entra fallback for that feed. The plugin'scredMapis keyed by host, so the still-valid entry occupies the host slot and the dropped feed inherits its credentials. Fetches against the dropped feed will 401 because the valid PAT isn't scoped to it.Found during PR #6 self-review (round 4) — pre-existing latent bug that v0.0.10's probe-then-drop behavior makes user-visible with a misleading "falling through to Entra token acquisition" log.
Reproducer
~/.m2/settings.xml:build.sbt:sbt update:Credentials("realm", "pkgs.dev.azure.com", "azure", VALID-pat)buildCredentialsFromMavenSettingsreturnsNonefor FeedB[info] pkgs.dev.azure.com: settings.xml credentials returned 401; falling through to Entra token acquisition.← misleadingBut:
buildCredentialsWithAccessToken:credMap = {pkgs.dev.azure.com -> FeedA cred}; for FeedB resolver,credMap.contains(host)is true → no Entra cred generatedupdateCoursierConf: looks up FeedB resolver's host → finds FeedA's cred → registersAuthentication(azure, VALID-pat)against resolver idFeedBWhy the bug is pre-existing, not introduced by 0.0.10
Even on 0.0.9 with the same shape (FeedA settings entry, no FeedB entry),
credMap = {host -> FeedA cred}and FeedB ends up with FeedA's PAT applied. The host-keying is sbt/Ivy's native credential matching model —Credentialscarries a host, not a resolver name. The plugin'scredMapreflects that.0.0.10 didn't introduce the bug; it just made the user-visible failure mode worse by adding a misleading "falling through to Entra" log over the same broken substrate.
Affected code paths
AzureDevOpsCredentialsPlugin.scalain master:buildCredentialsWithAccessToken:updateCoursierConf— samecredMapkeying and samecredMap.get(host)lookup, then registersrepo.name -> Authentication(...)with the host-matched credential.Proposed fix
Switch from host-keyed to per-resolver-keyed credential matching, OR maintain a separate
(host, resolverName)index so dropped-settings entries don't shadow each other via the host slot. Concrete design needs to decide:authenticationByRepositoryId(per-repo.name). Ivy matches by host and can't carry two credentials for the same host with reliable precedence.Credentialsinstances each tagged with the right host (and ideally per-resolver Coursier auth entry).Reasonable v1 scope: fix the Coursier path, document the Ivy limitation, leave the existing host-key behavior as the default fallback when settings entries are valid.
Current workarounds (documented in README "Limitations")
azureDevOpsValidateExistingCredentials := "never"to disable the probe — pre-0.0.10 "trust both, let fetch fail on the stale one" surfaces a clean 401 instead of the misleading "falling through" log.<org>.pkgs.visualstudio.comURLs so different orgs land on different hostnames (within-org multi-feed still collides).Repro test (would land with the fix)
Severity