diff --git a/.docker/docker-entrypoint.sh b/.docker/docker-entrypoint.sh index df71b4b99..21e5e0e3e 100755 --- a/.docker/docker-entrypoint.sh +++ b/.docker/docker-entrypoint.sh @@ -414,9 +414,18 @@ if [ ! -f "$bin_path" ]; then fi if [ "$DEBUG_FLAG" = "1" ]; then + # Verify that /usr/local/bin/dlv is a real Delve binary, not the production stub + # (production images ship a shell stub that exits 1 to satisfy the COPY instruction + # without embedding the vulnerable golang.org/x/sys < v0.27.0 — GO-2026-5024). + # Real Delve exits 0 on `dlv version`; the stub exits 1. + if ! /usr/local/bin/dlv version >/dev/null 2>&1; then + echo "Note: Delve not available in this image (production build, GO-2026-5024 mitigation)." + echo " Running Charon directly. To enable remote debugging, rebuild with:" + echo " docker build --build-arg BUILD_DEBUG=1 ..." + run_as_charon "$bin_path" & # Check if binary has debug symbols (required for Delve) # objdump -h lists section headers; .debug_info is present if DWARF symbols exist - if command -v objdump >/dev/null 2>&1; then + elif command -v objdump >/dev/null 2>&1; then if ! objdump -h "$bin_path" 2>/dev/null | grep -q '\.debug_info'; then echo "⚠️ WARNING: Binary lacks debug symbols (DWARF info stripped)." echo " Delve debugging will NOT work with this binary." diff --git a/.github/renovate.json b/.github/renovate.json index 0c9a6d876..af11eccdc 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -225,6 +225,19 @@ "datasourceTemplate": "golang-version", "versioningTemplate": "semver" }, + { + "customType": "regex", + "description": "Track NODE_VERSION in Actions workflows", + "managerFilePatterns": [ + "/^\\.github/workflows/.*\\.yml$/" + ], + "matchStrings": [ + "NODE_VERSION: ['\"]?(?[\\d\\.]+)['\"]?" + ], + "depNameTemplate": "node", + "datasourceTemplate": "node-version", + "versioningTemplate": "node" + }, { "customType": "regex", "description": "Track GO_VERSION in Actions workflows", @@ -414,18 +427,9 @@ "groupSlug": "go-non-major" }, { - "description": "Group Go github-tags fallback updates from Dockerfile custom manager into Go non-major PR", + "description": "Group NPM non-major updates into one PR", "matchDatasources": [ - "github-tags" - ], - "matchManagers": [ - "custom.regex" - ], - "matchFileNames": [ - "Dockerfile" - ], - "matchPackageNames": [ - "jackc/pgx" + "npm" ], "matchUpdateTypes": [ "minor", @@ -433,13 +437,16 @@ "pin", "digest" ], - "groupName": "go-non-major", - "groupSlug": "go-non-major" + "groupName": "npm-non-major", + "groupSlug": "npm-non-major" }, { - "description": "Group NPM non-major updates into one PR", - "matchDatasources": [ - "npm" + "description": "Dockerfile ARG trackers (any datasource) group under Dockerfile, not Go/NPM — placed after the datasource group rules so it wins", + "matchManagers": [ + "custom.regex" + ], + "matchFileNames": [ + "Dockerfile" ], "matchUpdateTypes": [ "minor", @@ -447,8 +454,11 @@ "pin", "digest" ], - "groupName": "npm-non-major", - "groupSlug": "npm-non-major" + "groupName": "dockerfile-non-major", + "groupSlug": "dockerfile-non-major", + "addLabels": [ + "dockerfile" + ] }, { "description": "Development branch: Auto-merge non-major updates after proven stable", diff --git a/.github/skills/test-e2e-playwright-coverage-scripts/run.sh b/.github/skills/test-e2e-playwright-coverage-scripts/run.sh index 1910e7d8b..de9cf814e 100755 --- a/.github/skills/test-e2e-playwright-coverage-scripts/run.sh +++ b/.github/skills/test-e2e-playwright-coverage-scripts/run.sh @@ -158,7 +158,7 @@ start_vite() { # Ensure dependencies are installed if [[ ! -d "node_modules" ]]; then log_info "Installing frontend dependencies..." - npm ci --silent + npm ci --silent --ignore-scripts fi # Start Vite in background with explicit port diff --git a/.github/workflows/codecov-upload.yml b/.github/workflows/codecov-upload.yml index 47689dfb5..2c8b1621d 100644 --- a/.github/workflows/codecov-upload.yml +++ b/.github/workflows/codecov-upload.yml @@ -174,7 +174,7 @@ jobs: - name: Install dependencies working-directory: frontend - run: npm ci + run: npm ci --ignore-scripts - name: Run frontend tests and coverage working-directory: ${{ github.workspace }} diff --git a/.github/workflows/e2e-tests-split.yml b/.github/workflows/e2e-tests-split.yml index c2c0dbc49..b5c889b3c 100644 --- a/.github/workflows/e2e-tests-split.yml +++ b/.github/workflows/e2e-tests-split.yml @@ -82,7 +82,7 @@ on: pull_request: env: - NODE_VERSION: '20' + NODE_VERSION: '24.12.0' GO_VERSION: '1.26.4' GOTOOLCHAIN: local DOCKERHUB_REGISTRY: docker.io @@ -166,7 +166,7 @@ jobs: - name: Install dependencies if: steps.resolve-image.outputs.image_source == 'build' - run: npm ci + run: npm ci --ignore-scripts - name: Set up Docker Buildx if: steps.resolve-image.outputs.image_source == 'build' @@ -184,6 +184,7 @@ jobs: tags: ${{ steps.resolve-image.outputs.image_tag }} cache-from: type=gha cache-to: type=gha,mode=max + no-cache-filters: caddy-builder,crowdsec-builder - name: Save Docker image if: steps.resolve-image.outputs.image_source == 'build' @@ -303,7 +304,7 @@ jobs: exit 1 - name: Install dependencies - run: npm ci + run: npm ci --ignore-scripts - name: Install Playwright Chromium run: | @@ -505,7 +506,7 @@ jobs: exit 1 - name: Install dependencies - run: npm ci + run: npm ci --ignore-scripts - name: Install Playwright Chromium (required by security-tests dependency) run: | @@ -715,7 +716,7 @@ jobs: exit 1 - name: Install dependencies - run: npm ci + run: npm ci --ignore-scripts - name: Install Playwright Chromium (required by security-tests dependency) run: | @@ -952,7 +953,7 @@ jobs: exit 1 - name: Install dependencies - run: npm ci + run: npm ci --ignore-scripts - name: Install Playwright Chromium run: | @@ -1190,7 +1191,7 @@ jobs: exit 1 - name: Install dependencies - run: npm ci + run: npm ci --ignore-scripts - name: Install Playwright Chromium (required by security-tests dependency) run: | @@ -1436,7 +1437,7 @@ jobs: exit 1 - name: Install dependencies - run: npm ci + run: npm ci --ignore-scripts - name: Install Playwright Chromium (required by security-tests dependency) run: | diff --git a/.github/workflows/quality-checks.yml b/.github/workflows/quality-checks.yml index 841e4c529..b972ac98c 100644 --- a/.github/workflows/quality-checks.yml +++ b/.github/workflows/quality-checks.yml @@ -310,7 +310,7 @@ jobs: - name: Install dependencies working-directory: frontend - run: npm ci + run: npm ci --ignore-scripts - name: Run frontend tests and coverage id: frontend-tests diff --git a/.github/workflows/release-goreleaser.yml b/.github/workflows/release-goreleaser.yml index beaae3df9..84d1ca367 100644 --- a/.github/workflows/release-goreleaser.yml +++ b/.github/workflows/release-goreleaser.yml @@ -62,7 +62,7 @@ jobs: # Inject version into frontend build from tag (if present) VERSION=${GITHUB_REF#refs/tags/} echo "VITE_APP_VERSION=${VERSION}" >> "$GITHUB_ENV" - npm ci + npm ci --ignore-scripts npm run build - name: Install Cross-Compilation Tools (Zig) diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml index 7cb09f114..640dfa125 100644 --- a/.github/workflows/renovate.yml +++ b/.github/workflows/renovate.yml @@ -33,7 +33,7 @@ jobs: go-version-file: backend/go.mod - name: Run Renovate - uses: renovatebot/github-action@693b9ef15eec82123529a37c782242f091365961 # v46.1.14 + uses: renovatebot/github-action@8217b3fc286df088d7c27f3255fe8414463bc0fd # v46.1.15 with: configurationFile: .github/renovate.json token: ${{ secrets.RENOVATE_TOKEN || secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/weekly-nightly-promotion.yml b/.github/workflows/weekly-nightly-promotion.yml index 8e739f0d7..1b03fcbec 100644 --- a/.github/workflows/weekly-nightly-promotion.yml +++ b/.github/workflows/weekly-nightly-promotion.yml @@ -443,11 +443,12 @@ jobs: const nightlyHeadSha = nightlyBranch.commit.sha; core.info(`Current nightly HEAD for dispatch fallback: ${nightlyHeadSha}`); + const prNumber = '${{ needs.create-promotion-pr.outputs.pr_number }}'; const requiredWorkflows = [ { id: 'e2e-tests-split.yml' }, { id: 'codeql.yml' }, { id: 'codecov-upload.yml', inputs: { run_backend: 'true', run_frontend: 'true' } }, - { id: 'security-pr.yml' }, + { id: 'security-pr.yml', inputs: { pr_number: prNumber } }, { id: 'supply-chain-verify.yml' }, ]; diff --git a/Dockerfile b/Dockerfile index 2446bef89..8dba5104b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ ARG BUILD_DEBUG=0 ARG GO_VERSION=1.26.4 # renovate: datasource=docker depName=alpine versioning=docker -ARG ALPINE_IMAGE=alpine:3.23.4@sha256:5b10f432ef3da1b8d4c7eb6c487f2f5a8f096bc91145e68878dd4a5019afde11 +ARG ALPINE_IMAGE=alpine:3.24.0@sha256:a2d49ea686c2adfe3c992e47dc3b5e7fa6e6b5055609400dc2acaeb241c829f4 # ---- Shared CrowdSec Version ---- # renovate: datasource=github-releases depName=crowdsecurity/crowdsec @@ -22,14 +22,14 @@ ARG CROWDSEC_VERSION=1.7.8 ARG CROWDSEC_RELEASE_SHA256=704e37121e7ac215991441cef0d8732e33fa3b1a2b2b88b53a0bfe5e38f863bd # ---- Shared Go Security Patches ---- -# renovate: datasource=go depName=github.com/expr-lang/expr +# renovate: datasource=github-tags depName=expr-lang/expr extractVersion=^v(?.+)$ ARG EXPR_LANG_VERSION=1.17.8 # renovate: datasource=go depName=golang.org/x/net -ARG XNET_VERSION=0.55.0 +ARG XNET_VERSION=0.56.0 # renovate: datasource=go depName=golang.org/x/crypto -ARG XCRYPTO_VERSION=0.52.0 +ARG XCRYPTO_VERSION=0.53.0 # renovate: datasource=npm depName=npm -ARG NPM_VERSION=11.16.0 +ARG NPM_VERSION=11.17.0 # Allow pinning Caddy version - Renovate will update this # Build the most recent Caddy 2.x release (keeps major pinned under v3). @@ -37,7 +37,7 @@ ARG NPM_VERSION=11.16.0 # avoid accidentally pulling a v3 major release. Renovate can still update # this ARG to a specific v2.x tag when desired. ## Try to build the requested Caddy v2.x tag (Renovate can update this ARG). -## If the requested tag isn't available, fall back to a known-good v2.11.3 build. +## If the requested tag isn't available, fall back to a known-good v2.11.4 build. # renovate: datasource=go depName=github.com/caddyserver/caddy/v2 ARG CADDY_VERSION=2.11.4 # renovate: datasource=go depName=github.com/caddyserver/caddy/v2 @@ -94,7 +94,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build \ # ---- Frontend Builder ---- # Build the frontend using the BUILDPLATFORM to avoid arm64 musl Rollup native issues # renovate: datasource=docker depName=node -FROM --platform=$BUILDPLATFORM node:24.16.0-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 AS frontend-builder +FROM --platform=$BUILDPLATFORM node:24.16.0-alpine3.24@sha256:fb71d01345f11b708a3553c66e7c74074f2d506400ea81973343d915cb64eef0 AS frontend-builder WORKDIR /app/frontend # Copy frontend package files @@ -118,7 +118,7 @@ RUN apk upgrade --no-cache && \ # hadolint ignore=DL3059 RUN npm install -g picomatch@4.0.4 --no-fund --no-audit -RUN npm ci +RUN npm ci --ignore-scripts # Copy frontend source and build COPY frontend/ ./ @@ -164,18 +164,32 @@ RUN set -eux; \ test -e "$LOADER"; \ fi -# Install Delve (cross-compile for target) -# Note: xx-go install puts binaries in /go/bin/TARGETOS_TARGETARCH/dlv if cross-compiling. -# We find it and move it to /go/bin/dlv so it's in a consistent location for the next stage. +# Install Delve (cross-compile for target) — debug builds only. +# Security: dlv is only installed when BUILD_DEBUG=1. Production images (BUILD_DEBUG=0, +# the default) receive a harmless stub so the unconditional COPY below still succeeds, +# but no Delve binary with golang.org/x/sys < v0.27.0 (GO-2026-5024) is shipped. +# When dlv IS needed, we build it inside a temporary module that pins +# golang.org/x/sys to the patched version used by the rest of the project. # renovate: datasource=go depName=github.com/go-delve/delve ARG DLV_VERSION=1.26.3 # hadolint ignore=DL3059,DL4006 -RUN CGO_ENABLED=0 xx-go install github.com/go-delve/delve/cmd/dlv@v${DLV_VERSION} && \ - DLV_PATH=$(find /go/bin -name dlv -type f | head -n 1) && \ - if [ -n "$DLV_PATH" ] && [ "$DLV_PATH" != "/go/bin/dlv" ]; then \ - mv "$DLV_PATH" /go/bin/dlv; \ - fi && \ - xx-verify /go/bin/dlv +RUN if [ "$BUILD_DEBUG" = "1" ]; then \ + echo "DEBUG build: installing Delve v${DLV_VERSION} with patched golang.org/x/sys..."; \ + mkdir -p /tmp/dlv-install && cd /tmp/dlv-install && \ + go mod init dlv_install && \ + go get golang.org/x/sys@v0.46.0 && \ + CGO_ENABLED=0 GOFLAGS="-mod=mod" xx-go install github.com/go-delve/delve/cmd/dlv@v${DLV_VERSION} && \ + DLV_PATH=$(find /go/bin -name dlv -type f | head -n 1) && \ + if [ -n "$DLV_PATH" ] && [ "$DLV_PATH" != "/go/bin/dlv" ]; then \ + mv "$DLV_PATH" /go/bin/dlv; \ + fi && \ + xx-verify /go/bin/dlv && \ + cd / && rm -rf /tmp/dlv-install; \ + else \ + echo "Production build: skipping Delve install (GO-2026-5024 mitigation)"; \ + printf '#!/bin/sh\necho "Delve not available in production builds. Rebuild with BUILD_DEBUG=1." >&2\nexit 1\n' \ + > /go/bin/dlv && chmod +x /go/bin/dlv; \ + fi # Copy Go module files COPY backend/go.mod backend/go.sum ./ @@ -466,7 +480,7 @@ RUN go get github.com/expr-lang/expr@v${EXPR_LANG_VERSION} && \ # renovate: datasource=go depName=github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream go get github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream@v1.7.13 && \ # renovate: datasource=go depName=github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs - go get github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs@v1.75.1 && \ + go get github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs@v1.75.2 && \ go get github.com/aws/aws-sdk-go-v2/service/kinesis@v1.43.7 && \ go get github.com/aws/aws-sdk-go-v2/service/s3@v1.102.1 && \ # CVE-2026-32952: go-ntlmssp DoS via malicious NTLM challenge response @@ -477,7 +491,7 @@ RUN go get github.com/expr-lang/expr@v${EXPR_LANG_VERSION} && \ # Affects /usr/local/bin/crowdsec and /usr/local/bin/cscli (CrowdSec embeds quic-go v0.57.0). # Fix available at v0.59.1. Caddy already resolves v0.59.1 through its own graph. # renovate: datasource=go depName=github.com/quic-go/quic-go - go get github.com/quic-go/quic-go@v0.59.1 && \ + go get github.com/quic-go/quic-go@v0.60.0 && \ # buger/jsonparser Delete() panic via negative slice index on malformed JSON. # Fix available at v1.2.0. # renovate: datasource=go depName=github.com/buger/jsonparser @@ -579,7 +593,7 @@ SHELL ["/bin/ash", "-o", "pipefail", "-c"] # Note: In production, users should provide their own MaxMind license key # This uses the publicly available GeoLite2 database # In CI, timeout quickly rather than retrying to save build time -ARG GEOLITE2_COUNTRY_SHA256=abce3a42f4f6bfb2c90cded582341da6764f5e152782ce6c832bc8fa1d873778 +ARG GEOLITE2_COUNTRY_SHA256=11b88595d026953920668d91f6d531057b397f05170237fc98a13a8b051ab861 RUN mkdir -p /app/data/geoip && \ if [ "$CI" = "true" ] || [ "$CI" = "1" ]; then \ echo "⏱️ CI detected - quick download (10s timeout, no retries)"; \ @@ -664,7 +678,11 @@ RUN chmod +x /usr/local/bin/install_hub_items.sh /usr/local/bin/register_bouncer # Copy Go binary from backend builder COPY --from=backend-builder /app/backend/charon /app/charon RUN ln -s /app/charon /app/cpmp || true -# Copy Delve debugger (xx-go install places it in /go/bin) +# Copy Delve stub/binary from backend-builder. +# Security (GO-2026-5024): production builds (BUILD_DEBUG=0) receive a harmless shell +# stub that prints an error and exits 1 — no vulnerable golang.org/x/sys v0.26.0 binary +# is present in production images. Debug builds (BUILD_DEBUG=1) receive the real dlv +# compiled against golang.org/x/sys v0.46.0 (patched). COPY --from=backend-builder /go/bin/dlv /usr/local/bin/dlv # Copy frontend build from frontend builder diff --git a/Makefile b/Makefile index 5ec9bb9e7..bc1f2707f 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ install: @echo "Installing backend dependencies..." cd backend && go mod download @echo "Installing frontend dependencies..." - cd frontend && npm install + cd frontend && npm install --ignore-scripts # Install Go development tools install-tools: diff --git a/SECURITY.md b/SECURITY.md index 1fde07e29..9059c435c 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -241,13 +241,13 @@ Charon users is negligible since the vulnerable code path is not exercised. --- -### [HIGH] CVE-2026-45135 · Caddy FastCGI Unsafe Unicode Handling in splitPos +### ✅ [HIGH] CVE-2026-45135 · Caddy FastCGI Unsafe Unicode Handling in splitPos | Field | Value | |--------------|-------| | **ID** | CVE-2026-45135 | | **Severity** | High · 8.1 | -| **Status** | Fix deployed — `no-cache-filter: caddy-builder` added to nightly workflow | +| **Status** | Fully remediated — `no-cache-filters: caddy-builder,crowdsec-builder` applied to nightly and E2E build workflows | **What** Caddy v2.11.2 contains unsafe Unicode handling in the FastCGI `splitPos` function. A @@ -280,10 +280,11 @@ prior nightly run despite the ARG value change — a known edge case where GHA c loses ARG-scoped metadata, preventing proper cache key invalidation. **Remediation Applied** -Added `no-cache-filter: caddy-builder` to the `build-and-push-nightly` job in -`.github/workflows/nightly-build.yml`. This forces the `caddy-builder` stage to rebuild from -scratch on every nightly run, bypassing the GHA layer cache for that stage. All other stages -continue to benefit from the cache. +Added `no-cache-filters: caddy-builder,crowdsec-builder` to the `build-and-push-nightly` job in +`.github/workflows/nightly-build.yml` and to the `Build Docker image` step in +`.github/workflows/e2e-tests-split.yml`. This forces both the `caddy-builder` and +`crowdsec-builder` stages to rebuild from scratch on every nightly and E2E build run, bypassing +the GHA layer cache for those stages. All other stages continue to benefit from the cache. --- diff --git a/agent/go.mod b/agent/go.mod index e5245f3b5..1f0912053 100644 --- a/agent/go.mod +++ b/agent/go.mod @@ -14,7 +14,7 @@ require ( github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect - golang.org/x/sys v0.45.0 // indirect + golang.org/x/sys v0.46.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/agent/go.sum b/agent/go.sum index 4a3eb7a2d..474f5c4e9 100644 --- a/agent/go.sum +++ b/agent/go.sum @@ -22,8 +22,8 @@ github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/backend/go.mod b/backend/go.mod index d3d8eac67..880006e37 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -17,15 +17,15 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/sirupsen/logrus v1.9.4 github.com/stretchr/testify v1.11.1 - golang.org/x/crypto v0.52.0 - golang.org/x/net v0.55.0 - golang.org/x/text v0.37.0 + golang.org/x/crypto v0.53.0 + golang.org/x/net v0.56.0 + golang.org/x/text v0.38.0 golang.org/x/time v0.15.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/sqlite v1.6.0 gorm.io/gorm v1.31.1 - software.sslmate.com/src/go-pkcs12 v0.7.1 + software.sslmate.com/src/go-pkcs12 v0.7.2 ) require ( @@ -43,7 +43,7 @@ require ( github.com/docker/go-connections v0.7.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/felixge/httpsnoop v1.1.0 // indirect github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gin-contrib/sse v1.1.1 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect @@ -87,10 +87,10 @@ require ( go.opentelemetry.io/otel v1.44.0 // indirect go.opentelemetry.io/otel/metric v1.44.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect - golang.org/x/arch v0.27.0 // indirect - golang.org/x/sys v0.45.0 // indirect + golang.org/x/arch v0.28.0 // indirect + golang.org/x/sys v0.46.0 // indirect google.golang.org/protobuf v1.36.11 // indirect - modernc.org/libc v1.72.5 // indirect + modernc.org/libc v1.73.3 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect modernc.org/sqlite v1.52.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index 9f10c0ee6..ce283e537 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -27,8 +27,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= +github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gin-contrib/gzip v1.2.6 h1:OtN8DplD5DNZCSLAnQ5HxRkD2qZ5VU+JhOrcfJrcRvg= @@ -183,24 +183,24 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= -golang.org/x/arch v0.27.0 h1:0WNVcR8u9yFz8j5FvdHpgwNp3FS5U4guYdzHwEiGjoU= -golang.org/x/arch v0.27.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= -golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/arch v0.28.0 h1:wVwVdqsTuUbJvhYVCspQYwZXHNYeLSoZnmHD+ggddpQ= +golang.org/x/arch v0.28.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -217,10 +217,10 @@ gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= -modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY= -modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= -modernc.org/ccgo/v4 v4.34.2 h1:mxsy2FdrB6+qG3NfXefz1AmWv0ehOSDO4jxgxd7h9yo= -modernc.org/ccgo/v4 v4.34.2/go.mod h1:1L7us56+kAKu04p25EATpmBBvhbcqqZ85ibqWVwVgog= +modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c= +modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws= +modernc.org/ccgo/v4 v4.34.4/go.mod h1:qdKqE8FNIYyysougB1RX9MxCzp5oJOcQXSobANJ4TuE= modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= @@ -229,8 +229,8 @@ modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc= modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.72.5 h1:m2OGx9Ser1VvTS4Z9ZJlWs+CBMxutLaTiAWkNz+NB9U= -modernc.org/libc v1.72.5/go.mod h1:np0N7KDJ7eUtMZmOqVZNldrZyG+DHLl2B5pg8Hbar3U= +modernc.org/libc v1.73.3 h1:enGE2dD5ZIzW3uf2K/ln6IYb/cAuW24c9gG4RFp8BO0= +modernc.org/libc v1.73.3/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= @@ -247,5 +247,5 @@ modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk= pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= -software.sslmate.com/src/go-pkcs12 v0.7.1 h1:bxkUPRsvTPNRBZa4M/aSX4PyMOEbq3V8I6hbkG4F4Q8= -software.sslmate.com/src/go-pkcs12 v0.7.1/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= +software.sslmate.com/src/go-pkcs12 v0.7.2 h1:Rh9FoMaI5k7Oo6EOS+2/BnoZ+JFIS+XHjM0VGkSPXLM= +software.sslmate.com/src/go-pkcs12 v0.7.2/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI= diff --git a/docs/plans/current_spec.md b/docs/plans/current_spec.md index 00b34fdbc..eeee56b87 100644 --- a/docs/plans/current_spec.md +++ b/docs/plans/current_spec.md @@ -1,546 +1,107 @@ -# Fix: Flash of Unstyled Content (FOUC) / Forced Layout Warning - -**Status:** Draft -**Date:** 2026-06-05 -**Target Files:** `frontend/index.html`, `frontend/src/context/ThemeContext.tsx`, `frontend/src/i18n.ts`, `frontend/src/main.tsx`, `frontend/src/index.css` - ---- - -## 1. Introduction - -### Overview - -Charon's frontend exhibits a classic Flash of Unstyled Content (FOUC) during initial page load. The browser console surfaces it as: - -> *"Layout was forced before the page was fully loaded. If stylesheets are not yet loaded this may cause a flash of unstyled content."* - -Users experience a visible dark-to-light (or light-to-dark) colour flicker on every hard reload or cold navigation. For light-mode users this is especially severe — the entire page flashes a dark slate background before switching to light colours. The page also experiences a blank white frame while i18n initialises. - -### Objectives - -1. Eliminate the theme class FOUC so `` carries the correct `.dark` / `.light` class **before** the first browser paint. -2. Eliminate the blank-page delay caused by async i18n initialisation. -3. Remove the forced-layout warning triggered by React's post-paint `useEffect` applying theme classes while CSS is still resolving. -4. Establish Playwright regression tests that prevent future regressions. - ---- - -## 2. Research Findings - -### 2.1 File Inventory - -| File | Lines | Role | -|---|---|---| -| `frontend/index.html` | 13 | HTML shell served by Vite / Go backend | -| `frontend/src/main.tsx` | 46 | React application entry point | -| `frontend/src/context/ThemeContext.tsx` | 27 | Dark/light mode state and DOM application | -| `frontend/src/context/LanguageContext.tsx` | 34 | Language state provider | -| `frontend/src/i18n.ts` | 38 | i18next initialisation with bundled JSON resources | -| `frontend/src/index.css` | 300 | Global stylesheet: Tailwind v4, CSS custom properties, light-mode overrides | -| `frontend/src/App.tsx` | ~180 | Root component; all 28 pages are React.lazy() | -| `frontend/tailwind.config.js` | ~80 | darkMode: 'class' | -| `frontend/vite.config.ts` | 43 | Vite with rolldown, vendor chunk splitting | - -### 2.2 Critical Code Paths - -**`frontend/index.html`** (full file): - -```html - - - - - - - Charon - - -
- - - -``` - -Observations: -- No inline ` - -``` - -Constraints: -- Plain `