From bc0951115bce30a27afa120a234e45e99d6cf601 Mon Sep 17 00:00:00 2001 From: NuklearRabbit <145918611+NuklearRabbit@users.noreply.github.com> Date: Fri, 28 Aug 2026 23:11:09 +0200 Subject: [PATCH 1/7] fix(ci): use Gitea-compatible canary artifacts --- .gitea/workflows/live-canary.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/live-canary.yml b/.gitea/workflows/live-canary.yml index 0f30769..d2759db 100644 --- a/.gitea/workflows/live-canary.yml +++ b/.gitea/workflows/live-canary.yml @@ -39,7 +39,7 @@ jobs: fi - name: Upload failure evidence if: failure() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 + uses: actions/upload-artifact@v3.2.2-node20 with: name: live-canary-failure path: | -- 2.54.0 From d20ff7a2435a8b673a13a510b2afbe333c4c3018 Mon Sep 17 00:00:00 2001 From: NuklearRabbit <145918611+NuklearRabbit@users.noreply.github.com> Date: Fri, 28 Aug 2026 23:42:59 +0200 Subject: [PATCH 2/7] fix(ci): upgrade pinned Trivy action --- .gitea/workflows/ci.yml | 4 ++-- .gitea/workflows/release.yml | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index fcd1bcb..bb8e0a9 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: docker build --target runtime --build-arg VCS_REF="$GITHUB_SHA" \ --tag mobilityops-api-ci --file backend/Dockerfile . - name: Production API image vulnerability scan (HIGH/CRITICAL) - uses: aquasecurity/trivy-action@6c175e9c4083a92bbca2f9724c8a5e33bc2d97a5 # v0.30.0 + uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0 with: scan-type: image image-ref: mobilityops-api-ci @@ -46,7 +46,7 @@ jobs: docker build --build-arg VCS_REF="$GITHUB_SHA" \ --tag mobilityops-web-ci frontend - name: Production web image vulnerability scan (HIGH/CRITICAL) - uses: aquasecurity/trivy-action@6c175e9c4083a92bbca2f9724c8a5e33bc2d97a5 # v0.30.0 + uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0 with: scan-type: image image-ref: mobilityops-web-ci diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 789f5ac..6fa5624 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -24,21 +24,21 @@ jobs: --ignore-unfixed --exit-code 1 "$image" done - name: Generate API CycloneDX SBOM - uses: aquasecurity/trivy-action@6c175e9c4083a92bbca2f9724c8a5e33bc2d97a5 # v0.30.0 + uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0 with: scan-type: image image-ref: mobilityops-api-release format: cyclonedx output: mobilityops-api-sbom.cdx.json - name: Generate web CycloneDX SBOM - uses: aquasecurity/trivy-action@6c175e9c4083a92bbca2f9724c8a5e33bc2d97a5 # v0.30.0 + uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0 with: scan-type: image image-ref: mobilityops-web-release format: cyclonedx output: mobilityops-web-sbom.cdx.json - name: Generate backup-tools CycloneDX SBOM - uses: aquasecurity/trivy-action@6c175e9c4083a92bbca2f9724c8a5e33bc2d97a5 # v0.30.0 + uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0 with: scan-type: image image-ref: mobilityops-backup-tools-release -- 2.54.0 From 00e8ec001ba44b97ff8fa4ab35f641fdd3edcbcf Mon Sep 17 00:00:00 2001 From: NuklearRabbit <145918611+NuklearRabbit@users.noreply.github.com> Date: Fri, 28 Aug 2026 23:57:46 +0200 Subject: [PATCH 3/7] fix(ci): run secret scan inside isolated workspace --- .gitea/workflows/ci.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index bb8e0a9..3a4361b 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -13,10 +13,19 @@ jobs: steps: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - name: Secret scan - uses: trufflesecurity/trufflehog@b9dd330365132cd2d01dd5dc8a857a056a2544e1 # v3.79.0 - with: - path: ./ - extra_args: --only-verified + shell: bash + run: | + set -euo pipefail + repository="$PWD" + source="file:///repo" + workspace=(-v "$repository:/repo" -w /repo) + if docker inspect "${HOSTNAME:-}" >/dev/null 2>&1; then + source="file://$repository" + workspace=(--volumes-from "$HOSTNAME" -w "$repository") + fi + docker run --rm "${workspace[@]}" \ + ghcr.io/trufflesecurity/trufflehog@sha256:7104dbb84d1ad2f5f6fa1134e92c6aa6f701f0a4ac2efd5a4c5c96225d899fe3 \ + git "$source" --fail --no-update --github-actions --only-verified - name: Backend tests in isolated PostgreSQL stack run: sh scripts/run-isolated-tests.sh - name: Backend static checks -- 2.54.0 From a99aed9a5f57d8f456a8ea894c98e9a6e1218a64 Mon Sep 17 00:00:00 2001 From: NuklearRabbit <145918611+NuklearRabbit@users.noreply.github.com> Date: Fri, 28 Aug 2026 23:59:30 +0200 Subject: [PATCH 4/7] perf(ci): deduplicate branch validation --- .gitea/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 3a4361b..5f47bbc 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -7,6 +7,10 @@ on: schedule: - cron: "17 3 * * 1" +concurrency: + group: acceptance-${{ gitea.repository }}-${{ gitea.ref }} + cancel-in-progress: true + jobs: backend: runs-on: ubuntu-latest -- 2.54.0 From 29186082408f544d2471d6463055795237cd42c4 Mon Sep 17 00:00:00 2001 From: NuklearRabbit <145918611+NuklearRabbit@users.noreply.github.com> Date: Sat, 29 Aug 2026 00:27:17 +0200 Subject: [PATCH 5/7] fix(ci): isolate contract and image scans from host paths --- .gitea/workflows/ci.yml | 20 +++----------------- backend/Dockerfile | 2 ++ scripts/scan-ci-image.sh | 29 +++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 17 deletions(-) create mode 100644 scripts/scan-ci-image.sh diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 5f47bbc..32f4a02 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -39,34 +39,20 @@ jobs: - name: Contract drift gate run: | docker compose -p mobilityops-ci -f compose.yaml -f compose.test.yaml run --rm \ - -v "$PWD:/repo:ro" api python /repo/scripts/check-contracts.py + api python scripts/check-contracts.py python scripts/check-source-budgets.py - name: Build production API image for vulnerability scan run: | docker build --target runtime --build-arg VCS_REF="$GITHUB_SHA" \ --tag mobilityops-api-ci --file backend/Dockerfile . - name: Production API image vulnerability scan (HIGH/CRITICAL) - uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0 - with: - scan-type: image - image-ref: mobilityops-api-ci - format: table - severity: HIGH,CRITICAL - exit-code: "1" - ignore-unfixed: true + run: bash scripts/scan-ci-image.sh mobilityops-api-ci - name: Build production web image for vulnerability scan run: | docker build --build-arg VCS_REF="$GITHUB_SHA" \ --tag mobilityops-web-ci frontend - name: Production web image vulnerability scan (HIGH/CRITICAL) - uses: aquasecurity/trivy-action@a9c7b0f06e461e9d4b4d1711f154ee024b8d7ab8 # v0.36.0 - with: - scan-type: image - image-ref: mobilityops-web-ci - format: table - severity: HIGH,CRITICAL - exit-code: "1" - ignore-unfixed: true + run: bash scripts/scan-ci-image.sh mobilityops-web-ci - name: Remove CI stack if: always() run: docker compose -p mobilityops-ci -f compose.yaml -f compose.test.yaml down -v --remove-orphans diff --git a/backend/Dockerfile b/backend/Dockerfile index d4ede03..1a48703 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -24,6 +24,8 @@ FROM runtime-base AS test COPY backend/requirements.lock ./requirements.lock RUN pip install --no-cache-dir -r requirements.lock COPY backend/tests ./tests +COPY contracts ./contracts +COPY scripts/check-contracts.py ./scripts/check-contracts.py USER app FROM runtime-base AS runtime diff --git a/scripts/scan-ci-image.sh b/scripts/scan-ci-image.sh new file mode 100644 index 0000000..abd2b6b --- /dev/null +++ b/scripts/scan-ci-image.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +TARGET_IMAGE="${1:?usage: scan-ci-image.sh }" +TRIVY_IMAGE="aquasec/trivy@sha256:be1190afcb28352bfddc4ddeb71470835d16462af68d310f9f4bca710961a41e" +SAFE_NAME="$(printf '%s' "$TARGET_IMAGE" | tr -cs 'A-Za-z0-9._-' '-')" +ARCHIVE_RELATIVE="artifacts/.${SAFE_NAME}.tar" +ARCHIVE="$ROOT/$ARCHIVE_RELATIVE" +trap 'rm -f "$ARCHIVE"' EXIT + +mkdir -p "$ROOT/artifacts" +docker image inspect "$TARGET_IMAGE" >/dev/null +docker save --output "$ARCHIVE" "$TARGET_IMAGE" + +WORKSPACE_ARGS=(-v "$ROOT:/workspace:ro") +CONTAINER_ARCHIVE="/workspace/$ARCHIVE_RELATIVE" +if docker inspect "${HOSTNAME:-}" >/dev/null 2>&1; then + WORKSPACE_ARGS=(--volumes-from "$HOSTNAME") + CONTAINER_ARCHIVE="$ROOT/$ARCHIVE_RELATIVE" +fi + +docker run --rm "${WORKSPACE_ARGS[@]}" "$TRIVY_IMAGE" image \ + --input "$CONTAINER_ARCHIVE" \ + --scanners vuln \ + --severity HIGH,CRITICAL \ + --ignore-unfixed \ + --exit-code 1 \ + --format table -- 2.54.0 From e8eed3e6414d03b347b00fd9f7854ef89994915e Mon Sep 17 00:00:00 2001 From: NuklearRabbit <145918611+NuklearRabbit@users.noreply.github.com> Date: Sat, 29 Aug 2026 00:40:51 +0200 Subject: [PATCH 6/7] fix(ci): package contract fixtures and recover deploy chunk races --- .dockerignore | 4 +++- backend/Dockerfile | 1 + frontend/e2e-live/smoke.spec.ts | 9 +++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 8487d98..25602f6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -14,7 +14,9 @@ artifacts docs deploy -n8n +n8n/** +!n8n/workflows/ +!n8n/workflows/** frontend *.tgz *.tar.gz diff --git a/backend/Dockerfile b/backend/Dockerfile index 1a48703..293ea96 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -26,6 +26,7 @@ RUN pip install --no-cache-dir -r requirements.lock COPY backend/tests ./tests COPY contracts ./contracts COPY scripts/check-contracts.py ./scripts/check-contracts.py +COPY n8n/workflows ./n8n/workflows USER app FROM runtime-base AS runtime diff --git a/frontend/e2e-live/smoke.spec.ts b/frontend/e2e-live/smoke.spec.ts index ecc28b5..778174b 100644 --- a/frontend/e2e-live/smoke.spec.ts +++ b/frontend/e2e-live/smoke.spec.ts @@ -26,6 +26,15 @@ test("non-destructive operator canary covers routes and grounded knowledge", asy await page.goto("/login"); await page.getByRole("button", { name: "Verken als Operationsmanager" }).click(); await expect(page).toHaveURL(/\/dashboard$/); + if (pageErrors.some((message) => message.toLowerCase().includes("dynamically imported module"))) { + // A deploy can replace the SPA between loading index.html and its lazy + // dashboard chunk. One clean reload must recover; a persistent chunk or + // server error is collected again and still fails the canary below. + pageErrors.length = 0; + await page.reload({ waitUntil: "domcontentloaded" }); + await expect(page).toHaveURL(/\/dashboard$/); + await expect(page.locator("main")).toBeVisible(); + } for (const [path, heading] of [ ["/vehicles", "Wagenpark"], -- 2.54.0 From 48147465b5b2ea682069d8736f24d054725ef957 Mon Sep 17 00:00:00 2001 From: NuklearRabbit <145918611+NuklearRabbit@users.noreply.github.com> Date: Tue, 1 Sep 2026 21:19:48 +0200 Subject: [PATCH 7/7] security: refresh Alpine runtime packages --- frontend/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frontend/Dockerfile b/frontend/Dockerfile index cf38a80..fb2d964 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -11,6 +11,9 @@ ENV VITE_API_BASE_URL=$VITE_API_BASE_URL RUN npm run build FROM nginx:1.30.4-alpine@sha256:97d490c12ba55b4946b01546d1c3ed324e8d41ab1c9fcb2a616aa470620e5b46 +# The pinned upstream image can lag Alpine security rebuilds. Apply the current +# fixes from its pinned Alpine release before shipping the runtime image. +RUN apk upgrade --no-cache ARG VCS_REF=development ARG BUILD_DATE=unknown LABEL org.opencontainers.image.title="Fleet Ops Web" \ -- 2.54.0