mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-27 20:24:21 +00:00
🥫 fix: Refuse to Boot on Retired JWT Secret Defaults (#15282)
* 🛡️ fix: Reject Retired JWT Defaults * 🛡️ fix: Reject Legacy Temporary JWT Secrets
This commit is contained in:
parent
8b1fcc0fc2
commit
9423ad47cc
2 changed files with 58 additions and 2 deletions
|
|
@ -122,6 +122,46 @@ describe('credentials', () => {
|
|||
expect(state.generated).toEqual(['CREDS_KEY', 'CREDS_IV']);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['JWT_SECRET', '16f8c0ef4a5d391b26034086c628469d3f9f497f08163ab9b40137092f2909ef'],
|
||||
['JWT_REFRESH_SECRET', 'eaa5191f2914e30b9387fd84e254e4ba6fc51b4654968a9b0803b456a54b8418'],
|
||||
] as const)('rejects retired default %s values', (name, value) => {
|
||||
process.env[name] = value;
|
||||
|
||||
expect(() => bootstrapCredentials()).toThrow(
|
||||
`[credentials] ${name} uses a retired default value. Configure a unique replacement before starting LibreChat.`,
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['JWT_SECRET', '16f8c0ef4a5d391b26034086c628469d3f9f497f08163ab9b40137092f2909ef'],
|
||||
['JWT_REFRESH_SECRET', 'eaa5191f2914e30b9387fd84e254e4ba6fc51b4654968a9b0803b456a54b8418'],
|
||||
] as const)('rejects retired default %s values from temporary credentials', (name, value) => {
|
||||
fs.writeFileSync(tempFile, `${name}=${value}\n`);
|
||||
|
||||
expect(() => bootstrapCredentials()).toThrow(
|
||||
`[credentials] ${name} uses a retired default value. Configure a unique replacement before starting LibreChat.`,
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects a retired default adopted from a concurrent temporary credential write', () => {
|
||||
fs.writeFileSync(tempFile, `CREDS_KEY=${'a'.repeat(64)}\nCREDS_IV=${'b'.repeat(32)}\n`);
|
||||
fs.writeFileSync(
|
||||
`${tempFile}.lock`,
|
||||
[
|
||||
`CREDS_KEY=${'a'.repeat(64)}`,
|
||||
`CREDS_IV=${'b'.repeat(32)}`,
|
||||
'JWT_SECRET=16f8c0ef4a5d391b26034086c628469d3f9f497f08163ab9b40137092f2909ef',
|
||||
`JWT_REFRESH_SECRET=${'c'.repeat(64)}`,
|
||||
].join('\n'),
|
||||
{ mode: 0o600 },
|
||||
);
|
||||
|
||||
expect(() => bootstrapCredentials()).toThrow(
|
||||
'[credentials] JWT_SECRET uses a retired default value. Configure a unique replacement before starting LibreChat.',
|
||||
);
|
||||
});
|
||||
|
||||
it('does not overwrite an explicitly selected environment file', () => {
|
||||
const environmentFile = path.join(tempDirectory, '.env');
|
||||
process.env.LIBRECHAT_TEMP_CREDENTIALS_PATH = environmentFile;
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export const credentialNames = [
|
|||
|
||||
export type CredentialName = (typeof credentialNames)[number];
|
||||
|
||||
export type CredentialSource = 'environment' | 'temporary' | 'legacy-default';
|
||||
export type CredentialSource = 'environment' | 'temporary';
|
||||
|
||||
export interface CredentialRuntimeState {
|
||||
filePath: string;
|
||||
|
|
@ -56,6 +56,18 @@ const legacyCredentialFingerprints: Partial<Record<CredentialName, string[]>> =
|
|||
JWT_REFRESH_SECRET: ['282ad5f60261639fefed381976b4d0dde52eab5527a1ab2ec75d5be1efa1165b'],
|
||||
};
|
||||
|
||||
const legacyJwtCredentialNames = new Set<CredentialName>(['JWT_SECRET', 'JWT_REFRESH_SECRET']);
|
||||
|
||||
function rejectLegacyJwtCredential(name: CredentialName, value: string): void {
|
||||
if (!legacyJwtCredentialNames.has(name) || !isLegacyCredential(name, value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`[credentials] ${name} uses a retired default value. Configure a unique replacement before starting LibreChat.`,
|
||||
);
|
||||
}
|
||||
|
||||
function getRuntimeState(): CredentialRuntimeState | undefined {
|
||||
const runtime = globalThis as typeof globalThis &
|
||||
Record<symbol, CredentialRuntimeState | undefined>;
|
||||
|
|
@ -248,6 +260,8 @@ function adoptCredentialFile(
|
|||
if (!isUsableTemporaryCredential(name, value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
rejectLegacyJwtCredential(name, value);
|
||||
}
|
||||
|
||||
for (const name of names) {
|
||||
|
|
@ -276,13 +290,15 @@ export function bootstrapCredentials(): CredentialRuntimeState {
|
|||
for (const name of credentialNames) {
|
||||
const environmentValue = process.env[name];
|
||||
if (isConfiguredCredential(environmentValue)) {
|
||||
sources[name] = isLegacyCredential(name, environmentValue) ? 'legacy-default' : 'environment';
|
||||
rejectLegacyJwtCredential(name, environmentValue);
|
||||
sources[name] = 'environment';
|
||||
continue;
|
||||
}
|
||||
|
||||
missingFromEnvironment.push(name);
|
||||
const fileValue = file.values[name];
|
||||
if (isUsableTemporaryCredential(name, fileValue)) {
|
||||
rejectLegacyJwtCredential(name, fileValue);
|
||||
process.env[name] = fileValue;
|
||||
sources[name] = 'temporary';
|
||||
loadedFromFile.push(name);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue