fix
Some checks are pending
CD Preprod / Deploy to Preprod (push) Blocked by required conditions
CD Preprod / Notify Success (push) Blocked by required conditions
CD Preprod / Notify Failure (push) Blocked by required conditions
CD Preprod / Security gate (push) Successful in 29s
CD Preprod / Backend — Lint (push) Successful in 1m3s
CD Preprod / Frontend — Lint & Type-check (push) Successful in 1m9s
CD Preprod / Backend — Unit Tests (push) Successful in 1m5s
CD Preprod / Frontend — Unit Tests (push) Successful in 43s
CD Preprod / Backend — Integration Tests (push) Successful in 46s
CD Preprod / Build Frontend (push) Successful in 35s
CD Preprod / Build Backend (push) Successful in 1m3s
CD Preprod / Build Log Exporter (push) Successful in 34s
CD Preprod / Image security (${{ matrix.service }}, ${{ matrix.arch }}) (amd64, backend) (push) Successful in 25s
CD Preprod / Image security (${{ matrix.service }}, ${{ matrix.arch }}) (amd64, frontend) (push) Successful in 25s
CD Preprod / Image security (${{ matrix.service }}, ${{ matrix.arch }}) (amd64, log-exporter) (push) Successful in 21s
CD Preprod / Image security (${{ matrix.service }}, ${{ matrix.arch }}) (arm64, backend) (push) Successful in 24s
CD Preprod / Image security (${{ matrix.service }}, ${{ matrix.arch }}) (arm64, frontend) (push) Successful in 23s
CD Preprod / Image security (${{ matrix.service }}, ${{ matrix.arch }}) (arm64, log-exporter) (push) Successful in 22s

This commit is contained in:
David 2026-09-25 16:59:38 +02:00
parent 3434fa494d
commit 450f1ffc18
9 changed files with 127 additions and 8 deletions

View File

@ -231,8 +231,11 @@ jobs:
PLATFORM: linux/${{ matrix.arch }} PLATFORM: linux/${{ matrix.arch }}
run: | run: |
trivy image --platform "$PLATFORM" --scanners vuln --severity HIGH,CRITICAL \ trivy image --platform "$PLATFORM" --scanners vuln --severity HIGH,CRITICAL \
--ignore-unfixed=false --exit-code 1 --timeout 15m --format json \ --ignore-unfixed=false --exit-code 1 --timeout 15m --no-progress --format json \
--output "$RUNNER_TEMP/image-security.json" "$IMAGE" --output "$RUNNER_TEMP/image-security.json" "$IMAGE"
- name: Show image scan results
if: always()
run: python3 scripts/ci/summarize-image-security.py
- name: Save image report - name: Save image report
if: always() if: always()
uses: https://github.com/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3, Gitea artifact protocol uses: https://github.com/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3, Gitea artifact protocol

View File

@ -312,8 +312,11 @@ jobs:
PLATFORM: linux/${{ matrix.arch }} PLATFORM: linux/${{ matrix.arch }}
run: | run: |
trivy image --platform "$PLATFORM" --scanners vuln --severity HIGH,CRITICAL \ trivy image --platform "$PLATFORM" --scanners vuln --severity HIGH,CRITICAL \
--ignore-unfixed=false --exit-code 1 --timeout 15m --format json \ --ignore-unfixed=false --exit-code 1 --timeout 15m --no-progress --format json \
--output "$RUNNER_TEMP/image-security.json" "$IMAGE" --output "$RUNNER_TEMP/image-security.json" "$IMAGE"
- name: Show image scan results
if: always()
run: python3 scripts/ci/summarize-image-security.py
- name: Save image report - name: Save image report
if: always() if: always()
uses: https://github.com/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3, Gitea artifact protocol uses: https://github.com/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3, Gitea artifact protocol

View File

@ -40,6 +40,10 @@ RUN npm prune --production --legacy-peer-deps
# =============================================== # ===============================================
FROM node:22-alpine AS production FROM node:22-alpine AS production
# Runtime starts with node; remove the base image package manager and its dependencies.
# npm remains available in the dependency and build stages.
RUN npm uninstall --global npm
# Install dumb-init for proper signal handling # Install dumb-init for proper signal handling
RUN apk add --no-cache dumb-init RUN apk add --no-cache dumb-init

View File

@ -50,6 +50,10 @@ RUN npm run build
# =============================================== # ===============================================
FROM node:22-alpine AS production FROM node:22-alpine AS production
# Runtime starts with node; remove the base image package manager and its dependencies.
# npm remains available in the dependency and build stages.
RUN npm uninstall --global npm
# Install dumb-init for proper signal handling # Install dumb-init for proper signal handling
RUN apk add --no-cache dumb-init curl RUN apk add --no-cache dumb-init curl

View File

@ -5,6 +5,9 @@ WORKDIR /app
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./
RUN npm ci --omit=dev RUN npm ci --omit=dev
# Only Node and application dependencies are needed at runtime.
RUN npm uninstall --global npm
COPY src/ ./src/ COPY src/ ./src/
EXPOSE 3200 EXPOSE 3200

View File

@ -234,3 +234,34 @@ exécution Gitea n'est effectué par cette correction locale.
Sources : [migration Next 15](https://nextjs.org/docs/app/guides/upgrading/version-15), Sources : [migration Next 15](https://nextjs.org/docs/app/guides/upgrading/version-15),
[distribution SheetJS](https://docs.sheetjs.com/docs/getting-started/installation/nodejs/), [distribution SheetJS](https://docs.sheetjs.com/docs/getting-started/installation/nodejs/),
[exceptions Trivy](https://trivy.dev/docs/dev/configuration/filtering/). [exceptions Trivy](https://trivy.dev/docs/dev/configuration/filtering/).
## Images de déploiement : npm global (25 septembre 2026)
Les rapports du run Gitea 150 attribuent huit alertes HIGH identiques, sur AMD64
et ARM64, aux dépendances du npm global livré dans `node:22-alpine` :
`usr/local/lib/node_modules/npm/node_modules/...`. Les audits npm du projet ne
couvrent pas ce gestionnaire global ; un audit applicatif vert ne suffit donc
pas à valider l'image finale.
Les trois Dockerfiles désinstallent le npm global avec `npm uninstall --global
npm` uniquement après les installations nécessaires, dans l'image d'exécution.
Le backend et le frontend gardent npm dans leurs étages de construction.
Les services démarrent avec Node, et les migrations backend utilisent directement
TypeORM via `startup.js`. La procédure manuelle Portainer utilise également Node.
Aucune CVE n'est ignorée et les scans par digest restent bloquants en préprod/prod.
Chaque job de scan affiche maintenant un résumé indiquant CVE, paquet, version,
correctif disponible et chemin, même après échec. Le rapport JSON complet reste
joint en artefact. Les barres de progression sont désactivées pour éviter les
journaux illisibles. L'avertissement de Trivy 0.74 sur la liste EOL d'Alpine 3.24
n'est pas la cause du code de sortie 1 observé : il est aussi présent sur les
scans corrigés qui retournent 0.
Validation du correctif : reconstruction des trois images ARM64 et du log-exporter
AMD64 ; scans Trivy au seuil HIGH/CRITICAL sans exclusions ; HTTP 200 sur les
routes de santé frontend et log-exporter ; chargement du démarrage backend et de
la CLI TypeORM sans npm/npx ; validation des workflows et 30 tests de scripts.
Les images backend/frontend AMD64 et le déploiement restent à confirmer par la
prochaine exécution Gitea après publication du correctif. Relancer uniquement les
anciens jobs de scan réanalyse les anciens digests vulnérables : il faut rebâtir
les images à partir du commit corrigé.

View File

@ -144,12 +144,12 @@ NEXT_PUBLIC_API_URL=https://api.xpeditis.com
## Migrations automatiques ## Migrations automatiques
Le backend exécute les migrations automatiquement au démarrage via le script `docker-entrypoint.sh` : Le backend exécute les migrations automatiquement via `startup.js`, qui attend PostgreSQL,
appelle directement TypeORM puis lance le serveur Node. npm n’est pas présent dans l’image finale :
```bash ```bash
# apps/backend/docker-entrypoint.sh attend PostgreSQL puis : # Commande de démarrage de l’image
npm run migration:run node startup.js
node dist/main.js
``` ```
Les logs Portainer affichent : Les logs Portainer affichent :
@ -199,8 +199,7 @@ traefik.http.services.frontend.loadbalancer.server.port=3000
### Migrations échouent ### Migrations échouent
```bash ```bash
docker exec -it xpeditis-backend sh docker exec -it xpeditis-backend node -e 'require("./startup").runMigrations().catch(() => process.exit(1))'
cd /app && npm run migration:run
``` ```
Si blocage : vérifier que PostgreSQL est accessible (`DATABASE_HOST` = nom du service Docker). Si blocage : vérifier que PostgreSQL est accessible (`DATABASE_HOST` = nom du service Docker).

View File

@ -0,0 +1,32 @@
"""Show actionable image findings even when the blocking Trivy step failed."""
import json
import os
from pathlib import Path
def summarize(path):
try:
report = json.loads(path.read_text())
if not isinstance(report, dict) or not isinstance(report.get('Results'), list):
raise ValueError('Missing scan results')
except (OSError, ValueError):
print('Image report missing or invalid: inspect the Trivy step; scan not verified.')
return 1
count = 0
for result in report['Results']:
for finding in result.get('Vulnerabilities') or []:
if finding.get('Severity') not in ('HIGH', 'CRITICAL'):
continue
count += 1
print(f'{finding["Severity"]} {finding.get("VulnerabilityID", "unknown")}: '
f'{finding.get("PkgName", "unknown")} '
f'{finding.get("InstalledVersion", "unknown")} -> '
f'{finding.get("FixedVersion") or "no fixed version published"}')
print(f' Path: {finding.get("PkgPath") or result.get("Target", "unknown")}')
print(f'{count} HIGH/CRITICAL image findings. Full details: image-security artifact.')
return int(count > 0)
if __name__ == '__main__':
raise SystemExit(summarize(Path(os.environ['RUNNER_TEMP']) / 'image-security.json'))

View File

@ -0,0 +1,40 @@
import contextlib
import importlib.util
import io
import json
from pathlib import Path
import tempfile
import unittest
spec = importlib.util.spec_from_file_location(
'image_summary', Path(__file__).with_name('summarize-image-security.py'))
summary = importlib.util.module_from_spec(spec)
spec.loader.exec_module(summary)
class ImageSummary(unittest.TestCase):
def test_reports_global_npm_path_and_preserves_failure(self):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / 'image-security.json'
path.write_text(json.dumps({'Results': [{'Vulnerabilities': [{
'Severity': 'HIGH', 'VulnerabilityID': 'CVE-example',
'PkgName': 'pacote', 'InstalledVersion': '19.0.2',
'FixedVersion': '21.5.1',
'PkgPath': 'usr/local/lib/node_modules/npm/node_modules/pacote/package.json',
}]}]}))
output = io.StringIO()
with contextlib.redirect_stdout(output):
self.assertEqual(summary.summarize(path), 1)
self.assertIn('19.0.2 -> 21.5.1', output.getvalue())
self.assertIn('usr/local/lib/node_modules/npm/', output.getvalue())
def test_missing_invalid_and_clean_reports(self):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / 'image-security.json'
with contextlib.redirect_stdout(io.StringIO()):
self.assertEqual(summary.summarize(path), 1)
for content in ('broken', '{}', '{"Results": null}'):
path.write_text(content)
self.assertEqual(summary.summarize(path), 1)
path.write_text('{"Results": []}')
self.assertEqual(summary.summarize(path), 0)