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
23 changes: 23 additions & 0 deletions .forge/features/node-security-october-2026/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# October 2026 Node.js Security Update Spec

Status: Scoped from the user's security-maintenance request (2026-10-21).

## Purpose and scope

Address the Node.js October 21 security release across Forge's development, CI,
and container runtimes. Keep existing application behavior and dependencies.
The user requested a branch from main named `update-vulernerability`, no commits,
and validation results plus commands for committing manually.

## Acceptance criteria

- Contributors and CI use a supported, patched Node release.
- Every Node container build and runner stage uses the patched release.
- Existing checks and native dependencies are exercised under that release.
- Results distinguish source changes from production rollout and skipped checks.

## Non-goals and open questions

No app/UI, schema, permission, dependency, PHP, or production-service changes.
No PHP source or Composer manifest is tracked. Actual production runtime versions
and deployment completion require operator access; do not infer them from source.
49 changes: 49 additions & 0 deletions .forge/features/node-security-october-2026/srd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# October 2026 Node.js Security Update SRD

## Evidence and boundary

The [official October advisory](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases)
identifies Node 24.22.1, 22.23.4, and 26.12.1 as patched releases. It covers
DNS/TLS validation, Brotli memory corruption, HTTP behavior, and bundled libraries.
Before this change `.nvmrc` pinned EOL 25.6.1, root engines admitted Node >=20.16.0,
and 18 Node stages in 13 Dockerfiles used the floating `24-bookworm-slim` tag.
The local executable was 24.21.0. Production versions have not been observed.

## Technical requirements and compatibility

- Use 24.22.1, retaining the existing production major and Debian bookworm-slim.
- Pin `.nvmrc`; CI already consumes it via `tooling/github/setup/action.yml`.
- Restrict root engines to `^24.22.1`. An unbounded minimum would still admit
EOL 25.x and unpatched early 26.x versions.
- The engine range is advisory for local commands. Contributors must select the
`.nvmrc` runtime; this patch does not enforce arbitrary manual Node invocations.
- Replace every Node tools/runner base with `node:24.22.1-bookworm-slim`.
- Keep pnpm 9.12.1, Turbo, lockfile, application dependencies, and nginx unchanged.
- Update contributor instructions to agree with the supported runtime.
- Access policy, tRPC, database, authentication, and all app behavior are unchanged.

These are separate atomic checks of the same runtime invariant, consistent with
[Forge's engineering principles](../../../docs/agentic-development/forge-engineering-principles.md).
No shared application abstraction or new test harness is needed.

## Verification

Use an official Node binary with its SHA-256 checksum verified. Check all runtime
references, engine-range boundaries, native solver/image support, and repo gates.
Confirm the Docker tag exists for amd64 and arm64, then exercise Linux runtime
behavior and representative image builds when Docker is available.
A package-manager audit is supplementary; it does not audit Node itself.

## Rollout and rollback

Source changes do not patch running containers. After review/merge, rebuild and
redeploy each configured Coolify resource using these Dockerfiles, including
static-site builders, and verify `node --version` in the five Node runners
(Blade, Guild, KH9, TK, cron) is 24.22.1. Static nginx runners contain no Node.
A container restart alone is insufficient. Non-Docker deployments and CI runners
must also use the patched runtime. Do not roll back to the vulnerable versions;
use a supported release carrying the same fixes if compatibility requires a change.

## Open questions

Production inventory and deployment execution are outside this local patch.
70 changes: 70 additions & 0 deletions .forge/features/node-security-october-2026/status.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# October 2026 Node.js Security Update Status

Current phase: Validated locally; ready for user commit and operator rollout.

## Decisions

- 2026-10-21: User requested Node security maintenance from main, the branch
`update-vulernerability`, no commit, and manual commit commands.
- Branch starts at freshly fetched `origin/main` (8097334e). Local main is checked
out in another worktree and was left untouched. Existing untracked work is preserved.
- Keep the production Node 24 major; use patched 24.22.1 everywhere, restrict
engines to 24.x, and leave application dependencies/lockfile unchanged.
- Independent read-only investigation confirmed every runtime reference and
installed Next/Vitest/Vite/OR-Tools/Sharp compatibility with Node 24.
- User confirmed: keep dependency remediation out of this Node branch; report
audit findings separately.

## Progress

- [x] Verify official advisory and source runtime inventory.
- [x] Create requested branch and feature bundle.
- [x] Align development/CI, engines, documentation, and all Node Docker stages.
- [x] Complete focused runtime/native validation and repository gates.
- [x] Complete independent patch review (no actionable bypass/regression found).
- [x] Prepare readiness report and scoped manual commit commands.

## Validation

- Official macOS arm64 24.22.1 archive downloaded; SHA-256 matches SHASUMS256.txt.
- Docker registry manifest confirms 24.22.1-bookworm-slim for amd64 and arm64.
- Runtime-reference assertions: all 18 Node stages, root engines, `.nvmrc`, and
CI version-file consumption agree. Engine range boundary checks passed.
- DNS NUL regression: old local 24.21.0 sent a query for both embedded-NUL and
trailing-NUL names. Patched 24.22.1 rejected both with ERR_INVALID_ARG_VALUE
before any query. Valid local DNS and Brotli round trips passed on macOS,
Linux arm64, and Linux amd64. Other upstream CVEs were not individually reproduced.
- `pnpm --filter @forge/api test src/tests/judging-schedule/solver.test.ts`:
passed, 10 tests. Native Sharp PNG encode/decode passed.
- `pnpm format`: passed, 24 tasks. Targeted Prettier check also covers root JSON,
setup docs, and this feature bundle.
- `TURBO_FORCE=true TURBO_CONCURRENCY=1 pnpm typecheck`: passed, 33 tasks,
no Turbo cache hits. Validation used the verified 24.22.1 binary via PATH.
- `TURBO_FORCE=true TURBO_CONCURRENCY=1 pnpm lint`: passed, 31 tasks, no Turbo
cache hits; existing warnings and no lint errors.
- Docker tools-stage build passed; its isolated container reports Node 24.22.1,
pnpm 9.12.1, and Turbo 2.8.11. Full app container builds were cancelled with the
initial concurrent attempts and were not completed. Linux base/runtime behavior
and the shared tools stage were validated; final application images and running
production containers still require operator rollout verification.
- Initial concurrent lint/typecheck/container attempts were explicitly cancelled
because of host memory pressure; they are not recorded as passing checks.
- Full build passed, 21 tasks, no Turbo cache hits. Exact command, with the verified
Node 24.22.1 binary on PATH:
`CI=1 TURBO_FORCE=true TURBO_CONCURRENCY=1 pnpm exec dotenv -e .env.example -- pnpm build --env-mode=loose --concurrency=1`.
Example values were injected into the build process; existing env files were not
changed. The operator's default local Node installation was not upgraded.
- `pnpm audit --json`: exited 1; metadata reports 6 critical, 69 high,
77 moderate, and 18 low vulnerability counts. The advisory dictionary contains
152 distinct records across 45 packages. Critical records involve Next.js and
Handlebars. These registry matches were not individually reproduced; the report
and raw JSON are retained separately outside the repository. Dependencies remain
unchanged at the user's request.

## Open questions and links

- Actual production runtime inventory and rollout require operator verification.
- The local DNS regression script, dependency report, and raw audit JSON were
retained through Codex Security artifact storage and linked in the handoff.
- No issue/PR was supplied or created; no commit or push will be performed.
- Source: https://nodejs.org/en/blog/vulnerability/october-2026-security-releases
25 changes: 25 additions & 0 deletions .forge/features/node-security-october-2026/test-cases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# October 2026 Node.js Security Update Test Cases

## Runtime invariant

- `.nvmrc`, root engines, and all 18 Node stages agree on patched 24.22.1.
- CI reads `.nvmrc`; no independent old CI version bypasses the pin.
- Engine range accepts 24.22.1 and newer 24.x; rejects 20.x, 22.x, 24.22.0,
EOL 25.x, and early unpatched 26.x. Other supported majors are outside the
deliberately chosen, production-aligned 24.x compatibility contract.
- Docker registry publishes the exact tag for Linux amd64 and arm64.

## Security and legitimate controls

- Compare DNS NUL handling with the prior local runtime and patched runtime.
- Valid DNS behavior and a Brotli round trip remain functional.
- Exercise real OR-Tools scheduling and Sharp encode/decode under patched Node.
- Do not claim a CVE reproducer for vulnerabilities without a verified trigger.
Upstream release identity is the evidence for the remaining runtime fixes.

## Repository and container checks

Run `pnpm format`, `pnpm lint`, `pnpm typecheck`, and `pnpm build` with the patched
Node at the front of PATH and Turbo cache bypassed. Exercise representative static,
standalone Next, and bot images when practical. Record exact failures and skipped
checks in `status.md`; do not expand this patch to unrelated baseline failures.
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v25.6.1
v24.22.1
4 changes: 2 additions & 2 deletions deploy/dockerfiles/blade.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7

FROM node:24-bookworm-slim AS tools
FROM node:24.22.1-bookworm-slim AS tools
RUN npm install --global pnpm@9.12.1 turbo@2.8.11
WORKDIR /app

Expand Down Expand Up @@ -43,7 +43,7 @@ RUN test -n "${FORGE_BUILD_PLACEHOLDER}" \
&& test -n "${NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY}" \
&& pnpm --filter @forge/blade build

FROM node:24-bookworm-slim AS runner
FROM node:24.22.1-bookworm-slim AS runner
RUN apt-get update \
&& apt-get install --yes --no-install-recommends ca-certificates curl \
&& rm -rf /var/lib/apt/lists/*
Expand Down
2 changes: 1 addition & 1 deletion deploy/dockerfiles/bloomknights.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7

FROM node:24-bookworm-slim AS tools
FROM node:24.22.1-bookworm-slim AS tools
RUN npm install --global pnpm@9.12.1 turbo@2.8.11
WORKDIR /app

Expand Down
2 changes: 1 addition & 1 deletion deploy/dockerfiles/club.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7

FROM node:24-bookworm-slim AS tools
FROM node:24.22.1-bookworm-slim AS tools
RUN npm install --global pnpm@9.12.1 turbo@2.8.11
WORKDIR /app

Expand Down
4 changes: 2 additions & 2 deletions deploy/dockerfiles/cron.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7

FROM node:24-bookworm-slim AS tools
FROM node:24.22.1-bookworm-slim AS tools
RUN npm install --global pnpm@9.12.1 turbo@2.8.11
WORKDIR /app

Expand All @@ -18,7 +18,7 @@ COPY --from=pruner /app/out/full/ .
RUN --mount=type=cache,id=forge-pnpm-store,target=/pnpm/store,sharing=locked \
pnpm --config.node-linker=isolated --filter @forge/cron deploy --prod /prod

FROM node:24-bookworm-slim AS runner
FROM node:24.22.1-bookworm-slim AS runner
ENV NODE_ENV=production
WORKDIR /app
COPY --from=builder --chown=node:node /prod/ ./
Expand Down
2 changes: 1 addition & 1 deletion deploy/dockerfiles/gemiknights.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7

FROM node:24-bookworm-slim AS tools
FROM node:24.22.1-bookworm-slim AS tools
RUN npm install --global pnpm@9.12.1 turbo@2.8.11
WORKDIR /app

Expand Down
4 changes: 2 additions & 2 deletions deploy/dockerfiles/guild.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7

FROM node:24-bookworm-slim AS tools
FROM node:24.22.1-bookworm-slim AS tools
RUN npm install --global pnpm@9.12.1 turbo@2.8.11
WORKDIR /app

Expand Down Expand Up @@ -39,7 +39,7 @@ RUN test -n "${FORGE_BUILD_PLACEHOLDER}" \
&& test -n "${NEXT_PUBLIC_TRPC_URL}" \
&& pnpm --filter @forge/guild build

FROM node:24-bookworm-slim AS runner
FROM node:24.22.1-bookworm-slim AS runner
RUN apt-get update \
&& apt-get install --yes --no-install-recommends ca-certificates curl \
&& rm -rf /var/lib/apt/lists/*
Expand Down
2 changes: 1 addition & 1 deletion deploy/dockerfiles/kh2020.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7

FROM node:24-bookworm-slim AS tools
FROM node:24.22.1-bookworm-slim AS tools
RUN npm install --global pnpm@9.12.1 turbo@2.8.11
WORKDIR /app

Expand Down
2 changes: 1 addition & 1 deletion deploy/dockerfiles/kh2021.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7

FROM node:24-bookworm-slim AS tools
FROM node:24.22.1-bookworm-slim AS tools
RUN npm install --global pnpm@9.12.1 turbo@2.8.11
WORKDIR /app

Expand Down
2 changes: 1 addition & 1 deletion deploy/dockerfiles/kh2023.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7

FROM node:24-bookworm-slim AS tools
FROM node:24.22.1-bookworm-slim AS tools
RUN npm install --global pnpm@9.12.1 turbo@2.8.11
WORKDIR /app

Expand Down
2 changes: 1 addition & 1 deletion deploy/dockerfiles/kh2024.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7

FROM node:24-bookworm-slim AS tools
FROM node:24.22.1-bookworm-slim AS tools
RUN npm install --global pnpm@9.12.1 turbo@2.8.11
WORKDIR /app

Expand Down
2 changes: 1 addition & 1 deletion deploy/dockerfiles/kh8.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7

FROM node:24-bookworm-slim AS tools
FROM node:24.22.1-bookworm-slim AS tools
RUN npm install --global pnpm@9.12.1 turbo@2.8.11
WORKDIR /app

Expand Down
4 changes: 2 additions & 2 deletions deploy/dockerfiles/kh9.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7

FROM node:24-bookworm-slim AS tools
FROM node:24.22.1-bookworm-slim AS tools
RUN npm install --global pnpm@9.12.1 turbo@2.8.11
WORKDIR /app

Expand All @@ -26,7 +26,7 @@ RUN test -n "${BLADE_URL}" \
&& test -n "${KHIX_HACKER_PORTAL_ORIGIN}" \
&& pnpm --filter @forge/2026 build

FROM node:24-bookworm-slim AS runner
FROM node:24.22.1-bookworm-slim AS runner
RUN apt-get update \
&& apt-get install --yes --no-install-recommends ca-certificates curl \
&& rm -rf /var/lib/apt/lists/*
Expand Down
4 changes: 2 additions & 2 deletions deploy/dockerfiles/tk.Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# syntax=docker/dockerfile:1.7

FROM node:24-bookworm-slim AS tools
FROM node:24.22.1-bookworm-slim AS tools
RUN npm install --global pnpm@9.12.1 turbo@2.8.11
WORKDIR /app

Expand All @@ -18,7 +18,7 @@ COPY --from=pruner /app/out/full/ .
RUN --mount=type=cache,id=forge-pnpm-store,target=/pnpm/store,sharing=locked \
pnpm --config.node-linker=isolated --filter @forge/tk deploy --prod /prod

FROM node:24-bookworm-slim AS runner
FROM node:24.22.1-bookworm-slim AS runner
ENV NODE_ENV=production
WORKDIR /app
COPY --from=builder --chown=node:node /prod/ ./
Expand Down
4 changes: 2 additions & 2 deletions docs/GETTING-STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This guide will help you set up Forge locally and make your first contribution.

Before you begin, ensure you have the following installed:

- [Node.js LTS](https://nodejs.org/en/download/) (v20.16.0 or higher)
- [Node.js LTS](https://nodejs.org/en/download/) (v24.22.1 or a newer 24.x release; use `.nvmrc` to match CI)
- [pnpm](https://pnpm.io/installation) (v9.6.0 or higher)
- [Docker Desktop](https://docs.docker.com/get-docker/)
- A POSIX/Unix terminal (Linux, macOS, or WSL for Windows users)
Expand Down Expand Up @@ -266,7 +266,7 @@ If a port is already in use, stop the conflicting process or change the port in
Make sure you're using the correct versions:

```bash
node --version # Should be >= 20.16.0
node --version # Should match .nvmrc (v24.22.1), or a newer 24.x release
pnpm --version # Should be >= 9.6.0
```

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "forge",
"private": true,
"engines": {
"node": ">=20.16.0",
"node": "^24.22.1",
"pnpm": "^9.6.0"
},
"packageManager": "pnpm@9.12.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
provisionDisposableDatabase,
} from "@forge/db/testing";

import { trackTestPoolShutdown } from "../support/close-test-pool";
import { permissionBitstring } from "../support/permissions";

type DatabaseClient = typeof import("@forge/db/client").db;
Expand All @@ -25,6 +26,7 @@ describe.skipIf(!canRunDatabaseTests())(
() => {
let disposable: DisposableDatabase | undefined;
let client: DatabaseClient;
let closePool: (() => Promise<void>) | undefined;
let audit: AuditSchemas;
let auth: AuthSchemas;
let knightHacks: KnightHacksSchemas;
Expand All @@ -51,6 +53,7 @@ describe.skipIf(!canRunDatabaseTests())(
process.env.DATABASE_URL = disposable.url;

({ db: client } = await import("@forge/db/client"));
closePool = trackTestPoolShutdown(client.$client);
audit = await import("@forge/db/schemas/audit");
auth = await import("@forge/db/schemas/auth");
knightHacks = await import("@forge/db/schemas/knight-hacks");
Expand Down Expand Up @@ -96,7 +99,7 @@ describe.skipIf(!canRunDatabaseTests())(
}, 120_000);

afterAll(async () => {
await client.$client.end().catch(() => undefined);
await closePool?.();
await disposable?.drop();
}, 30_000);

Expand Down
Loading