Files
Jamie Tanna af055eba22 feat(config): allow self-hosted admins to bypass allowedHeaders (#45654)
In a similar way to how `allowedEnv` works, we should allow a
self-hosted admin to specify whichever headers they wish in their
`hostRules` - whether in their global config, a `repositories[]` entry,
or a preset a `repositories[]` entry extends.

Previously, this would require a self-hosted admin to add any header
they wished to set to `allowedHeaders`, which would also allow any of
their users' repositories to set that same header.

We can align this behaviour with `allowedEnv`, which makes sure that
the admin's own `headers` are always applied regardless of
`allowedHeaders`, while a repository's own `hostRules` - and those of
any preset it extends - remain constrained by it as before.

We make sure to use an explicit `[]` to make it clear when there are no
headers to allow, opposed to using something less clear like
`undefined`.

We do not currently allow an `extends` (without a `repositories[]`
entry) to bypass, and they'll be followed-up with in #45670.

Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
2026-09-29 10:37:15 +00:00

108 lines
4.3 KiB
TypeScript

import os from 'node:os';
import fs from 'fs-extra';
import upath from 'upath';
import { applySecretsAndVariablesToConfig } from '../../config/secrets.ts';
import type { AllConfig, RenovateConfig } from '../../config/types.ts';
import { logger } from '../../logger/index.ts';
import { resetGlobalLogLevelRemaps } from '../../logger/remap.ts';
import { initPlatform } from '../../modules/platform/index.ts';
import * as packageCache from '../../util/cache/package/index.ts';
import { setEmojiConfig } from '../../util/emoji.ts';
import { validateGitVersion } from '../../util/git/index.ts';
import * as hostRules from '../../util/host-rules.ts';
import { setHttpRateLimits } from '../../util/http/rate-limits.ts';
import { initMergeConfidence } from '../../util/merge-confidence/index.ts';
import { setMaxLimit } from './limits.ts';
async function setDirectories(input: AllConfig): Promise<AllConfig> {
const config: AllConfig = { ...input };
process.env.TMPDIR = process.env.RENOVATE_TMPDIR ?? os.tmpdir();
if (config.baseDir) {
logger.debug(`Using configured baseDir: ${config.baseDir}`);
} else {
config.baseDir = upath.join(process.env.TMPDIR, 'renovate');
logger.debug(`Using baseDir: ${config.baseDir}`);
}
await fs.ensureDir(config.baseDir);
if (config.cacheDir) {
logger.debug(`Using configured cacheDir: ${config.cacheDir}`);
} else {
config.cacheDir = upath.join(config.baseDir, 'cache');
logger.debug(`Using cacheDir: ${config.cacheDir}`);
}
await fs.ensureDir(config.cacheDir);
if (config.binarySource === 'docker' || config.binarySource === 'install') {
if (config.containerbaseDir) {
logger.debug(
`Using configured containerbaseDir: ${config.containerbaseDir}`,
);
} else {
config.containerbaseDir = upath.join(config.cacheDir, 'containerbase');
logger.debug(`Using containerbaseDir: ${config.containerbaseDir}`);
}
await fs.ensureDir(config.containerbaseDir);
}
return config;
}
function limitCommitsPerRun(config: RenovateConfig): void {
const limit = config.prCommitsPerRunLimit;
setMaxLimit('Commits', typeof limit === 'number' && limit > 0 ? limit : null);
}
async function checkVersions(): Promise<void> {
const validGitVersion = await validateGitVersion();
if (!validGitVersion) {
throw new Error('Init: git version needs upgrading');
}
}
function setGlobalHostRules(config: AllConfig): void {
if (config.hostRules) {
logger.debug('Setting global hostRules');
applySecretsAndVariablesToConfig({
config,
deleteVariables: false,
deleteSecrets: false,
});
for (const rule of config.hostRules) {
// the self-hosted admin's own rules: `trusted`, so that their `headers` are applied over any a repository or preset sets for the same host, and exempt from `allowedHeaders` altogether - `allowedHeaders` constrains what a repository or preset may set, not the administrator, mirroring how `allowedEnv` does not constrain the administrator's own `env`
hostRules.add(rule, { trusted: true });
}
}
}
function configureThirdPartyLibraries(config: AllConfig): void {
// Not using early return style to make clear what's the criterion to set the variables,
// especially when there is more stuff added here in the future.
if (!config.useCloudMetadataServices) {
logger.debug('Disabling the use of cloud metadata services');
process.env.AWS_EC2_METADATA_DISABLED = 'true'; // See https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-envvars.html#envvars-list
process.env.METADATA_SERVER_DETECTION = 'none'; // See https://cloud.google.com/nodejs/docs/reference/gcp-metadata/latest#environment-variables
}
}
export async function globalInitialize(
config_: AllConfig,
): Promise<RenovateConfig> {
let config = config_;
setHttpRateLimits();
await checkVersions();
setGlobalHostRules(config);
config = await initPlatform(config);
config = await setDirectories(config);
await packageCache.init(config);
limitCommitsPerRun(config);
setEmojiConfig(config);
// registered a second time in case initialization changed them
setGlobalHostRules(config);
configureThirdPartyLibraries(config);
await initMergeConfidence(config);
return config;
}
export async function globalFinalize(config: RenovateConfig): Promise<void> {
await packageCache.cleanup(config);
resetGlobalLogLevelRemaps();
}