Files
renovate/lib/modules/datasource/pypi/host-rules.ts
Sebastian Poxhofer 17466ceb32 fix(host-rules): ignore URL credentials when matching matchHost (#46128)
feat(host-rules): ignore URL credentials when matching matchHost

`matchesHost()` compared the raw href of the searched URL against
`matchHost`, so a URL carrying `user:pass@` userinfo (often a literal
placeholder such as `${USER}:${PASS}@`) never matched a `matchHost`
without credentials, including path-scoped ones. Callers worked around
this by stripping `username`/`password` off the URL by hand before
calling `hostRules.find()`.

- Add `stripUrlCredentials()` to `lib/util/url.ts`, returning the URL's
  `href` without userinfo (or `null` if unparseable), covered by
  `lib/util/url.spec.ts`.
- `matchesHost()` in `lib/util/host-rules.ts` now strips credentials
  from the searched URL before comparing it against an HTTP(S)
  `matchHost`, so `hostRules.find()` ignores userinfo on every caller's
  URL. Covered by new cases in `lib/util/host-rules.spec.ts` (a
  `matchHost` rule matches a URL carrying `user:pass@`, including a
  path-scoped `matchHost`).
- Remove the hand-rolled strip in
  `lib/modules/datasource/pypi/host-rules.ts`'s
  `findPypiIndexCredentials()`, now redundant since `find()` does it.
  `lib/modules/datasource/pypi/host-rules.spec.ts` already covers this
  behaviour and needed no changes.

This is strictly additive: more URLs match `matchHost` than before,
and nothing that matched before stops matching.
2026-09-18 10:53:12 +00:00

49 lines
1.5 KiB
TypeScript

import { logger } from '../../../logger/index.ts';
import { find } from '../../../util/host-rules.ts';
import { parseUrl } from '../../../util/url.ts';
import { getGoogleAuthHostRule, isGoogleArtifactRegistry } from '../util.ts';
import { pypiDatasourceId } from './common.ts';
import type { PypiIndexCredentials } from './types.ts';
/**
* Resolves the credentials for a Python package index URL.
*
* The URL's own `user:password@` part - which is often a placeholder such as
* `${USER}:${PASS}@` - does not stop it matching a `matchHost` without
* credentials, as `find()` ignores userinfo when matching. Google Artifact
* Registry is only asked for a token when no host rule supplies credentials,
* so an explicitly configured username/password always wins.
*/
export async function findPypiIndexCredentials(
indexUrl: string | undefined,
): Promise<PypiIndexCredentials> {
const parsedUrl = parseUrl(indexUrl);
if (!parsedUrl) {
logger.once.debug(`Failed to parse index URL ${indexUrl}`);
return {};
}
const { username, password } = find({
hostType: pypiDatasourceId,
url: parsedUrl.toString(),
});
if (username ?? password) {
return { username, password };
}
if (isGoogleArtifactRegistry(parsedUrl.hostname)) {
const googleHostRule = await getGoogleAuthHostRule();
if (googleHostRule) {
return {
username: googleHostRule.username,
password: googleHostRule.password,
};
}
logger.once.debug(
`Could not get Google access token (url=${parsedUrl.toString()})`,
);
}
return {};
}