Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ workflows:
branches:
only:
- dev
- security_july_2026
tags:
only: /^dev-.*/

Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v22.13.1
v26.5.0
54 changes: 44 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,23 +1,57 @@
# syntax=docker/dockerfile:1

FROM node:22.13.1-alpine
ARG NODE_VERSION=26.5.0
ARG ALPINE_VERSION=3.23

RUN apk add --no-cache bash git
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS build

ARG PNPM_VERSION=10.28.2

ENV CI=true
ENV PRISMA_CLI_BINARY_TARGETS=linux-musl-openssl-3.0.x
WORKDIR /app

RUN apk upgrade --no-cache \
&& apk add --no-cache git openssh-client openssl \
&& npm install --global "pnpm@${PNPM_VERSION}" \
&& npm cache clean --force

COPY package.json pnpm-lock.yaml pnpm-workspace.yaml nest-cli.json tsconfig.json tsconfig.build.json \
eslint.config.mjs .prettierrc ./
COPY patches ./patches
COPY prisma ./prisma
RUN pnpm install --frozen-lockfile

COPY . .
RUN pnpm lint \
&& pnpm build \
&& pnpm prune --prod --ignore-scripts

FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS production

ARG RESET_DB_ARG=false
ENV RESET_DB=$RESET_DB_ARG
ARG SEED_DATA_ARG=""
ENV SEED_DATA=$SEED_DATA_ARG
ENV PRISMA_CLI_BINARY_TARGETS=linux-musl-openssl-3.0.x

ENV NODE_ENV=production
ENV PRISMA_CLI_BINARY_TARGETS=linux-musl-openssl-3.0.x
ENV RESET_DB=$RESET_DB_ARG
ENV SEED_DATA=$SEED_DATA_ARG
WORKDIR /app
COPY --chown=node:node . .
RUN npm install pnpm -g
RUN pnpm install --frozen-lockfile
RUN pnpm run build
RUN chmod +x appStartUp.sh

RUN apk upgrade --no-cache \
&& apk add --no-cache bash openssl \
&& rm -rf /usr/local/lib/node_modules/npm \
&& rm -f /usr/local/bin/npm /usr/local/bin/npx

COPY --from=build --chown=node:node /app/dist ./dist
COPY --from=build --chown=node:node /app/node_modules ./node_modules
COPY --from=build --chown=node:node /app/package.json ./package.json
COPY --from=build --chown=node:node /app/prisma ./prisma
COPY --from=build --chown=node:node /app/prisma.config.ts ./prisma.config.ts
COPY --from=build --chown=node:node --chmod=755 /app/appStartUp.sh ./appStartUp.sh

USER node
EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
CMD wget -q -O /dev/null "http://127.0.0.1:${PORT:-3000}/v6/projects/health" || exit 1
Expand Down
29 changes: 18 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
NestJS drop-in replacement for `tc-project-service`, serving the Topcoder platform at `/v6/projects`.

[![CircleCI](https://img.shields.io/badge/CircleCI-build%20status-informational?logo=circleci)](https://circleci.com/)
![Node](https://img.shields.io/badge/node-v22.13.1-339933?logo=node.js&logoColor=white)
![Node](https://img.shields.io/badge/node-v26.5.0-339933?logo=node.js&logoColor=white)
![pnpm](https://img.shields.io/badge/pnpm-10.28.2-F69220?logo=pnpm&logoColor=white)
![Audit](https://img.shields.io/badge/audit-2%20moderate%20ajv%20findings-orange)
![Audit](https://img.shields.io/badge/production%20audit-0%20findings-brightgreen)

## Table of Contents

Expand Down Expand Up @@ -352,7 +352,7 @@ Reference source: `.env.example`.

### Prerequisites

- Node.js `v22.13.1` (`nvm use` in this project folder)
- Node.js `v26.5.0` (`nvm use` in this project folder)
- pnpm `10.28.2`
- PostgreSQL

Expand All @@ -369,7 +369,7 @@ pnpm install
Configure `DATABASE_URL`, then run:

```bash
npx prisma migrate dev
pnpm exec prisma migrate dev
pnpm prisma db seed
```

Expand Down Expand Up @@ -419,6 +419,10 @@ Must pass before every commit per `AGENTS.md`.
## Deployment

- CI/CD: CircleCI -> AWS ECS Fargate.
- The multi-stage Docker image builds with Node 26.5.0 and copies only compiled
output, production dependencies, and Prisma migration assets into its runtime.
- Container startup invokes the local Prisma CLI to deploy migrations before
replacing the shell process with `node dist/src/main`.
- Blue-green rollout strategy is documented in `docs/MIGRATION_RUNBOOK.md`.
- Recommended alerts:
- 5xx rate > 2% for 5 minutes
Expand All @@ -436,18 +440,21 @@ Open findings are tracked inline with `TODO (security)` comments in source.
| `src/main.ts` | CORS returns `'*'` for requests with no `Origin` header | Low | Open - consider returning `false` for server-to-server calls |
| `src/main.ts` | Swagger UI publicly accessible with no auth in production | Medium | Open - restrict by IP or add HTTP Basic auth, or gate behind env flag |
| `src/main.ts` | Duplicate Swagger mount at `/v6/projects-api-docs` | Low (quality) | Open - consolidate to single path |
| `docs/DEPENDENCIES.md` | `tc-bus-api-wrapper` and `tc-core-library-js` sourced from GitHub (floating refs) | Medium | Open - publish to npm or pin to commit SHA |
| `docs/DEPENDENCIES.md` | 2 moderate `ajv` vulnerabilities (transitive via `@eslint/eslintrc` and `fork-ts-checker-webpack-plugin`) | Moderate | Open - requires upstream toolchain migration off Ajv v6 |
| `docs/DEPENDENCIES.md` | GitHub-sourced Topcoder packages do not have a registry release stream | Low | Mitigated with immutable commit pins; external API dependencies install only their generated Prisma-client subdirectories |

## Dependency Status

Summary from `docs/DEPENDENCIES.md`:

- Audit: 2 moderate `ajv` vulnerabilities remain (transitive, upstream fix required).
- All HIGH/CRITICAL findings are resolved via `pnpm.overrides` (`axios`, `jws`, `minimatch`, `hono`, `lodash`, `qs`, `fast-xml-parser`).
- Patch updates available: `@nestjs/*` (11.1.13 -> 11.1.14), `@prisma/*` (7.4.0 -> 7.4.1), `@aws-sdk/*`.
- Major updates available: `eslint` (9 -> 10), `jest` (29 -> 30), `uuid` (11 -> 13), `@types/node` (22 -> 25) - evaluate compatibility before upgrading.
- Supply-chain risk: `tc-bus-api-wrapper` and `tc-core-library-js` are GitHub-sourced with floating refs.
- Production audit: no known vulnerabilities.
- Node 26.5.0, NestJS 11.1.28, Prisma 7.8.0, Axios 1.18.1,
Lodash 4.18.1, qs 6.15.3, and UUID 14.0.1 are locked in the
security candidate.
- Security overrides for affected transitives are maintained in
`pnpm-workspace.yaml` and captured in `pnpm-lock.yaml`.
- Topcoder Git dependencies use immutable commits. Cross-service database
dependencies install only committed generated Prisma clients rather than the
full API applications and their unrelated dependency graphs.

Full details: `docs/DEPENDENCIES.md`.

Expand Down
2 changes: 1 addition & 1 deletion appStartUp.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set -eo pipefail
export DATABASE_URL=$(echo -e ${DATABASE_URL})

echo "Database - running migrations."
npx prisma migrate deploy
node node_modules/prisma/build/index.js migrate deploy

# Start the app
exec node dist/src/main
185 changes: 105 additions & 80 deletions docs/DEPENDENCIES.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,98 +2,123 @@

## Overview

This document tracks dependency security posture and version drift for `projects-api-v6`.
This document records the dependency and production-image posture for the
`projects-api-v6` security candidate rebuilt from the repository's `dev`
lineage.

Toolchain used for this verification cycle:

- Node: `v22.13.1`
- Node: `v26.5.0`
- pnpm: `10.28.2`
- Verification date: `2026-02-20`
- Prisma CLI, client, and PostgreSQL adapter: `7.8.0`
- Verification date: `2026-07-20`

To re-run checks:
Run `nvm use` from this project directory before each Node or pnpm command.
The standard verification commands are:

- `pnpm outdated`
- `pnpm audit`
- `pnpm lint`
- `pnpm build`

## Security Vulnerabilities
```bash
pnpm audit --prod --audit-level=moderate
pnpm lint
pnpm build
pnpm test --runInBand
```

| CVE / Advisory | Severity | Package | Affected Versions | Fixed In | Status | Planned / Required Fix |
|---|---|---|---|---|---|---|
| GHSA-43fc-jf86-j433 | **HIGH** | `axios` | `<=0.30.2` and `>=1.0.0 <=1.13.4` | `>=0.30.3`, `>=1.13.5` | ✅ Cleared in production/transitive paths | Applied `pnpm.overrides.axios = 1.13.5`; verified `tc-core-library-js` now resolves `axios@1.13.5`. |
| GHSA-gq3j-xvxp-8hrf | **LOW** | `hono` | `<4.11.10` | `>=4.11.10` | ✅ Cleared | Updated `pnpm.overrides.hono` to `4.11.10`; Prisma transitive paths resolve `hono@4.11.10`. |
| GHSA-3ppc-4f35-3m26 | **HIGH** | `minimatch` | `<10.2.1` | `>=10.2.1` | ✅ Cleared | Added `pnpm.overrides.minimatch = 10.2.1`; audit no longer reports minimatch. |
| GHSA-2g4f-4pwh-qvx6 | **MODERATE** | `ajv` | `<8.18.0` | `>=8.18.0` | ✅ Cleared | Enforced `pnpm.overrides.ajv = 8.18.0` and added compatibility patches for `eslint@9.39.2` / `@eslint/eslintrc@3.3.3` to preserve lint behavior with Ajv v8. |
| CVE-2025-65945 | **HIGH** | `jws` (transitive via `jsonwebtoken`, `jwks-rsa`) | `<=3.2.2`, `4.0.0` | `3.2.3`, `4.0.1` | ✅ Cleared | Existing `jws` override retained (`>=3.2.3 <4.0.0 || >=4.0.1`). |
## Security status

Current `pnpm audit` summary:
The production dependency audit reports:

```text
No known vulnerabilities found
```

## Outdated Dependencies

Regenerated from `pnpm outdated` after dependency/security updates.

| Package | Specifier | Resolved | Latest | Notes |
|---|---|---|---|---|
| `@nestjs/common` | `^11.0.1` | `11.1.13` | `11.1.14` | Patch update available |
| `@nestjs/core` | `^11.0.1` | `11.1.13` | `11.1.14` | Patch update available |
| `@nestjs/platform-express` | `^11.0.1` | `11.1.13` | `11.1.14` | Patch update available |
| `@nestjs/testing` (dev) | `^11.0.1` | `11.1.13` | `11.1.14` | Patch update available |
| `@prisma/adapter-pg` | `7.4.0` | `7.4.0` | `7.4.1` | Patch update available |
| `@prisma/client` | `7.4.0` | `7.4.0` | `7.4.1` | Patch update available |
| `prisma` (dev) | `7.4.0` | `7.4.0` | `7.4.1` | Patch update available |
| `@aws-sdk/client-s3` | `^3.926.0` | `3.985.0` | `3.994.0` | Minor update available |
| `@aws-sdk/s3-request-presigner` | `^3.926.0` | `3.985.0` | `3.994.0` | Minor update available |
| `qs` | `^6.14.2` | `6.14.2` | `6.15.0` | Minor update available (currently security-pinned by override) |
| `typescript-eslint` (dev) | `^8.20.0` | `8.54.0` | `8.56.0` | Minor update available |
| `@eslint/js` (dev) | `^9.18.0` | `9.39.2` | `10.0.1` | Major update available |
| `@types/jest` (dev) | `^29.5.14` | `29.5.14` | `30.0.0` | Major update available |
| `@types/node` (dev) | `^22.10.7` | `22.19.9` | `25.3.0` | Major update available |
| `eslint` (dev) | `^9.18.0` | `9.39.2` | `10.0.0` | Major update available |
| `globals` (dev) | `^15.14.0` | `15.15.0` | `17.3.0` | Major update available |
| `jest` (dev) | `^29.7.0` | `29.7.0` | `30.2.0` | Major update available |
| `uuid` | `^11.1.0` | `11.1.0` | `13.0.0` | Major update available |
| `@swc/cli` (dev) | `^0.6.0` | `0.6.0` | `0.8.0` | Minor update available |

## GitHub-Sourced Packages (Supply-Chain Risk)

| Package | Specifier | Risk |
|---|---|---|
| `tc-bus-api-wrapper` | `github:topcoder-platform/tc-bus-api-wrapper.git` | GitHub source dependency; no semver release stream in npm |
| `tc-core-library-js` | `topcoder-platform/tc-core-library-js.git#master` | Floating `master` reference; transitive changes can land without semver |

## pnpm Overrides in Effect

| Override | Pinned To | Reason |
|---|---|---|
| `ajv` | `8.18.0` | Fix advisory GHSA-2g4f-4pwh-qvx6 across all transitive paths |
| `axios` | `1.13.5` | Force patched axios across transitive paths (`tc-core-library-js` included) |
| `fast-xml-parser` | `5.3.6` | Security hardening |
| `hono` | `4.11.10` | Fix advisory GHSA-gq3j-xvxp-8hrf |
| `jws` | `>=3.2.3 <4.0.0 \|\| >=4.0.1` | Fix CVE-2025-65945 |
| `lodash` | `4.17.23` | Prototype pollution fix |
| `minimatch` | `10.2.1` | Fix advisory GHSA-3ppc-4f35-3m26 |
| `qs` | `6.14.2` | Prototype pollution / DoS fix |

## pnpm Patched Dependencies

| Package | Patch File | Reason |
|---|---|---|
| `@eslint/eslintrc@3.3.3` | `patches/@eslint__eslintrc@3.3.3.patch` | Adapt Ajv initialization to work with enforced `ajv@8.18.0` |
| `eslint@9.39.2` | `patches/eslint@9.39.2.patch` | Replace Ajv v6-specific draft-04 path/API usage with Ajv v8-compatible behavior |

## Verification Log

Commands run in `projects-api-v6/` (with `nvm use` before each):
The remediated direct dependency set includes:

| Package group | Version |
| --- | --- |
| Node.js | 26.5.0 |
| NestJS common, core, platform, and testing | 11.1.28 |
| NestJS Swagger | 11.4.6 |
| Prisma CLI, client, and PostgreSQL adapter | 7.8.0 |
| AWS SDK S3 client and request presigner | 3.1090.0 |
| Axios | 1.18.1 |
| Lodash | 4.18.1 |
| qs | 6.15.3 |
| UUID | 14.0.1 |

`pnpm-workspace.yaml` constrains vulnerable transitive ranges for Axios, Hono,
Fast XML Parser, Multer, Path-to-RegExp, file-type, form-data, js-yaml,
brace-expansion, Joi, and related packages. The generated lockfile is the
authoritative record of their resolved versions.

Prisma 7.8.0 currently prints an upstream support-list warning under Node 26.
The four committed external generated clients also retain their existing Prisma
6.19.x runtimes. Client generation, lint, build, migrations, the primary health
query, and explicit connection queries through all four external clients are
verified with Node 26.5.0. Keep this compatibility point in deployment QA until
the applicable Prisma support messages explicitly include Node 26.

## External Prisma clients

The application imports generated Prisma clients for challenge, member,
resource, and standardized-skills data. Installing each source repository's
root package pulled its entire API dependency graph into this service even
though none of that application code was used.

The dependencies now select only the committed generated-client subdirectory
from an immutable repository commit:

| Dependency | Commit | Installed path |
| --- | --- | --- |
| `@topcoder/challenge-api-v6` | `8ca7e4d065d15a077c648e4d04b85b73276cc078` | `packages/challenge-prisma-client` |
| `@topcoder/member-api-v6` | `a0ffd68bd7c63bbf525459b1e195d6d38ab26a91` | `packages/member-prisma-client` |
| `@topcoder/resource-api-v6` | `c64ffdccbed62533528dce55d33484be5e035d89` | `packages/resources-prisma-client` |
| `@topcoder/standardized-skills-api` | `012bf813583f80ec1dad014824ebb4d0bd434439` | `packages/skills-prisma-client` |

This preserves the exact generated clients used by the `dev` lineage while
excluding unrelated service dependencies and lifecycle scripts. These generated
packages contain Prisma 6.19.x runtimes; each client is connection/query
smoke-tested under Node 26 in addition to the application's Prisma 7 health
check.

## Other Git dependencies

The remaining Topcoder libraries are pinned to immutable commits:

| Package | Commit |
| --- | --- |
| `tc-bus-api-wrapper` | `297a9c0adcdb97661257e7825bee9c3f5578b833` |
| `tc-core-library-js` | `1075136355e1e1c4779f2138a30f3ffbd718bfa4` |

The wrapper's own `tc-core-library-js#master` dependency is overridden to the
same immutable core-library archive, preventing lockfile regeneration from
advancing that transitive ref. Publishing these libraries to a controlled
package registry would further reduce reliance on Git-hosted installation.

## Production image

The Dockerfile uses separate build and production stages on Node 26.5.0 with
Alpine 3.23. The production stage contains only:

- compiled application output;
- production dependencies;
- the Prisma CLI, configuration, schema, and migrations;
- the startup script required to deploy migrations before listening.

Development tooling and npm/npx are not copied into the final image. The
startup script invokes the local Prisma CLI directly and then uses `exec` for
the NestJS process, preserving ECS signal handling and migration behavior.

## Verification log

Update this table whenever dependency or image contents change.

| Command | Result |
|---|---|
| `pnpm install` | ✅ Passed |
| `pnpm audit` | ✅ Passed (`No known vulnerabilities found`) |
| `pnpm outdated` | ✅ Completed (table above updated) |
| `pnpm lint` | ✅ Passed |
| `pnpm build` | ✅ Passed |
| --- | --- |
| `pnpm install --frozen-lockfile` | Passed; Prisma 7.8.0 client generated |
| `pnpm audit --prod --audit-level=moderate` | Passed: no known vulnerabilities |
| `pnpm lint` | Passed |
| `pnpm build` | Passed |
| `pnpm test --runInBand` | 46 of 57 suites and 360 of 375 tests passed; 10 existing event-publish mock expectations and 5 JWT fixture expectations remain stale on `dev` |
| Docker migration and health smoke test | Passed: 3 migrations applied, server remained running, and `/v6/projects/health` returned `{"checksRun":1}` |
| External generated-client query smoke | Passed for challenge, member, resource, and skills clients under Node 26.5.0 |
| Trivy 0.72.0 Critical/High/Medium image scan | Passed: 0 / 0 / 0 |
Loading
Loading