diff --git a/documentation/docs/architecture.md b/documentation/docs/architecture.md
index fa2e767..475e0bc 100644
--- a/documentation/docs/architecture.md
+++ b/documentation/docs/architecture.md
@@ -15,7 +15,7 @@ Deux composants distincts dans un même dépôt, industrialisés par une **uniqu
│ └── vercel.json
├── .github/
│ ├── workflows/ci-cd.yml # pipeline principal durci (staging + main)
-│ ├── actions/trivy-scan/ # composite action — scan SBOM CycloneDX
+│ ├── actions/trivy-scan/ # composite action : scan SBOM CycloneDX
│ ├── branch-protection/main.yml # politique de protection documentée
│ └── secrets-prod.yaml # secrets chiffrés SOPS (valeurs ENC[...])
├── scripts/
diff --git a/documentation/docs/assets/Gitleaks.png b/documentation/docs/assets/Gitleaks.png
new file mode 100644
index 0000000..dcce9fa
Binary files /dev/null and b/documentation/docs/assets/Gitleaks.png differ
diff --git a/documentation/docs/assets/cd.png b/documentation/docs/assets/cd.png
new file mode 100644
index 0000000..9ddfcff
Binary files /dev/null and b/documentation/docs/assets/cd.png differ
diff --git a/documentation/docs/assets/ci-cd.webp b/documentation/docs/assets/ci-cd.webp
new file mode 100644
index 0000000..8aa2302
Binary files /dev/null and b/documentation/docs/assets/ci-cd.webp differ
diff --git a/documentation/docs/assets/ci.png b/documentation/docs/assets/ci.png
new file mode 100644
index 0000000..4b293c3
Binary files /dev/null and b/documentation/docs/assets/ci.png differ
diff --git a/documentation/docs/assets/codeql.png b/documentation/docs/assets/codeql.png
new file mode 100644
index 0000000..a31c16a
Binary files /dev/null and b/documentation/docs/assets/codeql.png differ
diff --git a/documentation/docs/assets/docker.png b/documentation/docs/assets/docker.png
new file mode 100644
index 0000000..950bf7c
Binary files /dev/null and b/documentation/docs/assets/docker.png differ
diff --git a/documentation/docs/assets/github.png b/documentation/docs/assets/github.png
new file mode 100644
index 0000000..35f2ff0
Binary files /dev/null and b/documentation/docs/assets/github.png differ
diff --git a/documentation/docs/assets/github_actions.png b/documentation/docs/assets/github_actions.png
new file mode 100644
index 0000000..b9084da
Binary files /dev/null and b/documentation/docs/assets/github_actions.png differ
diff --git a/documentation/docs/assets/github_pages.png b/documentation/docs/assets/github_pages.png
new file mode 100644
index 0000000..3657178
Binary files /dev/null and b/documentation/docs/assets/github_pages.png differ
diff --git a/documentation/docs/assets/logo_ynov_campus_sophia.png b/documentation/docs/assets/logo_ynov_campus_sophia.png
new file mode 100644
index 0000000..4ccc83a
Binary files /dev/null and b/documentation/docs/assets/logo_ynov_campus_sophia.png differ
diff --git a/documentation/docs/assets/nodejs.png b/documentation/docs/assets/nodejs.png
new file mode 100644
index 0000000..09b73b9
Binary files /dev/null and b/documentation/docs/assets/nodejs.png differ
diff --git a/documentation/docs/assets/ram.png b/documentation/docs/assets/ram.png
new file mode 100644
index 0000000..5e1bc1b
Binary files /dev/null and b/documentation/docs/assets/ram.png differ
diff --git a/documentation/docs/assets/skeleton-projet-final.zip b/documentation/docs/assets/skeleton-projet-final.zip
new file mode 100644
index 0000000..517e087
Binary files /dev/null and b/documentation/docs/assets/skeleton-projet-final.zip differ
diff --git a/documentation/docs/assets/sops.png b/documentation/docs/assets/sops.png
new file mode 100644
index 0000000..df0f3eb
Binary files /dev/null and b/documentation/docs/assets/sops.png differ
diff --git a/documentation/docs/assets/sujet-projet-final.pdf b/documentation/docs/assets/sujet-projet-final.pdf
new file mode 100644
index 0000000..2bfd1f3
Binary files /dev/null and b/documentation/docs/assets/sujet-projet-final.pdf differ
diff --git a/documentation/docs/assets/trivy.png b/documentation/docs/assets/trivy.png
new file mode 100644
index 0000000..f7d5bdc
Binary files /dev/null and b/documentation/docs/assets/trivy.png differ
diff --git a/documentation/docs/assets/vercel.png b/documentation/docs/assets/vercel.png
new file mode 100644
index 0000000..e6177c5
Binary files /dev/null and b/documentation/docs/assets/vercel.png differ
diff --git a/documentation/docs/ci.md b/documentation/docs/ci.md
index db2486d..1e0c397 100644
--- a/documentation/docs/ci.md
+++ b/documentation/docs/ci.md
@@ -1,5 +1,7 @@
# Pipeline CI durci
+{ .logo }
+
Le workflow [`ci-cd.yml`](https://github.com/Sorway/DevSecOps/blob/main/.github/workflows/ci-cd.yml)
agit comme une **barrière** : rien ne se déploie sans le succès complet des contrôles.
diff --git a/documentation/docs/composite-action.md b/documentation/docs/composite-action.md
index e6a388b..2c5228f 100644
--- a/documentation/docs/composite-action.md
+++ b/documentation/docs/composite-action.md
@@ -1,4 +1,4 @@
-# Composite Action — Trivy SBOM
+# Composite Action : Trivy SBOM
Pour centraliser la gouvernance de sécurité et éviter la redondance, le scan de dépendances au format
SBOM est isolé dans une **action composite** locale, réutilisable en boîte noire.
@@ -11,6 +11,8 @@ et expose des entrées / sorties strictes.
## L'action `trivy-scan`
+{ .logo }
+
Fichier [`.github/actions/trivy-scan/action.yml`](https://github.com/Sorway/DevSecOps/blob/main/.github/actions/trivy-scan/action.yml).
```yaml
diff --git a/documentation/docs/conformite.md b/documentation/docs/conformite.md
index 3bb2d92..29e9ed1 100644
--- a/documentation/docs/conformite.md
+++ b/documentation/docs/conformite.md
@@ -1,63 +1,228 @@
# Conformité aux consignes
-Récapitulatif exigence par exigence, avec le lien vers l'implémentation. :material-check-decagram:
+Chaque exigence de l'énoncé, avec **sa preuve** : extrait de code réel, configuration, ou
+déploiement en ligne. Les blocs « Preuve » sont repliés, cliquez pour les ouvrir.
+
+[](https://github.com/Sorway/DevSecOps/actions/workflows/ci-cd.yml)
+
+**Preuves en ligne :** [Frontend](https://sorway.github.io/DevSecOps/) ·
+[API `/api/health`](https://projet-final-inky-iota.vercel.app/api/health) ·
+[Dépôt](https://github.com/Sorway/DevSecOps) ·
+[Image GHCR](https://github.com/Sorway/DevSecOps/pkgs/container/devsecops%2Fbackend)
## Gouvernance Git
-- [x] `staging` pivot d'intégration, CI complète à chaque événement — [détails](gouvernance.md)
+- [x] `staging` pivot d'intégration, CI complète à chaque événement
- [x] `main` production, push directs interdits (ruleset `REVIEW_REQUIRED`)
-- [x] `on: push/pull_request: [staging, main]`
-- [x] Blocs `if: github.ref == 'refs/heads/main'` + matrice `needs` stricte
-- [x] Job de production lié à un `environment` nommé (`production` / `github-pages`)
+- [x] Déclenché sur `staging` **et** `main`, `if` sur `main`, `needs` strict, `environment` nommé
+
+??? example "Preuve : déclenchement, gating conditionnel et environnement (`ci-cd.yml`)"
+
+ ```yaml
+ on:
+ push:
+ branches: [staging, main] # déclenché sur les deux branches
+ pull_request:
+ branches: [staging, main]
+
+ jobs:
+ deploy-backend:
+ if: github.ref == 'refs/heads/main' # bloc conditionnel de job
+ needs: [test, gitleaks, codeql, sbom-scan, docker-image] # dépendance stricte
+ environment: production # environnement nommé
+ ```
+
+ La protection de `main` est **effective** : toute PR y est en état `REVIEW_REQUIRED` et ne peut
+ être fusionnée sans revue (ruleset GitHub).
+
+## Durcissement local (Shift-Left)
+
+- [x] Hook `pre-commit` : `actionlint` + `gitleaks --staged` (bloquants)
+- [x] Refus des fichiers `.env` / `.pem` / `.key` avec message rouge, commit avorté
+- [x] Règle Gitleaks sur-mesure `SECWALLET_` + 24 caractères + entropie
+
+??? example "Preuve : contrôle de structure du hook (`scripts/pre-commit.sh`)"
+
+ ```bash
+ if [[ "$file" == *.env || "$file" == *.pem || "$file" == *.key ]]; then
+ echo -e "${RED}Sécurité : Tentative de commit d'un fichier de configuration ou d'une clé en clair. Opération annulée.${RESET}"
+ exit 1
+ fi
+ ```
-## Durcissement local
+??? example "Preuve : règle Gitleaks sur-mesure (`gitleaks.toml`)"
-- [x] Hook `pre-commit` : `actionlint` + `gitleaks --staged` (bloquants) — [détails](shift-left.md)
-- [x] Contrôle de structure `.env` / `.pem` / `.key` + message rouge, commit avorté
-- [x] Règle Gitleaks sur-mesure `SECWALLET_[A-Z0-9]{24}` + entropie
+ ```toml
+ [[rules]]
+ id = "secwallet-internal-token"
+ regex = '''SECWALLET_[A-Z0-9]{24}'''
+ entropy = 3.5
+ keywords = ["SECWALLET_"]
+ ```
-## Secrets par enveloppe
+## Secrets par enveloppe (SOPS / age)
-- [x] Paire de clés **age** (privée `ops.txt`, ignorée par Git) — [détails](secrets.md)
-- [x] `.github/secrets-prod.yaml` chiffré SOPS : valeurs `ENC[...]`, clés lisibles (`encrypted_regex`)
-- [x] Déchiffrement runtime **en RAM**, aucun secret sur le disque du runner
+- [x] Paire de clés **age**, privée `ops.txt` (ignorée par Git)
+- [x] Chiffrement SOPS **sélectif** : valeurs `ENC[...]`, clés lisibles
+- [x] Déchiffrement runtime **en RAM**, aucun secret sur le disque
+
+??? example "Preuve : chiffrement sélectif (`.github/secrets-prod.yaml`)"
+
+ ```yaml
+ production:
+ DATABASE_URL: ENC[AES256_GCM,data:TCwiv...,type:str] # valeur chiffrée
+ JWT_SECRET: ENC[AES256_GCM,data:5bSz4...,type:str]
+ API_KEY: ENC[AES256_GCM,data:oV5AU...,type:str]
+ sops:
+ encrypted_regex: ^(DATABASE_URL|JWT_SECRET|API_KEY)$ # seules ces valeurs
+ ```
+
+??? example "Preuve : déchiffrement en RAM, sans fichier (`ci-cd.yml`, job `deploy-backend`)"
+
+ ```bash
+ export SOPS_AGE_KEY # clé en mémoire seulement
+ secrets_json="$(sops -d --output-type json .github/secrets-prod.yaml)" # déchiffré en RAM
+ # aucun mktemp, aucun SOPS_AGE_KEY_FILE : rien n'est écrit sur le disque du runner
+ ```
## Conteneurisation & GHCR
-- [x] `Dockerfile` multi-stage, non-root (`node:24-alpine`) — [détails](conteneurisation.md)
-- [x] Build conditionné au filtrage de chemins (`dorny/paths-filter`)
-- [x] Scan de l'image (Trivy `HIGH,CRITICAL`) avant publication
-- [x] Publication GHCR conditionnelle (scan OK + `main`), tag = SHA du commit
+- [x] `Dockerfile` multi-stage, non-root (`node:24-alpine`)
+- [x] Build conditionné au filtrage de chemins
+- [x] Scan Trivy de l'image avant publication
+- [x] Publication GHCR conditionnelle (scan OK + `main`), tag = SHA
+
+??? example "Preuve : Dockerfile multi-stage non-root (`backend/Dockerfile`)"
+
+ ```dockerfile
+ FROM node:24-alpine AS deps
+ WORKDIR /app
+ COPY package*.json ./
+ RUN npm ci --omit=dev
+
+ FROM node:24-alpine AS runtime
+ RUN apk upgrade --no-cache \
+ && addgroup -S nodeapp && adduser -S nodeapp -G nodeapp
+ COPY --from=deps /app/node_modules ./node_modules
+ USER nodeapp
+ ```
+
+??? example "Preuve : filtrage, scan et push conditionnel (`ci-cd.yml`, job `docker-image`)"
+
+ ```yaml
+ - uses: dorny/paths-filter@v4
+ id: filter
+ with:
+ filters: |
+ backend:
+ - 'backend/**'
+ - '.github/workflows/ci-cd.yml'
+ - name: Scan backend image
+ if: steps.filter.outputs.backend == 'true'
+ run: trivy image --exit-code 1 --severity HIGH,CRITICAL "${{ steps.meta.outputs.image }}"
+ - name: Push backend image to GHCR
+ if: github.ref == 'refs/heads/main' && steps.filter.outputs.backend == 'true'
+ run: docker push "ghcr.io/${GITHUB_REPOSITORY,,}/backend:${GITHUB_SHA}"
+ ```
## Pipeline CI durci
-- [x] `permissions: contents: read` global, écritures isolées au job — [détails](ci.md)
+- [x] `permissions: contents: read` global, écritures isolées au job
- [x] Cache des dépendances Node
-- [x] CodeQL + upload SARIF, échec sur `High`/`Error` — [détails](sast.md)
-- [x] Tests bloquants ; Gitleaks exit ≠ 0 ; **aucun `continue-on-error: true`**
+- [x] CodeQL + upload SARIF, échec sur `High`/`Error`
+- [x] Tests bloquants, Gitleaks exit ≠ 0, **aucun `continue-on-error`**
+
+??? example "Preuve : moindre privilège (`ci-cd.yml`)"
+
+ ```yaml
+ permissions:
+ contents: read # global : lecture seule
+
+ jobs:
+ docker-image:
+ permissions:
+ packages: write # écriture isolée à ce job
+ codeql:
+ permissions:
+ security-events: write
+ ```
+
+??? example "Preuve : CodeQL fait échouer sur une faille majeure (`ci-cd.yml`)"
+
+ ```yaml
+ - name: Fail on CodeQL high severity
+ run: |
+ jq -e '[ .runs[].results[]
+ | select((.level=="error") or (.properties["security-severity"] // "0" | tonumber >= 7.0))
+ ] | length == 0' codeql-results/javascript.sarif
+ ```
## Composite Action
-- [x] `.github/actions/trivy-scan/action.yml` de type composite — [détails](composite-action.md)
-- [x] Entrée obligatoire = chemin du SBOM CycloneDX JSON
+- [x] `.github/actions/trivy-scan/action.yml` de type composite
+- [x] Entrée obligatoire = chemin du SBOM CycloneDX
- [x] `trivy sbom` : échec sur `CRITICAL`, avertissement sur `HIGH`/`MEDIUM`
-- [x] Installe Trivy elle-même (boîte noire réutilisable)
+- [x] Installe Trivy elle-même (boîte noire)
+
+??? example "Preuve : action composite (`.github/actions/trivy-scan/action.yml`)"
+
+ ```yaml
+ inputs:
+ sbom-path:
+ required: true # entrée obligatoire
+ runs:
+ using: composite
+ steps:
+ - name: Ensure Trivy is installed # boîte noire : elle s'installe seule
+ shell: bash
+ run: command -v trivy >/dev/null 2>&1 || { curl -sfL ... ; sudo install trivy /usr/local/bin/ ; }
+ - name: Scan CRITICAL vulnerabilities
+ shell: bash
+ run: trivy sbom --exit-code 1 --severity CRITICAL --format table "${{ inputs.sbom-path }}"
+ - name: Summarize HIGH and MEDIUM # avertissement seulement
+ shell: bash
+ run: trivy sbom --exit-code 0 --severity HIGH,MEDIUM ... >> "$GITHUB_STEP_SUMMARY"
+ ```
## Déploiement continu
-- [x] Frontend GitHub Pages, `main` uniquement, OIDC (`pages` + `id-token: write`) — [détails](deploiement.md)
+- [x] Frontend GitHub Pages, `main` uniquement, OIDC (`pages` + `id-token: write`)
- [x] `upload-pages-artifact` du `/frontend` + `deploy-pages` (hermétique)
-- [x] Backend Vercel en CLI, `needs` sécurité + livraison
-- [x] Déchiffrement SOPS en RAM, injection à la volée (`--env`)
+- [x] Backend Vercel, `needs` sécurité + livraison, secrets en RAM
+
+??? example "Preuve : frontend en OIDC (`ci-cd.yml`, job `deploy-frontend`)"
+
+ ```yaml
+ if: github.ref == 'refs/heads/main'
+ needs: [test, gitleaks, codeql, sbom-scan, docker-image]
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ permissions:
+ pages: write
+ id-token: write
+ ```
+
+??? example "Preuve : backend Vercel, secrets injectés à la volée (`ci-cd.yml`)"
+
+ ```bash
+ vercel pull --yes --environment=production --token "$VERCEL_TOKEN"
+ vercel deploy backend --prod --yes --token "$VERCEL_TOKEN" "${env_args[@]}" # --env=K=V en RAM
+ ```
## Robustesse
-- [x] Concurrence annulante (`cancel-in-progress: true`) — [détails](robustesse.md)
+- [x] Concurrence annulante (`cancel-in-progress: true`)
- [x] Healthcheck `curl --fail /api/health`, échec si ≠ 200
----
+??? example "Preuve : concurrence et healthcheck (`ci-cd.yml`)"
+
+ ```yaml
+ concurrency:
+ group: ci-cd-${{ github.ref }}
+ cancel-in-progress: true
-!!! success "Livrables"
- - **Frontend** : [sorway.github.io/DevSecOps](https://sorway.github.io/DevSecOps/)
- - **API** : [projet-final-inky-iota.vercel.app](https://projet-final-inky-iota.vercel.app) (`/api/health`)
- - **Dépôt** : [github.com/Sorway/DevSecOps](https://github.com/Sorway/DevSecOps)
+ # ... en fin de job deploy-backend :
+ - name: Healthcheck
+ run: curl --fail --silent --show-error "${{ steps.vercel.outputs.url }}/api/health"
+ ```
diff --git a/documentation/docs/conteneurisation.md b/documentation/docs/conteneurisation.md
index 2a2f404..6b078c1 100644
--- a/documentation/docs/conteneurisation.md
+++ b/documentation/docs/conteneurisation.md
@@ -1,5 +1,7 @@
# Conteneurisation & GHCR
+{ .logo }
+
L'artefact serveur du backend est construit et publié en image Docker **sécurisée**.
## Dockerfile multi-stage non-root
diff --git a/documentation/docs/contexte.md b/documentation/docs/contexte.md
index 618b51d..0bd7eb6 100644
--- a/documentation/docs/contexte.md
+++ b/documentation/docs/contexte.md
@@ -1,72 +1,349 @@
# Contexte & consignes
-## Le sujet
+Les documents originaux fournis pour ce projet :
-**Industrialisation, durcissement et architecture CI/CD.** Une équipe de développement nous confie
-le dépôt d'une application scindée en deux composants au sein d'un même dépôt :
+[:material-file-pdf-box: Sujet original (PDF)](assets/sujet-projet-final.pdf){ .md-button .md-button--primary }
+[:material-folder-zip: Base de code fournie (ZIP)](assets/skeleton-projet-final.zip){ .md-button }
-- **`frontend/`** : une Single Page Application entièrement statique (HTML, CSS, JavaScript moderne)
- qui consomme l'API.
-- **`backend/`** : une API REST Node.js / Express manipulant des **données hautement sensibles**,
- nécessitant des clés d'API et des accès à des infrastructures externes.
+## Le point de départ
-!!! quote "Objectif"
- Structurer la gouvernance Git pour **interdire toute action non auditée** et bâtir un flux où le
- code de production est **techniquement validé** avant son déploiement.
+On nous confie un dépôt qui contient déjà l'application. Notre travail n'est donc pas d'écrire le
+code métier, mais de **l'industrialiser** : bâtir autour de lui une chaîne d'intégration et de
+déploiement qui rende chaque action tracée, vérifiée et reproductible. En clair, faire passer un
+projet du stade « ça marche sur ma machine » à celui d'une usine logicielle auditable.
-## Les exigences, par thème
+L'application a deux visages : un **frontend** (une SPA statique qui consomme l'API) et un
+**backend** (une API Node.js / Express qui manipule des données sensibles, avec des clés d'API et
+des accès à des infrastructures externes). Ce sont deux mondes très différents à livrer, donc on les
+traite séparément dans une même chaîne.
-=== "Gouvernance"
+Tout le projet tient dans une phrase, qu'on a gardée en tête à chaque décision :
- - `staging` = branche pivot d'intégration (CI complète à chaque événement).
- - `main` = production, **push directs interdits**.
- - Workflow déclenché sur `staging` **et** `main`, blocs `if: github.ref == 'refs/heads/main'`,
- matrice `needs` stricte, job de prod lié à un `environment` nommé.
+> **Aucun code n'atteint la production sans avoir été techniquement validé.**
-=== "Durcissement local"
+```mermaid
+flowchart LR
+ subgraph POSTE["Sur le poste"]
+ code[Écriture du code]:::actor --> hook[Hook pre-commit]:::warn
+ end
+ subgraph GH["Sur GitHub"]
+ hook --> stg[(staging)]:::info
+ stg --> ci[CI durcie]:::info
+ ci --> gate{Tout est vert ?}:::warn
+ gate -->|oui, via PR + revue| main[(main)]:::prod
+ gate -->|non| stop[Bloqué]:::danger
+ end
+ subgraph PROD["En production"]
+ main --> pages[GitHub Pages]:::ok
+ main --> vercel[Vercel]:::ok
+ end
+ classDef actor fill:#495057,stroke:#343a40,color:#fff;
+ classDef info fill:#1c7ed6,stroke:#1971c2,color:#fff;
+ classDef warn fill:#f59f00,stroke:#e8590c,color:#fff;
+ classDef ok fill:#12b886,stroke:#0ca678,color:#fff;
+ classDef prod fill:#0b7285,stroke:#095c6b,color:#fff;
+ classDef danger fill:#e03131,stroke:#c92a2a,color:#fff;
+```
- - Hook `pre-commit` : `actionlint` sur les workflows + `gitleaks` sur les modifications indexées.
- - Règle Gitleaks sur-mesure : jetons `SECWALLET_` + 24 caractères + entropie.
- - Refus des fichiers `.env` / `.pem` / `.key` dans l'index (message rouge, commit avorté).
+Chaque étage ci-dessus est une **garantie** ajoutée. On détaille maintenant chaque thème selon la
+même démarche : le besoin auquel il répond, ce qu'on a vu en cours et les bonnes pratiques associées,
+les contraintes précises de l'énoncé, ce qu'on a concrètement mis en place, et enfin les points à ne
+pas oublier.
-=== "Secrets"
+## 1. La gouvernance des branches
- - Paire de clés **age** (privée nommée `ops.txt`).
- - `.github/secrets-prod.yaml` chiffré via **SOPS** : seules les **valeurs** deviennent `ENC[...]`,
- les **clés** YAML restent lisibles (`git diff` propres).
+{ .logo-md }
-=== "Conteneurisation"
+
- - `Dockerfile` multi-stage (bonnes pratiques).
- - Build Docker conditionné au **filtrage de chemins**.
- - Scan de l'image (Trivy) **avant** publication.
- - Publication sur **GHCR** seulement si le scan réussit, image taggée au **SHA** du commit.
+- 🎯 Prise en compte du besoin dans une équipe, plusieurs personnes touchent au même code en
+ même temps. Sans règles, n'importe qui pourrait envoyer directement en production une modification
+ non testée, ou écraser le travail d'un autre. On voulait une organisation où la production reste
+ intouchable et où toute l'équipe se synchronise sur une base commune, sans conflit.
+- 🎓 Vu en cours et bonnes pratiques un dépôt sérieux repose sur un flux de branches clair,
+ souvent une variante allégée du Git Flow : une branche d'**intégration** où tout converge, et une
+ branche de **production verrouillée**. La bonne pratique clé, c'est que rien n'atteint la production
+ sans une **Pull Request relue par un pair**, garde-fou humain en plus des contrôles automatiques.
+- 📋 Contraintes de l'énoncé le workflow doit se déclencher sur `staging` **et** `main`, les
+ jobs de déploiement portent un `if` sur `main`, une matrice `needs` stricte les enchaîne, et le job
+ de production est rattaché à un `environment` nommé.
+- 🛠️ Ce qu'on met en place `staging` en branche par défaut, `main` protégée par un ruleset
+ GitHub qui exige une revue avant fusion, et la politique rendue lisible directement dans le YAML.
+- ⚠️ À ne pas oublier le push direct sur `main` est **interdit** (pas seulement déconseillé), la
+ politique doit être **visible dans le YAML**, et le job de prod porte un `environment` nommé.
-=== "CI durci"
+
- - `permissions: contents: read` global ; privilèges d'écriture isolés au job.
- - Cache des dépendances Node.
- - **CodeQL** (SARIF) ; échec si vulnérabilité `High`/`Error`.
- - Tests bloquants ; Gitleaks exit ≠ 0 ; **`continue-on-error: true` interdit**.
+```mermaid
+flowchart LR
+ feat[branche de travail]:::info -->|Pull Request| stg[(staging)]:::info
+ stg -->|CI complète à chaque push| check[Validation]:::warn
+ check -->|PR + revue obligatoire| main[(main)]:::prod
+ main -.->|push direct| blocked[Interdit par le ruleset]:::danger
+ classDef info fill:#1c7ed6,stroke:#1971c2,color:#fff;
+ classDef warn fill:#f59f00,stroke:#e8590c,color:#fff;
+ classDef prod fill:#0b7285,stroke:#095c6b,color:#fff;
+ classDef danger fill:#e03131,stroke:#c92a2a,color:#fff;
+```
-=== "Composite Action"
+## 2. Le durcissement local (Shift-Left)
- - `.github/actions/trivy-scan/action.yml` de type **composite**.
- - Entrée obligatoire : chemin du SBOM **CycloneDX JSON**.
- - Échec **uniquement** sur `CRITICAL` ; `HIGH`/`MEDIUM` → avertissement dans le résumé.
+{ .logo-md }
-=== "Déploiement & robustesse"
+
- - Frontend → **GitHub Pages** (OIDC, artefacts éphémères).
- - Backend → **Vercel** (déchiffrement SOPS en RAM, aucun secret sur disque).
- - Concurrence annulante ; **healthcheck** `/api/health` post-déploiement.
+- 🎯 Prise en compte du besoin plus une erreur est détectée tôt, moins elle coûte cher. Un
+ secret oublié ou un workflow invalide qui atteint GitHub, c'est déjà une trace publique et du temps
+ perdu. On voulait un premier filet **sur le poste du développeur**, avant même le premier `push`.
+- 🎓 Vu en cours et bonnes pratiques c'est le principe du **Shift-Left**, « décaler vers la
+ gauche » les contrôles de sécurité, c'est-à-dire les rapprocher du moment où le code est écrit.
+ L'outil standard est le **hook Git `pre-commit`**, un script qui s'exécute avant chaque commit et
+ peut le refuser.
+- 📋 Contraintes de l'énoncé valider séquentiellement `actionlint` puis `gitleaks` **sur les
+ fichiers indexés uniquement**, refuser tout `.env` / `.pem` / `.key` avec un message rouge, et
+ s'appuyer sur une règle Gitleaks sur-mesure pour les jetons `SECWALLET_`.
+- 🛠️ Ce qu'on met en place un hook **versionné** (auditable et réinstallable), trois barrières
+ bloquantes successives, et une règle `SECWALLET_[A-Z0-9]{24}` couplée à une vérification d'entropie.
+- ⚠️ À ne pas oublier le message rouge est imposé **au mot près**, gitleaks ne scanne que le
+ **staged**, la règle veut **exactement 24 caractères**, et le hook doit **réellement bloquer**.
-## Livrables
+
-Un fichier texte avec les **URLs Vercel et GitHub Pages** et l'URL du **dépôt public** contenant
-l'arborescence complète (frontend, backend, hooks locaux, structure `.github/`) montrant la
-protection des branches, le filtrage Docker et le chaînage des dépendances.
+```mermaid
+flowchart TB
+ start([git commit]):::info --> struct{Fichier .env / .pem / .key indexé ?}:::warn
+ struct -->|oui| abort[Commit avorté, message rouge]:::danger
+ struct -->|non| lint{actionlint OK ?}:::warn
+ lint -->|non| abort
+ lint -->|oui| leaks{gitleaks : aucun secret ?}:::warn
+ leaks -->|non| abort
+ leaks -->|oui| ok([Commit autorisé]):::ok
+ classDef info fill:#1c7ed6,stroke:#1971c2,color:#fff;
+ classDef warn fill:#f59f00,stroke:#e8590c,color:#fff;
+ classDef danger fill:#e03131,stroke:#c92a2a,color:#fff;
+ classDef ok fill:#12b886,stroke:#0ca678,color:#fff;
+```
-!!! success "Vérification"
- La page [Conformité aux consignes](conformite.md) reprend chaque exigence et pointe vers son
- implémentation dans le dépôt.
+## 3. Les secrets par enveloppe
+
+{ .logo-md }
+
+
+
+- 🎯 Prise en compte du besoin une API qui manipule des données sensibles a besoin de secrets
+ (URL de base de données, clé JWT, clés d'API). Le réflexe naïf serait un fichier `.env`, mais il
+ fuirait dans l'historique Git. On voulait **versionner** ces secrets sans jamais les exposer.
+- 🎓 Vu en cours et bonnes pratiques le **chiffrement par enveloppe** dans une logique
+ **GitOps** : stocker les secrets **chiffrés** dans le dépôt et ne les déchiffrer qu'au dernier
+ moment, au runtime. On utilise **age** pour la paire de clés et **SOPS** pour chiffrer le fichier.
+- 📋 Contraintes de l'énoncé clé privée nommée `ops.txt`, chiffrement **des seules valeurs**
+ (clés YAML lisibles pour des `git diff` propres), et déchiffrement en CD **en RAM**, sans jamais
+ écrire de secret sur le disque du runner.
+- 🛠️ Ce qu'on met en place une paire `age`, un `encrypted_regex` qui ne cible que les valeurs,
+ et la clé `SOPS_AGE_KEY` lue directement depuis une variable d'environnement, donc en mémoire.
+- ⚠️ À ne pas oublier `ops.txt` reste **hors du dépôt**, **seules les valeurs** sont chiffrées,
+ et **aucun fichier de secret en clair** ne doit toucher le disque (donc pas de `mktemp`).
+
+
+
+```mermaid
+flowchart LR
+ clair[secrets-prod.yaml en clair]:::warn -->|sops chiffre les valeurs| enc[Valeurs en ENC..., clés lisibles]:::info
+ enc -->|commité sans risque| repo[(Dépôt)]:::info
+ repo -->|au déploiement| ram[Déchiffrement en RAM]:::ok
+ ram -->|injecté à la volée| vercel[Vercel]:::prod
+ ram -.->|jamais| disk[Fichier sur disque]:::danger
+ classDef warn fill:#f59f00,stroke:#e8590c,color:#fff;
+ classDef info fill:#1c7ed6,stroke:#1971c2,color:#fff;
+ classDef ok fill:#12b886,stroke:#0ca678,color:#fff;
+ classDef prod fill:#0b7285,stroke:#095c6b,color:#fff;
+ classDef danger fill:#e03131,stroke:#c92a2a,color:#fff;
+```
+
+## 4. La conteneurisation et GHCR
+
+{ .logo-md }
+
+
+
+- 🎯 Prise en compte du besoin pour livrer le backend de façon fiable, il faut qu'il tourne
+ partout pareil. On voulait un artefact reproductible (une image Docker), sans le reconstruire à
+ chaque fois qu'on modifie une virgule du frontend.
+- 🎓 Vu en cours et bonnes pratiques le **multi-stage build** (on installe dans un premier
+ étage, on ne garde que le nécessaire dans l'image finale, plus légère et avec moins de surface
+ d'attaque), l'exécution **non-root**, le **scan de vulnérabilités avant publication**, et un tag
+ **immuable** (le SHA) plutôt que `latest`.
+- 📋 Contraintes de l'énoncé image multi-stage, build **conditionné au filtrage de chemins**,
+ scan Trivy **avant** publication, et push GHCR seulement si le scan est clean, taggé au SHA.
+- 🛠️ Ce qu'on met en place un `Dockerfile` à deux étages (`deps` puis `runtime`), un
+ utilisateur dédié `nodeapp`, un filtre `dorny/paths-filter`, un `trivy image` bloquant, et un push
+ conditionnel.
+- ⚠️ À ne pas oublier multi-stage **et** non-root, rebuild **seulement si** le backend change,
+ scan **avant** le push, et tag **= SHA** (pas `latest`).
+
+
+
+```mermaid
+flowchart LR
+ change{backend modifié ?}:::warn -->|non| skip[On ne rebuild pas]:::info
+ change -->|oui| build[docker build multi-stage]:::info
+ build --> scan{Trivy : HIGH / CRITICAL ?}:::warn
+ scan -->|vulnérable| fail[Échec, aucun push]:::danger
+ scan -->|clean ET branche main| push[Push GHCR taggé au SHA]:::ok
+ classDef warn fill:#f59f00,stroke:#e8590c,color:#fff;
+ classDef info fill:#1c7ed6,stroke:#1971c2,color:#fff;
+ classDef danger fill:#e03131,stroke:#c92a2a,color:#fff;
+ classDef ok fill:#12b886,stroke:#0ca678,color:#fff;
+```
+
+## 5. La CI durcie, une vraie barrière
+
+{ .logo-md }
+
+
+
+- 🎯 Prise en compte du besoin une chaîne CI/CD n'a de valeur que si elle est **infranchissable**.
+ Si un développeur peut contourner un test qui échoue, la barrière ne sert plus à rien. On voulait un
+ pipeline strict, où un seul contrôle en échec bloque tout le reste.
+- 🎓 Vu en cours et bonnes pratiques le **moindre privilège** sur les jetons, l'analyse statique
+ (**SAST**) avec CodeQL, le principe **fail-fast**, et l'interdiction de tout contournement (le
+ fameux `continue-on-error`).
+- 📋 Contraintes de l'énoncé `permissions: contents: read` global, cache Node, CodeQL + SARIF
+ avec échec sur `High`/`Error`, tests et Gitleaks bloquants, `continue-on-error` interdit, CD dépendante.
+- 🛠️ Ce qu'on met en place des permissions globales en lecture seule (écritures ouvertes
+ uniquement dans le job concerné), un contrôle `jq` sur le rapport SARIF, et un graphe `needs` strict.
+- ⚠️ À ne pas oublier `contents: read` **global**, écritures **isolées**, `continue-on-error`
+ **interdit**, CodeQL doit **faire échouer** sur une faille majeure, et le **SARIF est téléversé**.
+
+
+
+```mermaid
+flowchart LR
+ V[validate-workflows]:::info --> T[test]:::info
+ V --> G[gitleaks]:::info
+ T --> C[codeql]:::info
+ T --> S[sbom-scan]:::info
+ T --> D[docker-image]:::info
+ G --> D
+ C --> D
+ S --> D
+ D --> DF[deploy-frontend]:::prod
+ D --> DB[deploy-backend]:::prod
+ classDef info fill:#1c7ed6,stroke:#1971c2,color:#fff;
+ classDef prod fill:#0b7285,stroke:#095c6b,color:#fff;
+```
+
+## 6. La composite action
+
+{ .logo-md }
+
+
+
+- 🎯 Prise en compte du besoin le scan de dépendances (via un SBOM) pourrait être réutilisé à
+ plusieurs endroits. Copier-coller la même suite de commandes serait fragile et pénible à maintenir.
+ On voulait un composant unique, autonome et réutilisable.
+- 🎓 Vu en cours et bonnes pratiques le principe **DRY** (« Don't Repeat Yourself »), qui pousse
+ à factoriser la logique répétée, et la notion de **boîte noire** : une action masque sa complexité
+ derrière des **entrées** et **sorties** claires.
+- 📋 Contraintes de l'énoncé type `composite`, **entrée obligatoire** (le chemin du SBOM
+ CycloneDX), échec **uniquement** sur `CRITICAL`, et simple **avertissement** pour `HIGH` et `MEDIUM`.
+- 🛠️ Ce qu'on met en place un `action.yml` composite, une entrée requise, l'installation de
+ Trivy intégrée à l'action, et deux niveaux de sévérité distincts.
+- ⚠️ À ne pas oublier type **composite** avec entrée **obligatoire**, échec **seulement sur
+ CRITICAL**, `HIGH`/`MEDIUM` en **avertissement**, et l'action **installe Trivy elle-même**.
+
+
+
+```mermaid
+flowchart LR
+ sbom[SBOM CycloneDX]:::info --> action[Action trivy-scan]:::info
+ action --> crit{CRITICAL ?}:::warn
+ crit -->|oui| fail[Build en échec]:::danger
+ crit -->|non| high{HIGH / MEDIUM ?}:::warn
+ high -->|oui| warn[Avertissement dans le résumé]:::warnnode
+ high -->|non| ok[Rien à signaler]:::ok
+ warn --> continue[Le pipeline continue]:::ok
+ classDef info fill:#1c7ed6,stroke:#1971c2,color:#fff;
+ classDef warn fill:#f59f00,stroke:#e8590c,color:#fff;
+ classDef warnnode fill:#f08c00,stroke:#e8590c,color:#fff;
+ classDef danger fill:#e03131,stroke:#c92a2a,color:#fff;
+ classDef ok fill:#12b886,stroke:#0ca678,color:#fff;
+```
+
+## 7. Le déploiement continu
+
+{ .logo-md }
+
+
+
+- 🎯 Prise en compte du besoin une fois la CI verte, on veut une mise en production
+ **automatique** et sans intervention manuelle, mais uniquement pour du code validé, et sans laisser
+ traîner un secret au passage.
+- 🎓 Vu en cours et bonnes pratiques l'**OIDC** (OpenID Connect), qui donne à GitHub une
+ **identité temporaire** auprès du service cible sans secret longue durée (plus sûr qu'un token
+ permanent), le déploiement **hermétique** (artefact éphémère), et l'injection des secrets **à la volée**.
+- 📋 Contraintes de l'énoncé déploiement **sur `main` uniquement** et seulement si toute la CI
+ est verte, frontend sur GitHub Pages en **OIDC** (`pages: write` + `id-token: write`), backend sur
+ Vercel avec secrets déchiffrés en RAM.
+- 🛠️ Ce qu'on met en place deux jobs conditionnés à `main` et dépendants de tout,
+ `upload-pages-artifact` + `deploy-pages` pour le frontend, et `vercel deploy` avec les variables en `--env`.
+- ⚠️ À ne pas oublier déploiement sur `main` **uniquement** et CI **verte**, **OIDC** (`pages` +
+ `id-token`), et secrets **injectés à la volée** sans rien écrire sur le disque.
+
+
+
+```mermaid
+flowchart LR
+ ci[CI 100% verte sur main]:::ok --> front[deploy-frontend]:::prod
+ ci --> back[deploy-backend]:::prod
+ front --> pages[GitHub Pages, OIDC]:::info
+ back --> vercel[Vercel, secrets en RAM]:::info
+ vercel --> health{/api/health = 200 ?}:::warn
+ health -->|non| alert[Job en échec, alerte]:::danger
+ health -->|oui| done[Déploiement validé]:::ok
+ classDef ok fill:#12b886,stroke:#0ca678,color:#fff;
+ classDef prod fill:#0b7285,stroke:#095c6b,color:#fff;
+ classDef info fill:#1c7ed6,stroke:#1971c2,color:#fff;
+ classDef warn fill:#f59f00,stroke:#e8590c,color:#fff;
+ classDef danger fill:#e03131,stroke:#c92a2a,color:#fff;
+```
+
+## 8. La robustesse
+
+
+
+- 🎯 Prise en compte du besoin deux risques restaient à couvrir. Gaspiller des ressources (et
+ créer des conflits de déploiement) quand plusieurs commits arrivent coup sur coup, et déployer une
+ version cassée sans s'en rendre compte.
+- 🎓 Vu en cours et bonnes pratiques l'**annulation de concurrence**, qui stoppe
+ automatiquement les exécutions devenues inutiles, et le **healthcheck** (smoke test) juste après le
+ déploiement, une petite requête qui confirme que l'application répond vraiment.
+- 📋 Contraintes de l'énoncé si deux commits sont poussés coup sur coup, le pipeline du premier
+ doit **s'annuler immédiatement**, et une requête `curl` vers `/api/health` doit **faire échouer** le
+ job si la réponse n'est pas `200`.
+- 🛠️ Ce qu'on met en place un bloc `concurrency` avec `cancel-in-progress`, et un `curl --fail`
+ sur l'URL de production générée dynamiquement par Vercel.
+- ⚠️ À ne pas oublier annulation **immédiate** du run périmé, healthcheck sur l'**URL
+ dynamique**, et tout code de réponse **différent de 200** fait **échouer** le job.
+
+
+
+```mermaid
+flowchart LR
+ push[Deux push rapprochés]:::warn --> cancel[Le 1er pipeline s'annule]:::info
+ cancel --> deploy[Déploiement du plus récent]:::prod
+ deploy --> health{/api/health = 200 ?}:::warn
+ health -->|oui| ok[Tout va bien]:::ok
+ health -->|non| alert[Job en échec, alerte équipe]:::danger
+ classDef warn fill:#f59f00,stroke:#e8590c,color:#fff;
+ classDef info fill:#1c7ed6,stroke:#1971c2,color:#fff;
+ classDef prod fill:#0b7285,stroke:#095c6b,color:#fff;
+ classDef ok fill:#12b886,stroke:#0ca678,color:#fff;
+ classDef danger fill:#e03131,stroke:#c92a2a,color:#fff;
+```
+
+Chaque exigence est reprise, **avec sa preuve** (extrait de code, configuration, déploiement en
+ligne), sur la page [Conformité](conformite.md). Le détail technique complet se trouve dans la section
+[Implémentation](architecture.md).
+
+
diff --git a/documentation/docs/deploiement.md b/documentation/docs/deploiement.md
index f061bd9..e7665d1 100644
--- a/documentation/docs/deploiement.md
+++ b/documentation/docs/deploiement.md
@@ -1,9 +1,13 @@
# Déploiement continu
+{ .logo }
+
Le déploiement ne se déclenche **que si toute la CI est verte** et **uniquement sur `main`**.
## Frontend → GitHub Pages
+{ .logo }
+
Publication hermétique via **OIDC**, sans laisser de trace de build dans l'historique Git.
```yaml
@@ -30,6 +34,9 @@ deploy-frontend:
## Backend → Vercel
+{ .logo }
+{ .logo }
+
Déploiement piloté **en ligne de commande** dans GitHub Actions, secrets déchiffrés **en RAM**.
```yaml
diff --git a/documentation/docs/gouvernance.md b/documentation/docs/gouvernance.md
index c54e4ad..81a9d97 100644
--- a/documentation/docs/gouvernance.md
+++ b/documentation/docs/gouvernance.md
@@ -1,5 +1,7 @@
# Gouvernance Git
+{ .logo }
+
La politique de sécurité est **lisible directement dans le YAML** : à la simple lecture du workflow,
un auditeur constate qu'un déploiement sur `main` exige le succès absolu des jobs amont.
diff --git a/documentation/docs/index.md b/documentation/docs/index.md
index 748b1da..eec433b 100644
--- a/documentation/docs/index.md
+++ b/documentation/docs/index.md
@@ -1,64 +1,72 @@
-# SecureWallet
+# SecureWallet, une chaîne de livraison sécurisée ! ♾️
-**Chaîne de livraison auditable, hermétique et infalsifiable** pour une SPA statique et une API
-Node.js manipulant des données sensibles, industrialisées en un pipeline durci où *aucun code
-n'atteint la production sans validation technique*.
+{ .hero }
-!!! note "Projet final — DevSecOps avancé (Sophia Ynov Campus)"
- Une équipe de développement nous confie le dépôt d'une application : une SPA statique
- (`frontend/`) et une API Node.js / Express (`backend/`) manipulant des données hautement
- sensibles (clés d'API, accès à des infrastructures externes). **Notre mission :
- industrialiser, durcir et sécuriser toute la chaîne CI/CD** — gouvernance Git, secrets
- chiffrés de bout en bout, conteneurisation, analyse statique, déploiement continu.
+## Projet final DevSecOps · M1 Cloud, Sécurité & Infrastructure
-[:material-web: Frontend (GitHub Pages)](https://sorway.github.io/DevSecOps/){ .md-button .md-button--primary }
-[:material-server: API (Vercel)](https://projet-final-inky-iota.vercel.app){ .md-button }
+
+
+
+
+
+
+
+
+**SecureWallet** industrialise une SPA statique et une API **Node.js** { .inline-logo } / Express en une **chaîne CI/CD durcie**, auditable et infalsifiable. L'image backend **Docker** { .inline-logo } (multi-stage, non-root) est scannée par **Trivy** { .inline-logo } puis publiée sur **GHCR**. Le code passe au crible de **CodeQL** { .inline-logo }, les secrets sont traqués par **Gitleaks** { .inline-logo } et chiffrés par enveloppe avec **SOPS** { .inline-logo } + age.
+
+Le tout est orchestré par **GitHub Actions** { .inline-logo } et déployé sur **GitHub Pages** { .inline-logo } (frontend, via OIDC) et **Vercel** { .inline-logo } (backend). Ce projet conclut un bloc de formation d'environ **28 h** de DevSecOps à Sophia Ynov Campus, en filière Cloud, Sécurité & Infrastructure.
+
+[:material-web: Frontend](https://sorway.github.io/DevSecOps/){ .md-button .md-button--primary }
+[:material-server: API](https://projet-final-inky-iota.vercel.app){ .md-button }
[:material-github: Dépôt](https://github.com/Sorway/DevSecOps){ .md-button }
-## Les deux composants
-
-| Composant | Description | Livraison |
-|-----------|-------------|-----------|
-| `frontend/` | Single Page Application 100 % statique (HTML / CSS / JS moderne) consommant l'API | **GitHub Pages** via OIDC |
-| `backend/` | API REST Node.js / Express manipulant des données sensibles | **Docker → GHCR** + **Vercel** |
-
-## Le pipeline en un coup d'œil
-
-À la simple lecture de [`ci-cd.yml`](https://github.com/Sorway/DevSecOps/blob/main/.github/workflows/ci-cd.yml),
-un auditeur constate qu'un déploiement sur `main` **exige le succès absolu** des jobs de validation amont.
-
-```mermaid
-flowchart LR
- subgraph CI["Intégration continue (staging + main)"]
- direction LR
- A[validate-workflows] --> B[test]
- A --> G[gitleaks]
- B --> C[codeql]
- B --> S[sbom-scan]
- B --> D
- G --> D
- C --> D
- S --> D
- D[docker-image]
- end
- subgraph CD["Déploiement (main uniquement)"]
- direction LR
- D --> F[deploy-frontend]
- D --> K[deploy-backend]
- end
- classDef prod fill:#0b7285,stroke:#0b7285,color:#fff;
- class F,K prod;
-```
-
-## Points clés
-
-- :material-source-branch: **Gouvernance** : `staging` pivot d'intégration, `main` protégée (revue + status checks, push direct interdit).
-- :material-shield-check: **Shift-Left** : hook `pre-commit` (actionlint + gitleaks + refus des `.env`/`.pem`/`.key`).
-- :material-lock: **Secrets** : chiffrement par enveloppe SOPS + age, déchiffrés en RAM, jamais sur disque.
-- :material-docker: **Conteneur** : Dockerfile multi-stage non-root, scan Trivy, publication conditionnelle sur GHCR taggée au SHA.
-- :material-magnify-scan: **CI hermétique** : moindre privilège, cache npm, CodeQL (SARIF), barrière stricte sans `continue-on-error`.
-- :material-rocket-launch: **CD** : GitHub Pages (OIDC) + Vercel (secrets injectés à la volée), healthcheck post-déploiement.
-
-!!! tip "Par où commencer"
- Lisez d'abord le [Contexte & consignes](contexte.md), puis suivez la navigation dans l'ordre.
- La page [Conformité aux consignes](conformite.md) récapitule, point par point, la couverture de l'énoncé.
+## L'Équipe sur ce lab
+
+