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
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:
parent
e055af9afe
commit
5c59ef044b
@ -4,7 +4,11 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- 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
|
||||
shell: bash
|
||||
run: |
|
||||
@ -13,6 +17,10 @@ runs:
|
||||
- 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
|
||||
|
||||
@ -40,7 +40,32 @@ jobs:
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
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é ══════════════════════════════════════════════════════════
|
||||
backend-quality:
|
||||
|
||||
@ -28,7 +28,32 @@ jobs:
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
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 ─────────────────────────────────────────────────────────
|
||||
backend-quality:
|
||||
|
||||
@ -14,7 +14,32 @@ jobs:
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
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:
|
||||
name: Backend — Lint
|
||||
|
||||
@ -16,7 +16,32 @@ jobs:
|
||||
- uses: https://github.com/actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
||||
with:
|
||||
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:
|
||||
name: Backend — Lint
|
||||
|
||||
@ -70,10 +70,10 @@ describe('administrator target protection', () => {
|
||||
const controller = new UsersController(
|
||||
{ findById: jest.fn(async () => user), save } as unknown as UserRepository,
|
||||
{} as SubscriptionService,
|
||||
{} as never,
|
||||
{ assertKeepsAnActiveAdmin: jest.fn() } as never,
|
||||
{ sendUserInvitation: jest.fn().mockResolvedValue(undefined) } as never,
|
||||
{ findById: jest.fn().mockResolvedValue(null) } as never
|
||||
{} as never,
|
||||
{ assertKeepsAnActiveAdmin: jest.fn() } as never,
|
||||
{ sendUserInvitation: jest.fn().mockResolvedValue(undefined) } as never,
|
||||
{ findById: jest.fn().mockResolvedValue(null) } as never
|
||||
);
|
||||
const result = controller.updateUser(user.id, { firstName: 'Changed' }, actor);
|
||||
if (role === UserRole.ADMIN) {
|
||||
|
||||
@ -11,7 +11,7 @@ import { IsEmail, IsOptional, IsString, MaxLength } from 'class-validator';
|
||||
export class DeleteAccountDto {
|
||||
@ApiProperty({
|
||||
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.' })
|
||||
confirmEmail: string;
|
||||
|
||||
@ -54,7 +54,9 @@ function buildService(options: { user?: UserOrmEntity | null } = {}) {
|
||||
} as unknown as EntityManager;
|
||||
|
||||
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[]) => {
|
||||
executed.push({ sql, parameters });
|
||||
return [];
|
||||
|
||||
@ -208,7 +208,9 @@ export class GDPRService {
|
||||
const user = await this.userRepository.findOne({ where: { id: userId } });
|
||||
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 anonymised: Record<string, number> = {};
|
||||
@ -228,7 +230,9 @@ export class GDPRService {
|
||||
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(
|
||||
'DELETE FROM password_reset_tokens WHERE user_id = $1',
|
||||
[userId]
|
||||
@ -361,7 +365,9 @@ export class GDPRService {
|
||||
|
||||
const next: UpdateConsentDto = {
|
||||
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,
|
||||
marketing: consentType ? consentType !== 'marketing' && (current?.marketing ?? false) : false,
|
||||
};
|
||||
|
||||
@ -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();
|
||||
|
||||
await service.purge();
|
||||
|
||||
@ -18,8 +18,5 @@ export interface ShipmentCounterPort {
|
||||
* an organization in a given year. Unpaid drafts (QUOTE), rejected and
|
||||
* cancelled bookings are excluded.
|
||||
*/
|
||||
countPaidShipmentsForOrganizationInYear(
|
||||
organizationId: string,
|
||||
year: number
|
||||
): Promise<number>;
|
||||
countPaidShipmentsForOrganizationInYear(organizationId: string, year: number): Promise<number>;
|
||||
}
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import {
|
||||
CsvRateConfigOrmEntity,
|
||||
CsvRateDirection,
|
||||
} from '../entities/csv-rate-config.orm-entity';
|
||||
import { CsvRateConfigOrmEntity, CsvRateDirection } from '../entities/csv-rate-config.orm-entity';
|
||||
|
||||
/**
|
||||
* CSV Rate Config Repository Port
|
||||
|
||||
@ -142,7 +142,7 @@ ces configurations ne signale plus ce secret après modification.
|
||||
|
||||
## 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
|
||||
é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
|
||||
@ -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/),
|
||||
[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.
|
||||
|
||||
45
scripts/ci/summarize-security.py
Normal file
45
scripts/ci/summarize-security.py
Normal 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'))
|
||||
45
scripts/ci/test_security_summary.py
Normal file
45
scripts/ci/test_security_summary.py
Normal 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())
|
||||
Loading…
Reference in New Issue
Block a user