From d64427898e5308dc4cea5fd48d4c1ad7139f9fbf Mon Sep 17 00:00:00 2001 From: jmgasper Date: Tue, 1 Sep 2026 08:49:18 +1000 Subject: [PATCH] security: remediate September 2026 findings --- .nvmrc | 2 +- Dockerfile | 17 +++++++++++++---- README.md | 10 ++++++++-- package.json | 2 +- pnpm-lock.yaml | 31 ++++++++++++++++++------------- pnpm-workspace.yaml | 4 ++++ test/app-startup.spec.ts | 7 +++++++ 7 files changed, 52 insertions(+), 21 deletions(-) diff --git a/.nvmrc b/.nvmrc index f9e7451..0a2925f 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -22.23.1 +26.5.1 diff --git a/Dockerfile b/Dockerfile index 107a153..7622e83 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,9 @@ # syntax=docker/dockerfile:1.7 -ARG NODE_VERSION=22.23.1 +ARG NODE_VERSION=26.5.1 +ARG ALPINE_VERSION=3.24 +ARG NODE_PACKAGE_VERSION=26.5.1-r0 +ARG OPENSSL_PACKAGE_VERSION=3.5.8-r0 ARG PNPM_VERSION=11.20.0 ARG PRISMA_VERSION=7.9.1 @@ -30,10 +33,16 @@ COPY prisma.config.ts ./ RUN pnpm install --prod --frozen-lockfile \ && SUPPORT_DATABASE_URL="postgresql://user:pass@localhost:5432/topcoder?schema=support" prisma generate -FROM node:${NODE_VERSION}-alpine AS production +FROM alpine:${ALPINE_VERSION} AS production +ARG NODE_PACKAGE_VERSION +ARG OPENSSL_PACKAGE_VERSION RUN apk upgrade --no-cache \ - && rm -rf /usr/local/lib/node_modules/npm \ - && rm -f /usr/local/bin/npm /usr/local/bin/npx + && apk add --no-cache \ + "libcrypto3=${OPENSSL_PACKAGE_VERSION}" \ + "libssl3=${OPENSSL_PACKAGE_VERSION}" \ + "nodejs-current=${NODE_PACKAGE_VERSION}" \ + && addgroup -S node \ + && adduser -S -G node node ENV NODE_ENV=production WORKDIR /usr/src/app COPY --from=build --chown=node:node /usr/src/app/dist ./dist diff --git a/README.md b/README.md index 394267d..442c482 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,10 @@ successful ticket response. Logs exclude markdown, addresses, and secrets. ## Local development +Node.js 26.5.1 is the supported development and production runtime. Run +`nvm use` in this project before invoking pnpm so the local toolchain matches +the container runtime. + ```bash nvm use cp .env.example .env @@ -209,8 +213,10 @@ shared Topcoder deployment suite with `APPNAME=support-api-v6` and platform The pipeline deploys to an existing ECS service; it intentionally does not provision infrastructure. Before the first deployment, operations must provide the ECR repository, ECS service/task family, target group, database and secret, -and SSM values. Container startup applies pending Prisma migrations before the -API process starts; Prisma's advisory lock keeps concurrent ECS task starts +and SSM values. The production image installs Alpine 3.24's dynamically linked +Node.js 26.5.1 package and OpenSSL 3.5.8 libraries, while npm and pnpm remain in +build-only stages. Container startup applies pending Prisma migrations before +the API process starts; Prisma's advisory lock keeps concurrent ECS task starts safe. API Gateway must map `/v6/support` to the target group. Serving Platform UI at `support.topcoder.com` is a separate infrastructure diff --git a/package.json b/package.json index efbe814..e298ff7 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "description": "Topcoder member support ticket API", "packageManager": "pnpm@11.20.0", "engines": { - "node": ">=22.23.1" + "node": ">=26.5.1" }, "scripts": { "build": "nest build", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7b2612c..86c2a30 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,7 +5,11 @@ settings: excludeLinksFromLockfile: false overrides: + '@istanbuljs/load-nyc-config>js-yaml': 3.15.1 + '@eslint/eslintrc>js-yaml': 4.3.1 + cosmiconfig>js-yaml: 4.3.1 '@nestjs/swagger>js-yaml': 5.2.3 + '@prisma/config>deepmerge-ts': 8.0.0 axios: 1.18.1 follow-redirects: 1.16.0 form-data: 4.0.6 @@ -1750,8 +1754,8 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - deepmerge-ts@7.1.5: - resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} + deepmerge-ts@8.0.0: + resolution: {integrity: sha512-ICNjaP0ML+eSdEpJYQC46XiAn/UjAdwbEl0dE8p85ZTeNDinN4Kd4+9jS4OSAuH7st6eC7rQhsqTF5zIDaUm2g==} engines: {node: '>=16.0.0'} deepmerge@4.3.1: @@ -1939,6 +1943,7 @@ packages: eslint@9.39.5: resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true peerDependencies: jiti: '*' @@ -2507,12 +2512,12 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-yaml@3.15.0: - resolution: {integrity: sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==} + js-yaml@3.15.1: + resolution: {integrity: sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==} hasBin: true - js-yaml@4.3.0: - resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} + js-yaml@4.3.1: + resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} hasBin: true js-yaml@5.2.3: @@ -3978,7 +3983,7 @@ snapshots: globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.3.0 + js-yaml: 4.3.1 minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -4170,7 +4175,7 @@ snapshots: camelcase: 5.3.1 find-up: 4.1.0 get-package-type: 0.1.0 - js-yaml: 3.15.0 + js-yaml: 3.15.1 resolve-from: 5.0.0 '@istanbuljs/schema@0.1.6': {} @@ -4539,7 +4544,7 @@ snapshots: '@prisma/config@7.9.1': dependencies: c12: 3.3.4 - deepmerge-ts: 7.1.5 + deepmerge-ts: 8.0.0 effect: 3.20.0 empathic: 2.0.0 transitivePeerDependencies: @@ -5526,7 +5531,7 @@ snapshots: cosmiconfig@8.3.6(typescript@5.9.3): dependencies: import-fresh: 3.3.1 - js-yaml: 4.3.0 + js-yaml: 4.3.1 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: @@ -5618,7 +5623,7 @@ snapshots: deep-is@0.1.4: {} - deepmerge-ts@7.1.5: {} + deepmerge-ts@8.0.0: {} deepmerge@4.3.1: {} @@ -6578,12 +6583,12 @@ snapshots: js-tokens@4.0.0: {} - js-yaml@3.15.0: + js-yaml@3.15.1: dependencies: argparse: 1.0.10 esprima: 4.0.1 - js-yaml@4.3.0: + js-yaml@4.3.1: dependencies: argparse: 2.0.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index de982fa..695bf7e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -13,7 +13,11 @@ peerDependencyRules: - react-dom overrides: + '@istanbuljs/load-nyc-config>js-yaml': 3.15.1 + '@eslint/eslintrc>js-yaml': 4.3.1 + 'cosmiconfig>js-yaml': 4.3.1 '@nestjs/swagger>js-yaml': 5.2.3 + '@prisma/config>deepmerge-ts': 8.0.0 axios: 1.18.1 follow-redirects: 1.16.0 form-data: 4.0.6 diff --git a/test/app-startup.spec.ts b/test/app-startup.spec.ts index 5ebf9b7..7c56128 100644 --- a/test/app-startup.spec.ts +++ b/test/app-startup.spec.ts @@ -55,6 +55,13 @@ describe('production startup', () => { it('uses the migration-aware startup script in the production image', () => { const dockerfile = readFileSync(join(projectRoot, 'Dockerfile'), 'utf8'); + expect(dockerfile).toContain('ARG ALPINE_VERSION=3.24'); + expect(dockerfile).toContain('ARG NODE_PACKAGE_VERSION=26.5.1-r0'); + expect(dockerfile).toContain('ARG OPENSSL_PACKAGE_VERSION=3.5.8-r0'); + expect(dockerfile).toContain('FROM alpine:${ALPINE_VERSION} AS production'); + expect(dockerfile).toContain('"nodejs-current=${NODE_PACKAGE_VERSION}"'); + expect(dockerfile).toContain('"libcrypto3=${OPENSSL_PACKAGE_VERSION}"'); + expect(dockerfile).toContain('"libssl3=${OPENSSL_PACKAGE_VERSION}"'); expect(dockerfile).toContain( 'COPY --from=build --chown=node:node --chmod=755 /usr/src/app/appStartUp.sh ./appStartUp.sh', );