diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 0d1c51a7c..2f4893e5e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -151,6 +151,32 @@ jobs: docker volume rm ep7718-plugins >/dev/null 2>&1 || true [ "$ok" = "1" ] || exit 1 + - + # Regression test: Etherpad container must boot without external + # network access (e.g., air-gapped host or restrictive runtime policy). + name: Regression — boot with disabled network (--network none) + working-directory: etherpad + run: | + docker run --rm -d \ + --network none \ + --name test-offline ${{ env.TEST_TAG }} + docker logs -f test-offline & + ok=0 + for i in $(seq 1 60); do + status=$(docker container inspect -f '{{.State.Health.Status}}' test-offline 2>/dev/null) || { + echo "container exited prematurely while offline" + docker logs test-offline || true + break + } + case ${status} in + healthy) ok=1; echo "offline boot healthy after $((i*2))s"; break;; + starting) sleep 2;; + *) echo "unexpected status: ${status}"; docker logs test-offline || true; break;; + esac + done + docker rm -f test-offline >/dev/null 2>&1 || true + [ "$ok" = "1" ] || exit 1 + build-test-local-plugin: # Regression coverage for #7687: the Docker image's # `bin/installLocalPlugins.sh` step runs as the `etherpad` user and diff --git a/.github/workflows/downstream-smoke.yml b/.github/workflows/downstream-smoke.yml new file mode 100644 index 000000000..c565a22c5 --- /dev/null +++ b/.github/workflows/downstream-smoke.yml @@ -0,0 +1,108 @@ +name: Downstream smoke + +# Boots a real Etherpad from the PR and verifies the separate downstream clients +# (Rust terminal editor, Node CLI, desktop/mobile) still round-trip against it. +# Phase 1 lands the boot/healthcheck/self-check/teardown harness + manifest; the +# per-client matrix activates as each client flips `enabled:true` in clients.json. + +on: + pull_request: + paths-ignore: + - "doc/**" + - "docs/**" + schedule: + - cron: '0 4 * * *' # nightly against the default branch + +permissions: + contents: read + +jobs: + smoke: + name: Boot + downstream clients + runs-on: ubuntu-latest + timeout-minutes: 25 + env: + PNPM_HOME: ~/.pnpm-store + APIKEY: downstream-smoke-key + steps: + - name: Checkout core (PR) + uses: actions/checkout@v6 + + - uses: actions/cache@v5 + name: Cache pnpm store + with: + path: ${{ env.PNPM_HOME }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - uses: pnpm/action-setup@v6 + name: Install pnpm + with: + run_install: false + + - name: Use Node.js + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: pnpm + + - name: Install dependencies + run: pnpm i --frozen-lockfile + + - name: Boot Etherpad on :9003 (apikey auth) + run: | + # The template ships a literal "port": 9001 and sso auth; rewrite both. + # (PORT env is ignored once the settings file specifies a port.) + sed -e 's#"port": 9001,#"port": 9003,#' \ + -e 's#${AUTHENTICATION_METHOD:sso}#apikey#' \ + settings.json.template > settings.json + # Fail fast if the template format drifted and sed silently no-op'd. + grep -q '"port": 9003,' settings.json \ + || { echo "::error::port rewrite failed — settings.json.template format changed"; exit 1; } + grep -q '"authenticationMethod": "apikey"' settings.json \ + || { echo "::error::auth rewrite failed — settings.json.template format changed"; exit 1; } + printf '%s' "$APIKEY" > APIKEY.txt + pnpm run prod > /tmp/ep.log 2>&1 & + echo $! > /tmp/ep.pid + echo "booted pid $(cat /tmp/ep.pid)" + + - name: Wait for healthcheck + run: | + for i in $(seq 1 60); do + if curl -fsS "http://localhost:9003/api/" >/dev/null 2>&1; then + echo "server up after ${i} tries"; exit 0 + fi + sleep 2 + done + echo "::error::server did not come up"; tail -50 /tmp/ep.log; exit 1 + + - name: Self-check — authenticated create + read roundtrip + run: | + K="$APIKEY" + curl -fsS "http://localhost:9003/api/1/createPad?apikey=${K}&padID=smoke&text=hi%0A" | tee /tmp/create.json + grep -q '"code":0' /tmp/create.json + curl -fsS "http://localhost:9003/api/1/getText?apikey=${K}&padID=smoke" | tee /tmp/get.json + grep -q '"text":"hi' /tmp/get.json + + - name: Generate canonical wire-vectors + run: cd src && pnpm run vectors:gen + + # Rust toolchain for the `rust`-kind client (etherpad-pad). Other kinds + # use the node+pnpm already set up above. + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Run enabled downstream clients + env: + SMOKE_URL: http://localhost:9003 + SMOKE_APIKEY: ${{ env.APIKEY }} + run: bash src/tests/downstream/run-clients.sh + + - name: Teardown (by PID, never pkill) + if: always() + run: | + if [ -f /tmp/ep.pid ]; then + kill "$(cat /tmp/ep.pid)" 2>/dev/null || true + echo "killed $(cat /tmp/ep.pid)" + fi diff --git a/CHANGELOG.md b/CHANGELOG.md index ae4622048..de9c171ef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +# 3.3.1 + +3.3.1 is a small bug-fix and hardening follow-up to 3.3.0. It closes a stored-XSS vector in the numbered-list `start` attribute, hardens the database layer so a dropped connection to PostgreSQL / Redis / RethinkDB no longer crashes the process (via ueberdb2 6.1.9), and fixes a handful of pad and admin regressions — the iOS dark-mode status bar, the settings language dropdown, the pad-deletion modal under `allowPadDeletionByAllUsers`, and a single unreadable pad blanking the admin Manage-pads list. + +### Security + +- **Pad editor — escape and integer-coerce the numbered-list `start` attribute (GHSA-f7h5-v9hm-548j, #7937).** A crafted `
    ` value flowed unescaped into `domline.ts`, a distinct client-side sink from the export-path fix in 3.3.0's #7905. The value is now integer-coerced and HTML-escaped before it reaches the DOM. A jsdom regression test covers the sink. + +### Notable fixes + +- **Skin — paint the root canvas so iOS dark mode has no white status bar (#7606 / #7931).** iOS Safari paints the top safe area from the `html` root background, which `theme-color` (an Android address-bar hint) does not affect, so dark-mode pads showed a white status-bar strip on iOS. Colibris now sets the root background and `color-scheme` so the safe area matches the editor. +- **Settings — show the detected language in the dropdown (#7925 / #7928).** The settings language `