feat: add support for helmet configuration (#27058)

This commit is contained in:
Jason Rasmussen
2026-03-26 13:41:23 -04:00
committed by GitHub
parent dbaf4b548b
commit b33874ef12
9 changed files with 96 additions and 18 deletions
+21
View File
@@ -0,0 +1,21 @@
{
"contentSecurityPolicy": {
"directives": {
"default-src": ["'self'"],
"script-src": ["'self'", "'wasm-unsafe-eval", "'unsafe-inline'", "https://www.gstatic.com"],
"style-src": ["'self'", "'unsafe-inline'"],
"img-src": ["'self'", "'data:'", "'blob:'"],
"connect-src": [
"'self'",
"blob:",
"https://pay.futo.org",
"https://static.immich.cloud",
"https://tiles.immich.cloud"
],
"worker-src": ["'self'", "blob:"],
"frame-src": ["'none'"],
"object-src": ["'none'"],
"base-uri": ["'self'"]
}
}
}
+3 -1
View File
@@ -7,7 +7,8 @@
"license": "GNU Affero General Public License version 3",
"files": [
"bin",
"dist"
"dist",
"helmet.json"
],
"scripts": {
"build": "nest build",
@@ -81,6 +82,7 @@
"fluent-ffmpeg": "^2.1.2",
"geo-tz": "^8.0.0",
"handlebars": "^4.7.8",
"helmet": "^8.1.0",
"i18n-iso-countries": "^7.6.0",
"ioredis": "^5.8.2",
"jose": "^5.10.0",
+8 -1
View File
@@ -2,6 +2,7 @@ import { NestExpressApplication } from '@nestjs/platform-express';
import { json } from 'body-parser';
import compression from 'compression';
import cookieParser from 'cookie-parser';
import helmetMiddleware from 'helmet';
import { existsSync } from 'node:fs';
import sirv from 'sirv';
import { IMMICH_SERVER_START, excludePaths, serverVersion } from 'src/constants';
@@ -39,7 +40,7 @@ export async function configureExpress(
},
) {
const configRepository = app.get(ConfigRepository);
const { environment, host, port, resourcePaths, network } = configRepository.getEnv();
const { environment, host, port, helmet, resourcePaths, network } = configRepository.getEnv();
const logger = await app.resolve(LoggingRepository);
logger.setContext('Bootstrap');
@@ -47,6 +48,12 @@ export async function configureExpress(
app.set('trust proxy', ['loopback', ...network.trustedProxies]);
app.set('etag', 'strong');
if (helmet.config) {
app.use(helmetMiddleware(helmet.config));
logger.log('Initialized helmet middleware');
}
app.use(cookieParser());
app.use(json({ limit: '10mb' }));
+4
View File
@@ -42,6 +42,10 @@ export class EnvDto {
@Optional()
IMMICH_CONFIG_FILE?: string;
@IsString()
@Optional()
IMMICH_HELMET_FILE?: string;
@IsEnum(ImmichEnvironment)
@Optional()
IMMICH_ENV?: ImmichEnvironment;
@@ -5,9 +5,11 @@ import { QueueOptions } from 'bullmq';
import { plainToInstance } from 'class-transformer';
import { validateSync } from 'class-validator';
import { Request, Response } from 'express';
import { HelmetOptions } from 'helmet';
import { RedisOptions } from 'ioredis';
import { CLS_ID, ClsModuleOptions } from 'nestjs-cls';
import { OpenTelemetryModuleOptions } from 'nestjs-otel/lib/interfaces';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { citiesFile, excludePaths, IWorker } from 'src/constants';
import { Telemetry } from 'src/decorators';
@@ -58,6 +60,10 @@ export interface EnvData {
config: ClsModuleOptions;
};
helmet: {
config?: HelmetOptions;
};
database: {
config: DatabaseConnectionParams;
skipMigrations: boolean;
@@ -143,6 +149,25 @@ const asSet = <T>(value: string | undefined, defaults: T[]) => {
return new Set(values.length === 0 ? defaults : (values as T[]));
};
const resolveHelmetFile = (helmetFile: 'true' | 'false' | string | undefined) => {
// default is off
if (!helmetFile || helmetFile === 'false') {
return;
}
helmetFile =
helmetFile === 'true'
? // eslint-disable-next-line unicorn/prefer-module
join(__dirname, '..', '..', 'helmet.json')
: helmetFile;
try {
return JSON.parse(readFileSync(helmetFile).toString()) as HelmetOptions;
} catch (error) {
throw new Error(`Failed to read helmet file: ${helmetFile}`, { cause: error });
}
};
const getEnv = (): EnvData => {
const dto = plainToInstance(EnvDto, process.env);
const errors = validateSync(dto);
@@ -289,6 +314,10 @@ const getEnv = (): EnvData => {
vectorExtension,
},
helmet: {
config: resolveHelmetFile(dto.IMMICH_HELMET_FILE),
},
licensePublicKey: isProd ? productionKeys : stagingKeys,
network: {
@@ -35,6 +35,10 @@ const envData: EnvData = {
vectorExtension: DatabaseExtension.Vectors,
},
helmet: {
config: {},
},
licensePublicKey: {
client: 'client-public-key',
server: 'server-public-key',