CVE-2026-111497 ("Node2Shell") is an out-of-bounds write in the Brotli
decoder Node bundles; it was fixed on 2026-10-21 in 22.23.4, 24.22.1 and
26.12.1. Both images were on node:20-alpine, and Node 20 is past end of
life, so it gets no fix.
The site is not reachable through the main path (Express 4 / body-parser
1.20 refuse a `br` request body with 415, and nothing calls the zlib
Brotli API), but fetch() decodes Brotli responses and the runtime itself
was unpatched. Pin the exact patched version so a floating tag cannot
hold an old build, and run CI on the same version the image ships.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
125 lines
5.8 KiB
YAML
125 lines
5.8 KiB
YAML
# Gate every pull request into `main` or `edge` on a fast, DB-free check suite so
|
|
# a broken build or failing test can't reach either integration branch. Complements
|
|
# build-images.yml, which runs only AFTER merge (on push to main) to publish
|
|
# images — this one runs BEFORE merge.
|
|
#
|
|
# `edge` is listed as well as `main` because long workstreams land phase by phase
|
|
# on `edge` and reach `main` as a single cutover (the module system, protocol v3).
|
|
# With `branches: [main]` alone, every one of those phase PRs merges with NO checks
|
|
# at all and the entire workstream runs blind until the cutover — which is exactly
|
|
# what happened to the nine Android M12 phase PRs in that repo. A branch that
|
|
# accumulates work for weeks needs the gate more than `main` does, not less.
|
|
#
|
|
# Enforcement (one-time, in the Gitea UI):
|
|
# Repository Settings → Branches → Branch Protection (rule for `main`)
|
|
# • Enable Status Check
|
|
# • Status check patterns: PR Checks / *
|
|
# Note: Gitea only lists a context in its dropdown after it has reported once,
|
|
# so let this workflow run on one PR first. The `PR Checks / *` glob matches
|
|
# without needing the dropdown.
|
|
# The workflow now RUNS on PRs into `edge` too, but running is not enforcing:
|
|
# blocking a red phase PR needs its own protection rule for `edge`, with the
|
|
# same `PR Checks / *` pattern. Without one the checks report and merging stays
|
|
# possible anyway.
|
|
#
|
|
# Runner: reuses the existing self-hosted `ubuntu-latest` runner. These jobs need
|
|
# only Node (no Docker socket), and the server tests stub their models + point the
|
|
# DB pool at a dead port, so no MariaDB service is required.
|
|
|
|
name: PR Checks
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [main, edge]
|
|
|
|
# A newer push to the same PR cancels the in-flight run.
|
|
concurrency:
|
|
group: pr-checks-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
server-tests:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22.23.4
|
|
cache: npm
|
|
cache-dependency-path: server/package-lock.json
|
|
- name: Check core names no module identifier
|
|
# Phase 3's acceptance criterion 1 (MODULE_API.md §5.2): core must not
|
|
# name a module's files, import them, route to them, or declare its
|
|
# symbols. Before `npm ci`, deliberately — it is plain Node over
|
|
# server/ and client/ source with no dependency of its own, so putting it
|
|
# first makes a boundary break the first thing a reviewer sees instead of
|
|
# something found under a pile of unrelated failures, and it costs
|
|
# nothing when it passes.
|
|
run: npm run check:modules
|
|
- name: Check the engagement subsystem names no external host
|
|
# ENGAGEMENT.md §3.2 rule 4 — no transport may ship a default host,
|
|
# endpoint or sender. Dependency-free and runs before the install for the
|
|
# same reason as the check above: a phone-home is a design break, not a
|
|
# test failure, and it should be the first thing a reviewer sees.
|
|
run: npm run check:hosts
|
|
- name: Install server deps
|
|
run: npm ci --prefix server
|
|
- name: Run server tests
|
|
run: npm test --prefix server
|
|
|
|
- name: Check the route manifest is current
|
|
# The URL surface is frozen while the routers are carved up by capability
|
|
# (docs/website/API_V2_PLAN.md § Phase 2). Regenerating from the live Express
|
|
# stack and diffing proves a "mechanical" refactor moved no URL. A PR that
|
|
# really does change one has to commit the new manifest, putting it in front
|
|
# of a reviewer instead of letting it pass silently.
|
|
run: npm run routes:manifest --prefix server -- --check
|
|
|
|
- name: Check the engagement trigger manifest is current
|
|
# ENGAGEMENT.md 4.3 property 4 - the same mechanism as the route manifest
|
|
# above, for the event contract instead of the URL surface. A trigger
|
|
# declaration is what a stored template interpolates and what a stored
|
|
# rule is written against, so renaming a variable or widening a ceiling
|
|
# breaks them silently, at send time, in mail someone already received.
|
|
# Regenerating and diffing makes that change something a reviewer reads.
|
|
run: npm run engagement:manifest --prefix server -- --check
|
|
|
|
client-build:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22.23.4
|
|
cache: npm
|
|
cache-dependency-path: client/package-lock.json
|
|
- name: Install client deps
|
|
run: npm ci --prefix client
|
|
- name: Run client tests
|
|
# Pure-logic unit tests on Node's built-in runner (no browser/DOM).
|
|
run: npm test --prefix client
|
|
- name: Build client
|
|
run: npm run build --prefix client
|
|
|
|
bot-tests:
|
|
# The install still runs first and still catches a broken or out-of-sync
|
|
# lockfile before it ships in the bot image — that was this job's whole
|
|
# purpose until phase 7 (TEAMS.md §7.1) put real logic in the bot: it now
|
|
# pulls slash-command definitions from the app, merges them into the
|
|
# whole-set PUT, and runs the defer→dispatch→edit path. None of that is
|
|
# reachable from the server suite, and phases 8 and 9 add more of it.
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: 22.23.4
|
|
cache: npm
|
|
cache-dependency-path: bot/package-lock.json
|
|
- name: Install bot deps
|
|
run: npm ci --prefix bot
|
|
- name: Run bot tests
|
|
# Node's built-in runner, no browser and no Discord connection — the
|
|
# interaction is a fake that records what was called on it.
|
|
run: npm test --prefix bot
|