mirror of
https://github.com/renovatebot/renovate.git
synced 2026-10-27 07:50:47 +00:00
4732da7d2b
As part of changes we've recently made to the merging of
`hostRules[].headers`, it highlighted that a repository - or a preset
it extends - could lead to a header an admin has configured for one host
to be sent to another.
This happened due to the way we additively merged i.e.
`Object.assign({}, ...matchedHeaders)`.
This doesn't require the repository to know a credential is being
set (for a specific host), or to act maliciously - any header of their
own for that host is enough to bring the administrator's masked one
back, with nothing logged or failing to show it happened.
To fix this, we can:
- apply admin headers last, as they are "trusted"
- stop repositories from being able to unset an admin-set header
- retain the existing logic for the "most specific rule" being used
- make sure that `trusted` isn't a field that could be injected by a
repository, and make sure we explicitly only set it where we know
we're using self-hosted administrator config
- make `filterAllowedHeaders` drop `headers` if every header name was
denied, so `{}` isn't returned and used to suppress headers when
merged
Co-authored-by: Claude Opus 5 <jamie.tanna+claude-code@mend.io>
119 lines
4.8 KiB
TypeScript
119 lines
4.8 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, warnOnDenied = true): void {
|
|
if (config.hostRules) {
|
|
logger.debug('Setting global hostRules');
|
|
applySecretsAndVariablesToConfig({
|
|
config,
|
|
deleteVariables: false,
|
|
deleteSecrets: false,
|
|
});
|
|
// filtered here, rather than left to `add()`, so that the WARN about any dropped header can be suppressed when the same rules are registered again
|
|
// `config.hostRules` is deliberately left as it is: a `repositories[]` entry can widen `allowedHeaders` for its own repository, and `start()` re-filters these rules with the entry's allowlist to honour that
|
|
const rules = hostRules.filterAllowedHeaders(
|
|
config.hostRules,
|
|
config.allowedHeaders,
|
|
warnOnDenied,
|
|
);
|
|
for (const rule of rules) {
|
|
// already filtered above, so `add()`'s own enforcement has nothing left to drop
|
|
// 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
|
|
hostRules.add(rule, {
|
|
allowedHeaders: config.allowedHeaders,
|
|
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; anything `allowedHeaders` drops was already warned about by the call above
|
|
setGlobalHostRules(config, false);
|
|
configureThirdPartyLibraries(config);
|
|
await initMergeConfidence(config);
|
|
return config;
|
|
}
|
|
|
|
export async function globalFinalize(config: RenovateConfig): Promise<void> {
|
|
await packageCache.cleanup(config);
|
|
resetGlobalLogLevelRemaps();
|
|
}
|