Files
renovate/lib/util/sanitize.ts
Jamie Tanna 54384ff3e5 fix(util/host-rules): sanitise TLS credential fields in hostRules (#45119)
* chore(util): note that `httpsCertificate` and `httpsCertificateAuthority` aren't secret

Although we're currently treating them as such, they're not technically
unsafe to appear in the logs, as a Certificate (and the authority) are
public.

* fix(util/host-rules): sanitise TLS credential fields in hostRules

We previously noted that the `httpsPrivateKey`, `httpsCertificate`, and
`httpsCertificateAuthority` fields would be marked as confidential, but
we weren't correctly sanitising them.

We can make sure that these fields are correctly flagged as secret
("confidential") and that they need to be sanitised when referenced.

Although the `httpsCertificate` and `httpsCertificateAuthority` aren't
secret, we can keep them sanitised for consistency with how we've
currently documented them as being sanitised.

Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>

* test(util/host-rules): ensure that all secret fields are marked as secret

As a way to make sure that new fields being added `HostRule`s are
considered as to whether the field should be marked as secret
("confidential") or not, we can add a test (and a compile-time
exhaustive check) to validate that `redactedFields` and
`confidentialFields` are kept in sync.

Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>

---------

Co-authored-by: Claude Sonnet 5 <jamie.tanna+claude-code@mend.io>
2026-08-06 15:15:54 +00:00

73 lines
1.8 KiB
TypeScript

import { isNonEmptyString } from '@sindresorhus/is';
import { toBase64 } from './string.ts';
const globalSecrets = new Set<string>();
const repoSecrets = new Set<string>();
export const redactedFields = [
'authorization',
'token',
'githubAppKey',
'npmToken',
'npmrc',
'privateKey',
'privateKeyOld',
'gitPrivateKey',
'forkToken',
'password',
/* not actually sensitive, but redacted nonetheless */
'httpsCertificate',
'httpsPrivateKey',
/* not actually sensitive, but redacted nonetheless */
'httpsCertificateAuthority',
];
// TODO: returns null or undefined only when input is null or undefined.
export function sanitize(input: string): string;
export function sanitize(
input: string | null | undefined,
): string | null | undefined;
export function sanitize(
input: string | null | undefined,
): string | null | undefined {
if (!input) {
return input;
}
let output: string = input;
[globalSecrets, repoSecrets].forEach((secrets) => {
secrets.forEach((secret) => {
while (output.includes(secret)) {
output = output.replace(secret, '**redacted**');
}
});
});
return output;
}
const GITHUB_APP_TOKEN_PREFIX = 'x-access-token:';
export function addSecretForSanitizing(
secret: string | undefined,
type = 'repo',
): void {
if (!isNonEmptyString(secret)) {
return;
}
const secrets = type === 'repo' ? repoSecrets : globalSecrets;
secrets.add(secret);
secrets.add(toBase64(secret));
if (secret.startsWith(GITHUB_APP_TOKEN_PREFIX)) {
const trimmedSecret = secret.replace(GITHUB_APP_TOKEN_PREFIX, '');
secrets.add(trimmedSecret);
secrets.add(toBase64(trimmedSecret));
}
}
export function clearRepoSanitizedSecretsList(): void {
repoSecrets.clear();
}
export function clearGlobalSanitizedSecretsList(): void {
globalSecrets.clear();
}