From ef82cb5d0007125aa72144fe6ede85e5a318f4ba Mon Sep 17 00:00:00 2001 From: neon <18559658+neonsolstice@users.noreply.github.com> Date: Tue, 7 Apr 2026 07:30:07 -0600 Subject: [PATCH] fix: patch Trivy CRITICAL/HIGH findings in container image - apk upgrade in runtime stage to fix OpenSSL CVEs (CVE-2025-15467 CRITICAL, CVE-2025-69419 HIGH) - remove npm from runtime image to eliminate npm-bundled CVEs - update fastify to 5.8.5 and force via pnpm override (CVE-2026-33806 HIGH) - switch vitest pool to threads to fix flaky audio extractor tests - simplify unit test setup with direct instantiation --- Dockerfile | 3 ++ package.json | 3 ++ server/package.json | 6 +-- .../email/email-provider.service.test.ts | 54 ++++++++----------- .../email/email-transport.service.test.ts | 10 ++-- .../goodreads/goodreads.provider.test.ts | 21 ++------ .../open-library.provider.test.ts | 21 ++------ server/vitest.config.ts | 2 +- 8 files changed, 45 insertions(+), 75 deletions(-) diff --git a/Dockerfile b/Dockerfile index 59263e4b7..308c7dd63 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,6 +37,9 @@ RUN mkdir -p /deploy/migrations && cp -r /app/server/src/db/migrations/. /deploy FROM ${NODE_IMAGE} AS runtime WORKDIR /app +RUN apk upgrade --no-cache && \ + rm -rf /usr/local/lib/node_modules/npm /usr/local/bin/npm /usr/local/bin/npx + ENV NODE_ENV=production ENV PORT=3000 diff --git a/package.json b/package.json index cba79adbc..b8a078772 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,9 @@ ] }, "pnpm": { + "overrides": { + "fastify": "^5.8.5" + }, "onlyBuiltDependencies": [ "@nestjs/core", "@parcel/watcher", diff --git a/server/package.json b/server/package.json index e25611c7b..d312f4c99 100644 --- a/server/package.json +++ b/server/package.json @@ -39,7 +39,7 @@ "@fastify/helmet": "^13.0.2", "@fastify/multipart": "^9.4.0", "@fastify/rate-limit": "^10.3.0", - "@fastify/static": "^9.0.0", + "@fastify/static": "^9.1.0", "@nestjs/common": "^11.1.18", "@nestjs/config": "^4.0.3", "@nestjs/core": "^11.1.18", @@ -89,11 +89,11 @@ "@nestjs/schematics": "^11.0.10", "@nestjs/testing": "^11.1.18", "@swc/cli": "^0.8.1", - "@swc/core": "^1.15.21", + "@swc/core": "^1.15.24", "@types/bcrypt": "^6.0.0", "@types/express": "^5.0.6", "@types/node": "^25.5.2", - "@types/nodemailer": "^6.4.23", + "@types/nodemailer": "^8.0.0", "@types/passport-jwt": "^4.0.1", "@types/pg": "^8.18.0", "@types/supertest": "^7.2.0", diff --git a/server/src/modules/email/email-provider.service.test.ts b/server/src/modules/email/email-provider.service.test.ts index 3957acfd8..1e950d421 100644 --- a/server/src/modules/email/email-provider.service.test.ts +++ b/server/src/modules/email/email-provider.service.test.ts @@ -1,5 +1,4 @@ import { ConflictException, ForbiddenException, NotFoundException } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; import { EmailProviderService } from './email-provider.service'; import { EmailProviderRepository } from './email-provider.repository'; import { EmailEncryptionService } from './email-encryption.service'; @@ -46,38 +45,29 @@ describe('EmailProviderService', () => { startTls: true, }; - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - EmailProviderService, - { - provide: EmailProviderRepository, - useValue: { - findAllForUser: vi.fn().mockResolvedValue([mockProvider]), - findById: vi.fn().mockResolvedValue([mockProvider]), - insert: vi.fn().mockResolvedValue([mockProvider]), - update: vi.fn().mockResolvedValue([mockProvider]), - delete: vi.fn(), - clearDefault: vi.fn(), - setDefault: vi.fn().mockResolvedValue([mockProvider]), - setSharedByOwner: vi.fn().mockResolvedValue([{ ...mockProvider, isShared: true }]), - }, - }, - { - provide: EmailEncryptionService, - useValue: { encrypt: vi.fn().mockReturnValue('encrypted'), decrypt: vi.fn().mockReturnValue('decrypted') }, - }, - { - provide: EmailTransportService, - useValue: { buildTransporter: vi.fn(), verifyTransporter: vi.fn() }, - }, - ], - }).compile(); + beforeEach(() => { + repo = { + findAllForUser: vi.fn().mockResolvedValue([mockProvider]), + findById: vi.fn().mockResolvedValue([mockProvider]), + insert: vi.fn().mockResolvedValue([mockProvider]), + update: vi.fn().mockResolvedValue([mockProvider]), + delete: vi.fn(), + clearDefault: vi.fn(), + setDefault: vi.fn().mockResolvedValue([mockProvider]), + setSharedByOwner: vi.fn().mockResolvedValue([{ ...mockProvider, isShared: true }]), + } as unknown as EmailProviderRepository; - service = module.get(EmailProviderService); - repo = module.get(EmailProviderRepository); - encryption = module.get(EmailEncryptionService); - transport = module.get(EmailTransportService); + encryption = { + encrypt: vi.fn().mockReturnValue('encrypted'), + decrypt: vi.fn().mockReturnValue('decrypted'), + } as unknown as EmailEncryptionService; + + transport = { + buildTransporter: vi.fn(), + verifyTransporter: vi.fn(), + } as unknown as EmailTransportService; + + service = new EmailProviderService(repo, encryption, transport); }); describe('findAll', () => { diff --git a/server/src/modules/email/email-transport.service.test.ts b/server/src/modules/email/email-transport.service.test.ts index 44012f84c..de29f936e 100644 --- a/server/src/modules/email/email-transport.service.test.ts +++ b/server/src/modules/email/email-transport.service.test.ts @@ -1,4 +1,3 @@ -import { Test, TestingModule } from '@nestjs/testing'; import * as nodemailer from 'nodemailer'; import { EmailTransportService } from './email-transport.service'; @@ -7,12 +6,9 @@ vi.mock('nodemailer'); describe('EmailTransportService', () => { let service: EmailTransportService; - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [EmailTransportService], - }).compile(); - - service = module.get(EmailTransportService); + beforeEach(() => { + vi.clearAllMocks(); + service = new EmailTransportService(); }); it('should build transporter with correct config (Port 587)', () => { diff --git a/server/src/modules/metadata-fetch/providers/goodreads/goodreads.provider.test.ts b/server/src/modules/metadata-fetch/providers/goodreads/goodreads.provider.test.ts index 595ade1ad..da873666a 100644 --- a/server/src/modules/metadata-fetch/providers/goodreads/goodreads.provider.test.ts +++ b/server/src/modules/metadata-fetch/providers/goodreads/goodreads.provider.test.ts @@ -1,4 +1,3 @@ -import { Test, TestingModule } from '@nestjs/testing'; import { ProviderConfigurations } from '@projectx/types'; import { ProviderConfigService } from '../../../metadata-preferences/provider-config.service'; @@ -21,21 +20,11 @@ describe('GoodreadsProvider', () => { comicvine: { enabled: false, apiKey: '' }, }; - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - GoodreadsProvider, - { - provide: ProviderConfigService, - useValue: { - getConfig: vi.fn().mockResolvedValue(mockConfig), - }, - }, - ], - }).compile(); - - provider = module.get(GoodreadsProvider); - providerConfig = module.get(ProviderConfigService); + beforeEach(() => { + providerConfig = { + getConfig: vi.fn().mockResolvedValue(mockConfig), + } as unknown as ProviderConfigService; + provider = new GoodreadsProvider(providerConfig); }); afterEach(() => { diff --git a/server/src/modules/metadata-fetch/providers/open-library/open-library.provider.test.ts b/server/src/modules/metadata-fetch/providers/open-library/open-library.provider.test.ts index c8f5d6a46..5cd072d8a 100644 --- a/server/src/modules/metadata-fetch/providers/open-library/open-library.provider.test.ts +++ b/server/src/modules/metadata-fetch/providers/open-library/open-library.provider.test.ts @@ -1,4 +1,3 @@ -import { Test, TestingModule } from '@nestjs/testing'; import { ProviderConfigurations } from '@projectx/types'; import { ProviderConfigService } from '../../../metadata-preferences/provider-config.service'; @@ -20,21 +19,11 @@ describe('OpenLibraryProvider', () => { comicvine: { enabled: false, apiKey: '' }, }; - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [ - OpenLibraryProvider, - { - provide: ProviderConfigService, - useValue: { - getConfig: vi.fn().mockResolvedValue(mockConfig), - }, - }, - ], - }).compile(); - - provider = module.get(OpenLibraryProvider); - providerConfig = module.get(ProviderConfigService); + beforeEach(() => { + providerConfig = { + getConfig: vi.fn().mockResolvedValue(mockConfig), + } as unknown as ProviderConfigService; + provider = new OpenLibraryProvider(providerConfig); }); afterEach(() => { diff --git a/server/vitest.config.ts b/server/vitest.config.ts index c4d1d8fea..a68a66460 100644 --- a/server/vitest.config.ts +++ b/server/vitest.config.ts @@ -11,7 +11,7 @@ export default defineConfig({ test: { globals: true, environment: 'node', - pool: 'forks', + pool: 'threads', include: ['src/**/*.test.ts', 'test/**/*.test.ts'], passWithNoTests: true, reporters: process.env.CI ? ['default', 'github-actions'] : ['default'],