fix
Some checks failed
Dev CI / Backend — Lint (push) Successful in 1m4s
Dev CI / Security gate (push) Failing after 1m36s
Dev CI / Backend — Unit Tests (push) Successful in 1m7s
Dev CI / Frontend — Lint & Type-check (push) Successful in 1m51s
Dev CI / Frontend — Unit Tests (push) Successful in 1m22s
Dev CI / Notify Failure (push) Has been skipped

This commit is contained in:
David 2026-09-23 22:56:46 +02:00
parent e055af9afe
commit 5c59ef044b
15 changed files with 241 additions and 24 deletions

View File

@ -4,7 +4,11 @@ runs:
using: composite using: composite
steps: steps:
- uses: ./.gitea/actions/setup-node - uses: ./.gitea/actions/setup-node
- uses: ./.gitea/actions/setup-trivy - name: Install Trivy
shell: bash
run: |
trivy_bin=$(bash scripts/ci/install-tool.sh trivy)
"$trivy_bin" --version
- name: Validate workflows and deployment checks - name: Validate workflows and deployment checks
shell: bash shell: bash
run: | run: |
@ -13,6 +17,10 @@ runs:
- name: Audit dependencies, secrets and infrastructure - name: Audit dependencies, secrets and infrastructure
shell: bash shell: bash
run: bash scripts/ci/security-audit.sh run: bash scripts/ci/security-audit.sh
- name: Show security results
if: always()
shell: bash
run: python3 scripts/ci/summarize-security.py
- name: Save security reports on Gitea - name: Save security reports on Gitea
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,7 +40,32 @@ jobs:
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with: with:
persist-credentials: false persist-credentials: false
- uses: ./.gitea/actions/security - uses: ./.gitea/actions/setup-node
- name: Install Trivy
shell: bash
run: |
trivy_bin=$(bash scripts/ci/install-tool.sh trivy)
"$trivy_bin" --version
- name: Validate workflows and deployment checks
shell: bash
run: |
actionlint_bin=$(bash scripts/ci/install-tool.sh actionlint)
ACTIONLINT_BIN="$actionlint_bin" bash scripts/ci/validate-workflows.sh
- name: Audit dependencies, secrets and infrastructure
shell: bash
run: bash scripts/ci/security-audit.sh
- name: Show security results
if: always()
shell: bash
run: python3 scripts/ci/summarize-security.py
- name: Save security reports on Gitea
if: always()
uses: https://github.com/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3, Gitea artifact protocol
with:
name: security-reports
path: ${{ runner.temp }}/security-reports/*.json
retention-days: 7
if-no-files-found: error
# ═══ 1. Qualité ══════════════════════════════════════════════════════════ # ═══ 1. Qualité ══════════════════════════════════════════════════════════
backend-quality: backend-quality:

View File

@ -28,7 +28,32 @@ jobs:
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with: with:
persist-credentials: false persist-credentials: false
- uses: ./.gitea/actions/security - uses: ./.gitea/actions/setup-node
- name: Install Trivy
shell: bash
run: |
trivy_bin=$(bash scripts/ci/install-tool.sh trivy)
"$trivy_bin" --version
- name: Validate workflows and deployment checks
shell: bash
run: |
actionlint_bin=$(bash scripts/ci/install-tool.sh actionlint)
ACTIONLINT_BIN="$actionlint_bin" bash scripts/ci/validate-workflows.sh
- name: Audit dependencies, secrets and infrastructure
shell: bash
run: bash scripts/ci/security-audit.sh
- name: Show security results
if: always()
shell: bash
run: python3 scripts/ci/summarize-security.py
- name: Save security reports on Gitea
if: always()
uses: https://github.com/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3, Gitea artifact protocol
with:
name: security-reports
path: ${{ runner.temp }}/security-reports/*.json
retention-days: 7
if-no-files-found: error
# ── 1. Lint ───────────────────────────────────────────────────────── # ── 1. Lint ─────────────────────────────────────────────────────────
backend-quality: backend-quality:

View File

@ -14,7 +14,32 @@ jobs:
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with: with:
persist-credentials: false persist-credentials: false
- uses: ./.gitea/actions/security - uses: ./.gitea/actions/setup-node
- name: Install Trivy
shell: bash
run: |
trivy_bin=$(bash scripts/ci/install-tool.sh trivy)
"$trivy_bin" --version
- name: Validate workflows and deployment checks
shell: bash
run: |
actionlint_bin=$(bash scripts/ci/install-tool.sh actionlint)
ACTIONLINT_BIN="$actionlint_bin" bash scripts/ci/validate-workflows.sh
- name: Audit dependencies, secrets and infrastructure
shell: bash
run: bash scripts/ci/security-audit.sh
- name: Show security results
if: always()
shell: bash
run: python3 scripts/ci/summarize-security.py
- name: Save security reports on Gitea
if: always()
uses: https://github.com/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3, Gitea artifact protocol
with:
name: security-reports
path: ${{ runner.temp }}/security-reports/*.json
retention-days: 7
if-no-files-found: error
backend-quality: backend-quality:
name: Backend — Lint name: Backend — Lint

View File

@ -16,7 +16,32 @@ jobs:
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with: with:
persist-credentials: false persist-credentials: false
- uses: ./.gitea/actions/security - uses: ./.gitea/actions/setup-node
- name: Install Trivy
shell: bash
run: |
trivy_bin=$(bash scripts/ci/install-tool.sh trivy)
"$trivy_bin" --version
- name: Validate workflows and deployment checks
shell: bash
run: |
actionlint_bin=$(bash scripts/ci/install-tool.sh actionlint)
ACTIONLINT_BIN="$actionlint_bin" bash scripts/ci/validate-workflows.sh
- name: Audit dependencies, secrets and infrastructure
shell: bash
run: bash scripts/ci/security-audit.sh
- name: Show security results
if: always()
shell: bash
run: python3 scripts/ci/summarize-security.py
- name: Save security reports on Gitea
if: always()
uses: https://github.com/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3, Gitea artifact protocol
with:
name: security-reports
path: ${{ runner.temp }}/security-reports/*.json
retention-days: 7
if-no-files-found: error
backend-quality: backend-quality:
name: Backend — Lint name: Backend — Lint

View File

@ -11,7 +11,7 @@ import { IsEmail, IsOptional, IsString, MaxLength } from 'class-validator';
export class DeleteAccountDto { export class DeleteAccountDto {
@ApiProperty({ @ApiProperty({
example: 'personne@example.com', example: 'personne@example.com',
description: "Adresse du compte, ressaisie pour confirmer un acte irréversible", description: 'Adresse du compte, ressaisie pour confirmer un acte irréversible',
}) })
@IsEmail({}, { message: 'Une adresse email valide est requise pour confirmer.' }) @IsEmail({}, { message: 'Une adresse email valide est requise pour confirmer.' })
confirmEmail: string; confirmEmail: string;

View File

@ -54,7 +54,9 @@ function buildService(options: { user?: UserOrmEntity | null } = {}) {
} as unknown as EntityManager; } as unknown as EntityManager;
const dataSource = { const dataSource = {
transaction: jest.fn(async (callback: (m: EntityManager) => Promise<void>) => callback(manager)), transaction: jest.fn(async (callback: (m: EntityManager) => Promise<void>) =>
callback(manager)
),
query: jest.fn(async (sql: string, parameters: unknown[]) => { query: jest.fn(async (sql: string, parameters: unknown[]) => {
executed.push({ sql, parameters }); executed.push({ sql, parameters });
return []; return [];

View File

@ -208,7 +208,9 @@ export class GDPRService {
const user = await this.userRepository.findOne({ where: { id: userId } }); const user = await this.userRepository.findOne({ where: { id: userId } });
if (!user) throw new NotFoundException('User not found'); if (!user) throw new NotFoundException('User not found');
this.logger.warn(`GDPR erasure starting for user ${userId} — reason: ${reason ?? 'unspecified'}`); this.logger.warn(
`GDPR erasure starting for user ${userId} — reason: ${reason ?? 'unspecified'}`
);
const deleted: Record<string, number> = {}; const deleted: Record<string, number> = {};
const anonymised: Record<string, number> = {}; const anonymised: Record<string, number> = {};
@ -228,7 +230,9 @@ export class GDPRService {
userId, userId,
]); ]);
deleted.apiKeys = await rows('DELETE FROM api_keys WHERE user_id = $1', [userId]); deleted.apiKeys = await rows('DELETE FROM api_keys WHERE user_id = $1', [userId]);
deleted.cookieConsent = await rows('DELETE FROM cookie_consents WHERE user_id = $1', [userId]); deleted.cookieConsent = await rows('DELETE FROM cookie_consents WHERE user_id = $1', [
userId,
]);
deleted.passwordResetTokens = await rows( deleted.passwordResetTokens = await rows(
'DELETE FROM password_reset_tokens WHERE user_id = $1', 'DELETE FROM password_reset_tokens WHERE user_id = $1',
[userId] [userId]
@ -361,7 +365,9 @@ export class GDPRService {
const next: UpdateConsentDto = { const next: UpdateConsentDto = {
essential: true, essential: true,
functional: consentType ? consentType !== 'functional' && (current?.functional ?? false) : false, functional: consentType
? consentType !== 'functional' && (current?.functional ?? false)
: false,
analytics: consentType ? consentType !== 'analytics' && (current?.analytics ?? false) : false, analytics: consentType ? consentType !== 'analytics' && (current?.analytics ?? false) : false,
marketing: consentType ? consentType !== 'marketing' && (current?.marketing ?? false) : false, marketing: consentType ? consentType !== 'marketing' && (current?.marketing ?? false) : false,
}; };

View File

@ -49,7 +49,7 @@ describe('RetentionService', () => {
} }
}); });
it("épargne les traces de traitement des demandes de droits", async () => { it('épargne les traces de traitement des demandes de droits', async () => {
const { service, executed } = buildService(); const { service, executed } = buildService();
await service.purge(); await service.purge();

View File

@ -18,8 +18,5 @@ export interface ShipmentCounterPort {
* an organization in a given year. Unpaid drafts (QUOTE), rejected and * an organization in a given year. Unpaid drafts (QUOTE), rejected and
* cancelled bookings are excluded. * cancelled bookings are excluded.
*/ */
countPaidShipmentsForOrganizationInYear( countPaidShipmentsForOrganizationInYear(organizationId: string, year: number): Promise<number>;
organizationId: string,
year: number
): Promise<number>;
} }

View File

@ -1,10 +1,7 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { import { CsvRateConfigOrmEntity, CsvRateDirection } from '../entities/csv-rate-config.orm-entity';
CsvRateConfigOrmEntity,
CsvRateDirection,
} from '../entities/csv-rate-config.orm-entity';
/** /**
* CSV Rate Config Repository Port * CSV Rate Config Repository Port

View File

@ -142,7 +142,7 @@ ces configurations ne signale plus ce secret après modification.
## Validation ## Validation
Les 25 tests offline de promotion et santé passent : arbre différent, marqueurs Les 27 tests offline de promotion et santé passent : arbre différent, marqueurs
manquants, panne du registre, digest invalide, mauvaise branche, saisie hostile et manquants, panne du registre, digest invalide, mauvaise branche, saisie hostile et
échec HTTP sont refusés. Les tests vérifient aussi que les échecs et délais des échec HTTP sont refusés. Les tests vérifient aussi que les échecs et délais des
audits restent bloquants et que le rapport masque les valeurs de secrets. Actionlint valide la syntaxe après normalisation des URLs audits restent bloquants et que le rapport masque les valeurs de secrets. Actionlint valide la syntaxe après normalisation des URLs
@ -172,3 +172,20 @@ sur les runners de l'instance.
Références : [différences Gitea 1.22](https://docs.gitea.com/1.22/usage/actions/comparison/), Références : [différences Gitea 1.22](https://docs.gitea.com/1.22/usage/actions/comparison/),
[Renovate sur Gitea](https://docs.renovatebot.com/modules/platform/gitea/). [Renovate sur Gitea](https://docs.renovatebot.com/modules/platform/gitea/).
## Diagnostic du run Gitea 146
Le [journal complet du job sécurité](https://gitea.ops.xpeditis.com/David/xpeditis2.0/actions/runs/146/jobs/0/logs)
confirme que Node 22 et Trivy démarrent, que les validations passent et que
l'artefact `security-reports` est bien téléversé. L'affichage de l'action composite
s'arrête pourtant visuellement au lancement de Trivy. Les quatre workflows
exposent désormais installation, validation, audit, résumé et téléversement comme
étapes distinctes. Le résumé s'exécute même si l'audit échoue, sans afficher de
valeurs de secrets. Un rapport absent ou invalide est signalé comme indisponible.
Ce run échoue réellement sur les audits : 50 vulnérabilités élevées backend,
13 élevées et une critique frontend, quatre détections de secrets et douze
alertes d'infrastructure. Ces nombres décrivent ce run, pas un audit futur.
Les seuils restent bloquants ; cette correction d'affichage ne corrige pas les
vulnérabilités. Les douze erreurs Prettier du job backend ont été corrigées et
le lint sans correction automatique passe localement.

View File

@ -0,0 +1,45 @@
"""Print audit counts without exposing secret matches or source snippets."""
import json
import os
from pathlib import Path
def summarize(reports):
incomplete = False
def read(name):
nonlocal incomplete
try:
report = json.loads((reports / name).read_text())
if not isinstance(report, dict) or report.get('error'):
raise ValueError('Invalid audit report')
return report
except (OSError, ValueError):
print(f'{name}: report missing, invalid or scanner error; inspect the audit step.')
incomplete = True
return {}
for project in ('root', 'backend', 'frontend', 'log-exporter'):
report = read(f'npm-audit-{project}.json')
counts = report.get('metadata', {}).get('vulnerabilities', {})
if 'high' not in counts or 'critical' not in counts:
print(f'{project}: dependency audit unavailable.')
incomplete = True
continue
print(f'{project}: {counts["high"]} high, {counts["critical"]} critical vulnerabilities.')
report = read('source-security.json')
if 'Results' not in report:
print('Source audit unavailable.')
incomplete = True
else:
results = report['Results'] or []
secrets = sum(len(result.get('Secrets') or []) for result in results)
misconfigs = sum(len(result.get('Misconfigurations') or []) for result in results)
print(f'Source: {secrets} secret findings, {misconfigs} infrastructure findings.')
print('Download the security-reports artifact for details. The audit step determines pass/fail.')
return int(incomplete)
if __name__ == '__main__':
raise SystemExit(summarize(Path(os.environ['RUNNER_TEMP']) / 'security-reports'))

View File

@ -0,0 +1,45 @@
"""Diagnostics must not leak scanner evidence or report missing audits as clean."""
import contextlib
import importlib.util
import io
import json
from pathlib import Path
import tempfile
import unittest
spec = importlib.util.spec_from_file_location(
'security_summary', Path(__file__).with_name('summarize-security.py'))
summary = importlib.util.module_from_spec(spec)
spec.loader.exec_module(summary)
class SecuritySummary(unittest.TestCase):
def test_real_findings_are_counted_without_printing_evidence(self):
with tempfile.TemporaryDirectory() as temp:
reports = Path(temp)
for project in ('root', 'backend', 'frontend', 'log-exporter'):
(reports / f'npm-audit-{project}.json').write_text(json.dumps({
'metadata': {'vulnerabilities': {'high': 2, 'critical': 1}}}))
(reports / 'source-security.json').write_text(json.dumps({'Results': [{
'Secrets': [{'Match': 'DO-NOT-PRINT', 'Code': 'PRIVATE-CODE'}],
'Misconfigurations': [{'Description': 'PRIVATE-DESCRIPTION'}]}]}))
output = io.StringIO()
with contextlib.redirect_stdout(output):
self.assertEqual(summary.summarize(reports), 0)
text = output.getvalue()
self.assertIn('2 high, 1 critical', text)
self.assertIn('1 secret findings, 1 infrastructure findings', text)
self.assertNotIn('DO-NOT-PRINT', text)
self.assertNotIn('PRIVATE-', text)
def test_missing_and_failed_audits_are_not_reported_as_clean(self):
with tempfile.TemporaryDirectory() as temp:
reports = Path(temp)
(reports / 'npm-audit-root.json').write_text('{invalid')
(reports / 'npm-audit-backend.json').write_text('{"error": {"code": "NETWORK"}}')
output = io.StringIO()
with contextlib.redirect_stdout(output):
self.assertEqual(summary.summarize(reports), 1)
self.assertIn('dependency audit unavailable', output.getvalue())
self.assertIn('Source audit unavailable', output.getvalue())
self.assertNotIn('0 high', output.getvalue())