diff --git a/.nvmrc b/.nvmrc index 3eb1386..0a2925f 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -26.5.0 +26.5.1 diff --git a/Dockerfile b/Dockerfile index ee15d77..e7b4eb4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1.7 -ARG NODE_VERSION=26.5.0 +ARG NODE_VERSION=26.5.1 ARG PNPM_VERSION=11.15.1 FROM node:${NODE_VERSION}-alpine AS external-clients @@ -37,11 +37,13 @@ RUN DATABASE_URL="postgresql://user:password@localhost:5432/forums" pnpm prisma: && rm -rf node_modules \ && pnpm install --prod --frozen-lockfile --ignore-scripts -FROM node:${NODE_VERSION}-alpine AS production +FROM alpine:3.24 AS production +ARG NODE_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 nodejs-current=${NODE_VERSION}-r0 \ + && addgroup -S app \ + && adduser -S -D -H -u 10001 -G app app # External Prisma clients are copied beside the application under /usr/src. # Expose the application's production dependencies to their CommonJS imports. @@ -49,16 +51,16 @@ ENV NODE_ENV=production \ NODE_PATH=/usr/src/app/node_modules WORKDIR /usr/src/app -COPY --from=build --chown=node:node /usr/src/app/dist ./dist -COPY --from=build --chown=node:node /usr/src/app/node_modules ./node_modules -COPY --from=build --chown=node:node /usr/src/app/prisma/generated ./prisma/generated -COPY --from=build --chown=node:node /usr/src/app/package.json ./package.json -COPY --from=external-clients --chown=node:node /clients/challenge-api-v6/packages/challenge-prisma-client /usr/src/challenge-api-v6/packages/challenge-prisma-client -COPY --from=external-clients --chown=node:node /clients/identity-api-v6/packages/identity-prisma-client /usr/src/identity-api-v6/packages/identity-prisma-client -COPY --from=external-clients --chown=node:node /clients/member-api-v6/packages/member-prisma-client /usr/src/member-api-v6/packages/member-prisma-client -COPY --from=external-clients --chown=node:node /clients/resource-api-v6/packages/resources-prisma-client /usr/src/resource-api-v6/packages/resources-prisma-client +COPY --from=build --chown=app:app /usr/src/app/dist ./dist +COPY --from=build --chown=app:app /usr/src/app/node_modules ./node_modules +COPY --from=build --chown=app:app /usr/src/app/prisma/generated ./prisma/generated +COPY --from=build --chown=app:app /usr/src/app/package.json ./package.json +COPY --from=external-clients --chown=app:app /clients/challenge-api-v6/packages/challenge-prisma-client /usr/src/challenge-api-v6/packages/challenge-prisma-client +COPY --from=external-clients --chown=app:app /clients/identity-api-v6/packages/identity-prisma-client /usr/src/identity-api-v6/packages/identity-prisma-client +COPY --from=external-clients --chown=app:app /clients/member-api-v6/packages/member-prisma-client /usr/src/member-api-v6/packages/member-prisma-client +COPY --from=external-clients --chown=app:app /clients/resource-api-v6/packages/resources-prisma-client /usr/src/resource-api-v6/packages/resources-prisma-client -USER node +USER app EXPOSE 3000 CMD ["node", "dist/main.js"] diff --git a/README.md b/README.md index eeddda6..c0bb9d3 100644 --- a/README.md +++ b/README.md @@ -175,8 +175,11 @@ but the `/moderation` endpoints require administrator role access for humans or ## Development -The supported local and container runtime is Node.js 26.5.0 with pnpm 11.15.1. +The supported local and container runtime is Node.js 26.5.1 with pnpm 11.15.1. The checked-in `.nvmrc` selects the required Node.js release. +The production image installs Alpine's dynamically linked Node.js package and +runs as an unprivileged application user so patched system libraries are used at +runtime without shipping package-manager tooling. ```bash nvm use diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1ddd803..672ed8a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,18 +10,21 @@ overrides: ajv@^8.0.0: 8.18.0 axios: 1.18.1 body-parser@^2: 2.3.0 - brace-expansion@^1.0.0: 1.1.16 - brace-expansion@^2.0.0: 2.1.2 - brace-expansion@>=3: 5.0.7 + brace-expansion@^1.0.0: 1.1.18 + brace-expansion@^2.0.0: 2.1.4 + brace-expansion@>=3: 5.0.9 + deepmerge-ts: 8.0.0 defu: 6.1.7 effect@^3.0.0: 3.22.0 - fast-uri@^3.0.0: 3.1.4 + fast-uri@^3.0.0: 3.1.5 file-type@^21.0.0: 21.3.2 + find-my-way: 9.7.0 follow-redirects: 1.16.0 form-data: 4.0.6 - hono: 4.12.31 - js-yaml@^3: 3.15.0 - js-yaml@^4.0.0: 4.3.0 + hono: 4.12.34 + js-yaml@^3: 3.15.1 + js-yaml@^4.0.0: 4.3.1 + js-yaml@^5.0.0: 5.2.2 lodash: 4.18.1 minimatch@^3.0.0: 3.1.4 minimatch@^5.0.0: 5.1.8 @@ -31,6 +34,7 @@ overrides: picomatch@^4.0.0: 4.0.5 piscina@^4.0.0: 4.9.3 qs: 6.15.3 + valibot: 1.4.2 importers: @@ -1429,12 +1433,12 @@ packages: resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} engines: {node: '>=18'} - brace-expansion@1.1.16: - resolution: {integrity: sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==} + brace-expansion@1.1.18: + resolution: {integrity: sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==} - brace-expansion@5.0.7: - resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} - engines: {node: 18 || 20 || >=22} + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + engines: {node: 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -1735,8 +1739,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: @@ -1924,6 +1928,7 @@ packages: eslint@9.39.4: resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} 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: '*' @@ -2012,8 +2017,8 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-uri@3.1.4: - resolution: {integrity: sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==} + fast-uri@3.1.5: + resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} fb-watchman@2.0.2: resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} @@ -2043,8 +2048,8 @@ packages: resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} engines: {node: '>= 18.0.0'} - find-my-way@9.6.0: - resolution: {integrity: sha512-Zf4Xve4RymLl7NgaavNebZ01joJ8MfVerOG43wy7SHLO+r+K0C6d/SE0BiR7AV5V1VOCFlOP7ecdo+I4qmiHrQ==} + find-my-way@9.7.0: + resolution: {integrity: sha512-f2JHn75x2JlwUwLenZypgczR7YWMb/uO9BvUXtus+JMgkbIkLADd38cI4EiV+OQqrGo1Zlq6V8wnqMJ8e62wUQ==} engines: {node: '>=20'} find-up@4.1.0: @@ -2492,16 +2497,16 @@ 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.1: - resolution: {integrity: sha512-zfLtNfQqxVqq3uaTqSkh4x4hZw3KHobGUA0fJUj4wawW8bsQLTVqpHdXSIzidh7o+4lEW36tANuAGdaFx6Zgnw==} + js-yaml@5.2.2: + resolution: {integrity: sha512-dayzUzKkJ1MkuUtZglSebU43utNXH0OWQByK9rKOOuYIO8M5TV1y+n8ALMdG0rdzBnfNkOmZEqrURepb0ejqBw==} hasBin: true jsesc@3.1.0: @@ -3552,8 +3557,8 @@ packages: resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} engines: {node: '>=10.12.0'} - valibot@1.2.0: - resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} + valibot@1.4.2: + resolution: {integrity: sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==} peerDependencies: typescript: '>=5' peerDependenciesMeta: @@ -3934,7 +3939,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.4 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -4126,7 +4131,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': {} @@ -4437,7 +4442,7 @@ snapshots: '@nestjs/common': 11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@8.1.1) '@nestjs/core': 11.1.28(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@8.1.1))(@nestjs/platform-express@11.1.28)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/mapped-types': 2.1.1(@nestjs/common@11.1.28(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2)(supports-color@8.1.1))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2) - js-yaml: 5.2.1 + js-yaml: 5.2.2 lodash: 4.18.1 path-to-regexp: 8.4.2 reflect-metadata: 0.2.2 @@ -4483,7 +4488,7 @@ snapshots: '@prisma/config@7.9.0': dependencies: c12: 3.3.4 - deepmerge-ts: 7.1.5 + deepmerge-ts: 8.0.0 effect: 3.22.0 empathic: 2.0.0 transitivePeerDependencies: @@ -4501,14 +4506,14 @@ snapshots: '@prisma/get-platform': 7.2.0 '@prisma/query-plan-executor': 7.2.0 '@prisma/streams-local': 0.1.11 - find-my-way: 9.6.0 + find-my-way: 9.7.0 foreground-child: 3.3.1 get-port-please: 3.2.0 pathe: 2.0.3 proper-lockfile: 4.1.2 remeda: 2.33.4 std-env: 3.10.0 - valibot: 1.2.0(typescript@5.9.3) + valibot: 1.4.2(typescript@5.9.3) zeptomatch: 2.1.0 transitivePeerDependencies: - typescript @@ -5118,7 +5123,7 @@ snapshots: ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.4 + fast-uri: 3.1.5 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -5260,12 +5265,12 @@ snapshots: transitivePeerDependencies: - supports-color - brace-expansion@1.1.16: + brace-expansion@1.1.18: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@5.0.7: + brace-expansion@5.0.9: dependencies: balanced-match: 4.0.4 @@ -5461,7 +5466,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: @@ -5548,7 +5553,7 @@ snapshots: deep-is@0.1.4: {} - deepmerge-ts@7.1.5: {} + deepmerge-ts@8.0.0: {} deepmerge@4.3.1: {} @@ -5835,7 +5840,7 @@ snapshots: fast-safe-stringify@2.1.1: {} - fast-uri@3.1.4: {} + fast-uri@3.1.5: {} fb-watchman@2.0.2: dependencies: @@ -5873,7 +5878,7 @@ snapshots: transitivePeerDependencies: - supports-color - find-my-way@9.6.0: + find-my-way@9.7.0: dependencies: fast-deep-equal: 3.1.3 fast-querystring: 1.1.2 @@ -6508,16 +6513,16 @@ 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 - js-yaml@5.2.1: + js-yaml@5.2.2: dependencies: argparse: 2.0.1 @@ -6711,11 +6716,11 @@ snapshots: minimatch@10.2.5: dependencies: - brace-expansion: 5.0.7 + brace-expansion: 5.0.9 minimatch@3.1.4: dependencies: - brace-expansion: 1.1.16 + brace-expansion: 1.1.18 minimist@1.2.8: {} @@ -7496,7 +7501,7 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 convert-source-map: 2.0.0 - valibot@1.2.0(typescript@5.9.3): + valibot@1.4.2(typescript@5.9.3): optionalDependencies: typescript: 5.9.3 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 416dc06..c72f3ca 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -20,18 +20,21 @@ overrides: 'ajv@^8.0.0': 8.18.0 axios: 1.18.1 'body-parser@^2': 2.3.0 - 'brace-expansion@^1.0.0': 1.1.16 - 'brace-expansion@^2.0.0': 2.1.2 - 'brace-expansion@>=3': 5.0.7 + 'brace-expansion@^1.0.0': 1.1.18 + 'brace-expansion@^2.0.0': 2.1.4 + 'brace-expansion@>=3': 5.0.9 + deepmerge-ts: 8.0.0 defu: 6.1.7 'effect@^3.0.0': 3.22.0 - 'fast-uri@^3.0.0': 3.1.4 + 'fast-uri@^3.0.0': 3.1.5 'file-type@^21.0.0': 21.3.2 + find-my-way: 9.7.0 follow-redirects: 1.16.0 form-data: 4.0.6 - hono: 4.12.31 - 'js-yaml@^3': 3.15.0 - 'js-yaml@^4.0.0': 4.3.0 + hono: 4.12.34 + 'js-yaml@^3': 3.15.1 + 'js-yaml@^4.0.0': 4.3.1 + 'js-yaml@^5.0.0': 5.2.2 lodash: 4.18.1 'minimatch@^3.0.0': 3.1.4 'minimatch@^5.0.0': 5.1.8 @@ -41,6 +44,7 @@ overrides: 'picomatch@^4.0.0': 4.0.5 'piscina@^4.0.0': 4.9.3 qs: 6.15.3 + valibot: 1.4.2 allowBuilds: '@nestjs/core': false