From d3b1643cc329b7a7f6e40ce5a63644092e560147 Mon Sep 17 00:00:00 2001 From: jmgasper Date: Tue, 1 Sep 2026 08:57:53 +1000 Subject: [PATCH] fix(security): remediate September 2026 findings --- .nvmrc | 2 +- Dockerfile | 36 ++++++++----- README.md | 16 +++--- doc/Group.postman_environment.json | 4 +- mock/mock-api.js | 3 +- pnpm-lock.yaml | 82 ++++++++++++++++-------------- pnpm-workspace.yaml | 13 +++-- 7 files changed, 87 insertions(+), 69 deletions(-) diff --git a/.nvmrc b/.nvmrc index eacd481..34adfb4 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v26.5.0 +v26.5.1 diff --git a/Dockerfile b/Dockerfile index 8aba5c9..cfee036 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ # 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 build +FROM node:${NODE_VERSION}-alpine3.24 AS build RUN apk upgrade --no-cache \ && apk add --no-cache bash @@ -17,12 +17,21 @@ RUN pnpm run build RUN pnpm prune --prod RUN node node_modules/prisma/build/index.js generate -FROM node:${NODE_VERSION}-alpine AS runtime +FROM alpine:3.24 AS runtime +ARG ALPINE_NODE_VERSION=26.5.1-r0 +ARG OPENSSL_VERSION=3.5.8-r0 + +# Use Alpine's dynamically linked Node.js package so the runtime receives the +# patched system OpenSSL libraries without carrying npm or Node.js headers. RUN apk upgrade --no-cache \ - && apk add --no-cache bash \ - && rm -rf /usr/local/lib/node_modules/npm \ - && rm -f /usr/local/bin/npm /usr/local/bin/npx + && apk add --no-cache \ + bash \ + "libcrypto3=${OPENSSL_VERSION}" \ + "libssl3=${OPENSSL_VERSION}" \ + "nodejs-current=${ALPINE_NODE_VERSION}" \ + && addgroup -S -g 10001 app \ + && adduser -S -D -H -h /app -u 10001 -G app app ARG RESET_DB_ARG=false ENV RESET_DB=$RESET_DB_ARG @@ -32,10 +41,13 @@ ENV PRISMA_CLI_BINARY_TARGETS=linux-musl-openssl-3.0.x ENV NODE_ENV=production WORKDIR /app -COPY --from=build /app/dist ./dist -COPY --from=build /app/node_modules ./node_modules -COPY --from=build /app/prisma ./prisma -COPY --from=build /app/package.json ./package.json -COPY --from=build /app/appStartUp.sh ./appStartUp.sh -RUN chmod +x appStartUp.sh +COPY --from=build --chown=app:app /app/dist ./dist +COPY --from=build --chown=app:app /app/node_modules ./node_modules +COPY --from=build --chown=app:app /app/prisma ./prisma +COPY --from=build --chown=app:app /app/package.json ./package.json +COPY --from=build --chown=app:app /app/appStartUp.sh ./appStartUp.sh +RUN chmod 0555 appStartUp.sh + +USER app + CMD ["./appStartUp.sh"] diff --git a/README.md b/README.md index 2a12247..7680ce5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,11 @@ Group API built on modern frameworks for managing all group-related Topcoder needs. -Use Node.js 26.5.0 and pnpm 11.15.1. Run `nvm use` in this project before running pnpm commands. +Use Node.js 26.5.1 and pnpm 11.15.1. Run `nvm use` in this project before running pnpm commands. + +The production image runs as the unprivileged `app` user. Its Alpine 3.24 +runtime installs Node.js 26.5.1 and the patched system OpenSSL 3.5.8 packages; +the Node.js build toolchain and npm are not copied into the runtime image. ## Project setup @@ -73,15 +77,11 @@ pnpm exec prisma migrate reset curl --request POST \ --url https://auth0proxy.topcoder-dev.com/token \ --header 'content-type: application/json' \ - --data '{"auth0_url": "https://topcoder-dev.auth0.com/oauth/token", "client_id":"8uHVTW2WHp8BbBPX7J0YTAwgYbYTfjsM","client_secret":"hQXGeKO-cSZ15CLBNRDGZAGRcHWay6PAR_zTQQz4YjdX7QNU7NwGoaa3YuuUYvUv","audience":"https://m2m.topcoder-dev.com/","grant_type":"client_credentials"}' + --data '{"auth0_url": "https://topcoder-dev.auth0.com/oauth/token", "client_id":"YOUR_CLIENT_ID","client_secret":"YOUR_CLIENT_SECRET","audience":"https://m2m.topcoder-dev.com/","grant_type":"client_credentials"}' ``` -For a read-only scope M2M token, use: - -``` -"client_id":"dgrh9mkk8N6sWsVPFf6JqdVmjjuibowf" -"client_secret":"_6jFqIppsA3u87Tm7mRDXhX_yf8-DC2ooc5f0affjuWoRiLG1ZriHtUsMQgo61qp" -``` +Obtain M2M credentials with the required scope from the approved secret store; +never commit client credentials to this repository. - then you can use Postman to test all apis - Swagger docs are accessible at `http://localhost:3000/api-docs` diff --git a/doc/Group.postman_environment.json b/doc/Group.postman_environment.json index 28bdf81..67c1518 100644 --- a/doc/Group.postman_environment.json +++ b/doc/Group.postman_environment.json @@ -9,11 +9,11 @@ }, { "key": "TOKEN", - "value": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik5VSkZORGd4UlRVME5EWTBOVVkzTlRkR05qTXlRamxETmpOQk5UYzVRVUV3UlRFeU56TTJRUSJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoiOHVIVlRXMldIcDhCYkJQWDdKMFlUQXdnWWJZVGZqc01AY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vbTJtLnRvcGNvZGVyLWRldi5jb20vIiwiaWF0IjoxNzUxNTEzOTY0LCJleHAiOjE3NTE2MDAzNjQsInNjb3BlIjoicmVhZDpjaGFsbGVuZ2VzIGFsbDpncm91cHMgd3JpdGU6Z3JvdXBzIHJlYWQ6Z3JvdXBzIHJlYWQ6YnVzX3RvcGljcyB3cml0ZTpidXNfYXBpIiwiZ3R5IjoiY2xpZW50LWNyZWRlbnRpYWxzIiwiYXpwIjoiOHVIVlRXMldIcDhCYkJQWDdKMFlUQXdnWWJZVGZqc00ifQ.pdVpeB0PIZSFpTjcR82AvMiyFQizj04y7x7zqU2Y9Ao_ZjlwmcjDk02WgZrPzhgJ_59E5M9ZCgEyFGA3EZrzMKcUcyDDcL-PhN6l5-IMBYDyGYNh7gLuyY5vwMqGUZx3IC7mwLXMeNtenbigkbOx00AHI9R85g2Rqmi6wKnDHsGCFfNKckPBpdCCom-PHoxkkLEcCAedvcXDX8AUkIePJORfaoC5ZEV0aI0-miU1fUwqQMySZX891Hu7cDL5KxZY12ic_SNgoQ_vEi3yw5hbrl7urkmg1HyFxlb0tX7cQmgvVXqxwlaqgrBEv9UOB-vcwIqsWLJRKSJ1OA7abg5NXA", + "value": "YOUR_JWT_TOKEN", "enabled": true } ], "_postman_variable_scope": "environment", "_postman_exported_at": "2025-07-07T14:54:50.195Z", "_postman_exported_using": "Postman/9.28.4" -} \ No newline at end of file +} diff --git a/mock/mock-api.js b/mock/mock-api.js index f5fb934..0c8995e 100644 --- a/mock/mock-api.js +++ b/mock/mock-api.js @@ -32,7 +32,7 @@ app.post('/eventBus/bus/events', (req, res) => { res.json({}) }) -const m2mToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL3RvcGNvZGVyLWRldi5hdXRoMC5jb20vIiwic3ViIjoiakdJZjJwZDNmNDRCMWpxdk9haTMwQklLVFphbllCZlVAY2xpZW50cyIsImF1ZCI6Imh0dHBzOi8vbTJtLnRvcGNvZGVyLWRldi5jb20vIiwiaWF0IjoxNzQ4MDk5NDk4LCJleHAiOjE4NDgxODU4OTgsInNjb3BlIjoid3JpdGU6YnVzX2FwaSxhbGw6Y2hhbGxlbmdlcyIsImd0eSI6ImNsaWVudC1jcmVkZW50aWFscyIsImF6cCI6ImpHSWYycGQzZjQ0QjFqcXZPYWkzMEJJS1RaYW5ZQmZVIn0.h3ksdsdJm5USGF1VgROrpkTtStmCzv5ZA6y8bd8AnGY'; +const m2mToken = 'MOCK_M2M_TOKEN'; const m2mScope = 'write:bus_api,all:groups'; @@ -111,4 +111,3 @@ app.get('/challenges', (req, res) => { app.listen(app.get('port'), '0.0.0.0', () => { logger.info(`Express server listening on port ${app.get('port')}`) }) - diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d7cea4..5363b62 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,12 +12,14 @@ overrides: ajv@^8.0.0: 8.20.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@^1.0.0: 1.1.18 + brace-expansion@^2.0.0: 2.1.4 + brace-expansion@^5.0.0: 5.0.9 + deepmerge-ts: 8.0.0 defu: 6.1.7 diff@^4: 4.0.4 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@>=13.0.0 <21.3.2: 21.3.2 flatted: 3.4.2 follow-redirects: 1.16.0 @@ -25,8 +27,9 @@ overrides: handlebars: 4.7.9 ip-address@^10.0.0: 10.1.1 joi@^18.0.0: 18.2.1 - js-yaml@^3: 3.15.0 - js-yaml@^4.0.0: 4.3.0 + js-yaml@^3: 3.15.1 + js-yaml@^4.0.0: 4.3.1 + js-yaml@>=5.0.0 <=5.2.1: 5.2.2 jws: 4.0.1 lodash: 4.18.1 minimatch@^3.0.0: 3.1.4 @@ -1649,15 +1652,15 @@ 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@2.1.2: - resolution: {integrity: sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==} + brace-expansion@2.1.4: + resolution: {integrity: sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==} - 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==} @@ -1957,8 +1960,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: @@ -2127,6 +2130,7 @@ packages: eslint@9.39.2: resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} 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: '*' @@ -2227,8 +2231,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==} fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -2739,16 +2743,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: @@ -4265,7 +4269,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: @@ -4452,7 +4456,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.3': {} @@ -4841,7 +4845,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 @@ -4886,7 +4890,7 @@ snapshots: '@prisma/config@6.19.3': dependencies: c12: 3.1.0 - deepmerge-ts: 7.1.5 + deepmerge-ts: 8.0.0 effect: 3.22.0 empathic: 2.0.0 transitivePeerDependencies: @@ -5515,7 +5519,7 @@ snapshots: ajv@8.20.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 @@ -5672,16 +5676,16 @@ 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@2.1.2: + brace-expansion@2.1.4: dependencies: balanced-match: 1.0.2 - brace-expansion@5.0.7: + brace-expansion@5.0.9: dependencies: balanced-match: 4.0.4 @@ -5922,7 +5926,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: @@ -5965,7 +5969,7 @@ snapshots: deep-is@0.1.4: {} - deepmerge-ts@7.1.5: {} + deepmerge-ts@8.0.0: {} deepmerge@4.3.1: {} @@ -6257,7 +6261,7 @@ snapshots: fast-safe-stringify@2.1.1: {} - fast-uri@3.1.4: {} + fast-uri@3.1.5: {} fastq@1.19.1: dependencies: @@ -6980,16 +6984,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 @@ -7201,15 +7205,15 @@ 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 minimatch@9.0.9: dependencies: - brace-expansion: 2.1.2 + brace-expansion: 2.1.4 minimist@1.2.8: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 10bd861..e6303ba 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -12,12 +12,14 @@ overrides: 'ajv@^8.0.0': 8.20.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@^1.0.0': 1.1.18 + 'brace-expansion@^2.0.0': 2.1.4 + 'brace-expansion@^5.0.0': 5.0.9 + deepmerge-ts: 8.0.0 defu: 6.1.7 'diff@^4': 4.0.4 '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@>=13.0.0 <21.3.2': 21.3.2 flatted: 3.4.2 follow-redirects: 1.16.0 @@ -25,8 +27,9 @@ overrides: handlebars: 4.7.9 'ip-address@^10.0.0': 10.1.1 'joi@^18.0.0': 18.2.1 - 'js-yaml@^3': 3.15.0 - 'js-yaml@^4.0.0': 4.3.0 + 'js-yaml@^3': 3.15.1 + 'js-yaml@^4.0.0': 4.3.1 + 'js-yaml@>=5.0.0 <=5.2.1': 5.2.2 jws: 4.0.1 lodash: 4.18.1 'minimatch@^3.0.0': 3.1.4