Aller au contenu principal

Exceptions

Une exception accepte un risque pour une durée limitée : une vulnérabilité que vous ne pouvez pas encore corriger, un résultat de licence sur un outil interne, un secret dans une fixture de test. Elle fonctionne de la même manière pour tous les types de résultats (dépendances, images de conteneurs, SAST, licences, secrets, malware dans les dépendances, paquets du registre) et pour les exceptions de politique de paquets.

Une exception ne prend effet que lorsqu’une seconde personne l’approuve. D’ici là, le résultat reste ouvert et la politique continue de s’appliquer.

Une mise à l’écart est une exception qui n’a nécessité aucune approbation. Les mises à l’écart, y compris celles qui valent pour tout un dépôt ou toute une image, sont listées, consultées et révoquées au même endroit et par la même API que les exceptions approuvées ; voir Exceptions sans approbation.

Fonctionnement d’une exception

  1. Demande. Toute personne qui peut travailler sur les résultats en demande une, avec une justification et une date d’expiration. Cette date peut être au plus éloignée de la durée maximale fixée par votre organisation (366 jours, sauf si vous en fixez une plus courte).
  2. Approbation ou rejet. Un approbateur autre que le demandeur décide, avec un commentaire facultatif. Les approbateurs reçoivent un e-mail à l’arrivée d’une demande, dans leur propre langue.
  3. En vigueur. Le résultat est écarté jusqu’à la date d’expiration. Le résultat enregistre qui a demandé (dismissed_by) et qui a approuvé (dismissal_approved_by). Une nouvelle analyse conserve la mise à l’écart.
  4. La fin. À sa date d’expiration, le résultat est rouvert, et l’exception est marquée expired.

Les états sont requested, approved, rejected, expired et revoked.

Qui peut approuver

Personne ne peut approuver sa propre demande : la console ne lui affiche pas de bouton Approuver, et l’API répond self-approve. Un jeton derrière lequel il n’y a aucune personne, ainsi que l’identifiant d’un agent IA, ne peuvent pas du tout statuer sur une exception (user-identity-required).

Révocation automatique

Une exception approuvée est révoquée, et son résultat rouvert immédiatement, lorsque :

DéclencheurMotif de révocation
Quelqu’un la révoque manuellementmanual
La gravité du résultat dépasse ce qui a été approuvéseverity_raised
Une nouvelle vulnérabilité apparaît sur le même composant (même paquet et même version, ou même paquet d’image)new_vulnerability
L’approbateur quitte l’organisationapprover_left
L’exception reposait sur un verdict non atteignable, et une analyse ultérieure trouve le résultat atteignable ou ne peut pas trancherreachability_changed

Le départ de l’approbateur est traité au moment du changement d’appartenance ; les autres vérifications s’exécutent toutes les heures. reachability_changed révoque aussi une simple mise à l’écart qui reposait sur un verdict d’absence de chemin d’appel ; voir Atteignabilité.

Audit et SIEM

Chaque étape est un événement d’audit du tenant : code-security.exception.requested, .approved, .rejected, .revoked et .expired pour les résultats, et package_policy.exception.* pour les politiques de paquets. Un événement de révocation porte son motif et ce qui a changé. Comme tout événement d’audit, ils atteignent vos points de terminaison SIEM ; filtrez sur le préfixe d’action code-security.exception. pour ne recevoir que ceux-là.

Dans la console

Ouvrez Sécurité › Résultats › Exceptions. La page comporte :

  • En attente d’approbation : les demandes en attente, chacune avec Approuver et Rejeter et une zone de commentaire. Vos propres demandes n’affichent pas de bouton Approuver.
  • En vigueur : les exceptions approuvées, avec Révoquer.
  • Acceptés sans approbation : les mises à l’écart, y compris celles qui valent pour tout un dépôt ou toute une image, avec un filtre par type et Révoquer. Voir Exceptions sans approbation.
  • Historique : les exceptions rejetées, révoquées et expirées, avec qui a décidé, le commentaire et, pour une révocation, la raison.
  • Demander une exception pour un résultat : sur la page de n’importe quel résultat, le formulaire de mise à l’écart comporte un lien Demander une exception qui remplit ceci pour vous.
  • Paramètres : la durée maximale d’une exception, l’équipe d’approbateurs, et Ignorer un résultat nécessite une approbation.

API

URL de base https://frem.sh/_app/api/v1. Voir la référence de l’API pour la forme complète.

Méthode et cheminPortéeCe qu’il fait
GET /orgs/{org}/exceptionsfindings:readToutes les exceptions approuvées par une seconde personne, les plus récentes d’abord. Filtrez avec status (séparés par des virgules), kind (finding ou package) et scanner. Ajoutez approval=none pour les exceptions sans approbation, ou approval=all pour les deux.
POST /orgs/{org}/exceptionsfindings:writeEn demander une. Pour un résultat : scanner, finding_id, justification, expires_at. Pour une politique de paquets : "kind": "package" et les champs d’une exception de politique de paquets.
GET /orgs/{org}/exceptions/{id}findings:readUne exception, approuvée ou non.
POST /orgs/{org}/exceptions/{id}/approvefindings:writeApprouver, avec un {"comment": "..."} facultatif.
POST /orgs/{org}/exceptions/{id}/rejectfindings:writeRejeter, avec un commentaire facultatif.
POST /orgs/{org}/exceptions/{id}/revokefindings:writeRetirer une demande, ou révoquer une exception. Une exception valant pour tout un dépôt ou toute une image nécessite policy:write.
GET, PUT /orgs/{org}/exceptions/settingspolicy:read, policy:writemax_days (de 1 à 366), approver_team (un nom d’équipe, ou null pour les propriétaires plus la liste des approbateurs) et dismissals_require_approval (true ou false).

scanner vaut l’un de deps, images, sast, licenses, secrets, package_malware et packages ; finding_id est l’id que renvoient les points de terminaison des résultats. expires_at est une date (YYYY-MM-DD, jusqu’à la fin de ce jour en UTC) ou une heure ISO 8601. Une seconde demande pour un résultat qui a déjà une exception en attente ou approuvée répond 409 duplicate-pending.

Demander une exception pour un résultat de dépendance :

curl -sS -X POST https://frem.sh/_app/api/v1/orgs/acme/exceptions \
  -H "Authorization: Bearer $FREMFORGE_TOKEN" -H 'Content-Type: application/json' \
  -d '{"scanner":"deps","finding_id":"<id>","justification":"Not reachable from user input; fixed by the framework upgrade.","expires_at":"2026-12-31"}'

Une exception de licence pour la politique de paquets :

curl -sS -X POST https://frem.sh/_app/api/v1/orgs/acme/exceptions \
  -H "Authorization: Bearer $FREMFORGE_TOKEN" -H 'Content-Type: application/json' \
  -d '{"kind":"package","scope":"package","rule_kind":"licence","rule_selector":"GPL-3.0-only","pkg_type":"npm","name":"left-pad","justification":"Internal tool, never distributed.","expires_at":"2026-12-31"}'

Paramètres sous forme de code

PUT /orgs/{org}/exceptions/settings est une simple écriture idempotente : la durée maximale et l’équipe d’approbateurs peuvent donc vivre dans votre dépôt de configuration, à côté des autres paramètres de votre organisation :

curl -sS -X PUT https://frem.sh/_app/api/v1/orgs/acme/exceptions/settings \
  -H "Authorization: Bearer $FREMFORGE_TOKEN" -H 'Content-Type: application/json' \
  -d '{"max_days": 90, "approver_team": "security"}'

Exceptions sans approbation

Le bouton Ignorer d’un résultat est la façon de l’accepter à une seule personne. Il enregistre qui l’a écarté et pourquoi, et peut porter une date d’expiration. Il n’y a pas de seconde personne : c’est donc une exception sans approbation. Utilisez une exception approuvée lorsque vous avez besoin du principe des quatre yeux, ou activez Ignorer un résultat nécessite une approbation pour que chaque mise à l’écart faite par une personne qui n’est pas approbatrice passe par une telle exception.

Il en existe trois sortes, chacune listée sous Acceptés sans approbation dans la console et par GET /orgs/{org}/exceptions?approval=none :

SortescopeidCe qu’elle couvre
Mise à l’écartfindingdismissal.<scanner>.<hash>Un résultat. L’identifiant reste le même d’une analyse à l’autre. findings_count indique combien de résultats d’analyse elle couvre actuellement, par exemple le même résultat sur plusieurs pull requests.
Mise à l’écart de dépendance pour tout un dépôtreporepo.<uuid>Une alerte dans un paquet et un manifeste, dans chaque analyse ultérieure du dépôt.
Mise à l’écart pour toute une imageimageimage.<uuid>Un résultat d’image de conteneur dans chaque analyse ultérieure de l’image.

Chaque entrée porte "approval": "none", "status": "approved" (en vigueur) et les mêmes champs que toute exception : justification est le motif de la mise à l’écart, requested_by la personne qui l’a faite, et expires_at sa date d’expiration ou null. Avec approval=all, les exceptions approuvées portent "approval": "required". Sans le paramètre approval, la liste est exactement ce qu’elle était auparavant.

Une mise à l’écart échue n’est pas listée : la tâche quotidienne d’expiration rouvre ses résultats. Ne sont pas listés non plus : les résultats écartés par une exception approuvée (l’exception est l’entrée), les résultats disparus d’eux-mêmes, et les résultats de licence écartés par votre liste d’autorisation de licences, qui relèvent de cette liste.

Révoquer rouvre immédiatement les résultats, et un résultat de gravité élevée ou critique alerte de nouveau. Révoquer une mise à l’écart valant pour tout un dépôt ou toute une image supprime aussi la décision, de sorte que les analyses ultérieures cessent de la reporter ; cela nécessite policy:write, comme DELETE /orgs/{org}/code-security/deps/repo-dismissals/{id}. Chaque révocation est auditée : code-security.<scanner>.dismissal_revoked, ou code-security.deps.repo_dismissal_revoked et code-security.images.repo_dismissal_revoked.

curl -sS "https://frem.sh/_app/api/v1/orgs/acme/exceptions?approval=none&scanner=deps" \
  -H "Authorization: Bearer $FREMFORGE_TOKEN"

curl -sS -X POST "https://frem.sh/_app/api/v1/orgs/acme/exceptions/dismissal.deps.3f1c0a9e5b7d2c4e6f8a0b1c/revoke" \
  -H "Authorization: Bearer $FREMFORGE_TOKEN"

Les mises à l’écart nécessitent une approbation

Un paramètre par organisation, désactivé par défaut. Lorsqu’il est activé, la mise à l’écart d’un résultat de dépendance, SAST, secret, image de conteneur, licence ou malware de dépendance par une personne qui n’est pas approbatrice n’écarte rien. À la place :

  • chaque résultat ouvert reçoit une demande d’exception, avec le motif de la mise à l’écart comme justification et sa date d’expiration (la durée maximale de votre organisation lorsque la mise à l’écart n’en avait pas) ;
  • le résultat reste ouvert, et les approbateurs reçoivent un e-mail ;
  • un approbateur autre que la personne qui a écarté le résultat l’approuve ou la rejette dans la file. L’approbation écarte le résultat avec les deux noms, exactement comme toute autre exception. Personne ne peut approuver sa propre mise à l’écart.

Une mise à l’écart sur une branche de version protégée est une exception avec l’identifiant branch.<finding id> et la portée branch. Elle ne passe pas encore par cette file : tant que ce paramètre est activé, seul un approbateur peut écarter un résultat sur une branche de version, et toute autre personne reçoit 403 approval-required.

Les approbateurs continuent d’écarter directement. Dans la console, une mise à l’écart envoyée pour approbation vous amène à la file des exceptions avec un avis. Via l’API, chaque point de terminaison de mise à l’écart, unitaire ou groupée, répond alors 202 au lieu de 200 :

{
  "dismissed": false,
  "status": "pending_approval",
  "pending_count": 1,
  "exceptions": [
    { "id": "<exception id>", "finding_id": "<finding id>", "status": "pending_approval", "expires_at": "2027-10-11T15:00:00.000Z" }
  ],
  "skipped": []
}

skipped liste les résultats qui n’ont pas été mis en file : not-found (pas ouvert, ou pas à vous) ou duplicate-pending (il a déjà une exception en attente ou approuvée). Une mise à l’écart d’un seul résultat répond dans ces cas 404 ou 409 duplicate-pending. Une date d’expiration au-delà de votre durée maximale est refusée avec 400 invalid-expires-at.

Un résultat de malware sur un fichier envoyé (Sécurité › Résultats › Analyse antimalware des envois) n’a ni motif ni date d’expiration à faire passer par la file. Tant que le paramètre est activé, seul un approbateur peut en écarter un ; toute autre personne reçoit 403 not-an-approver.

Activez-le dans Sécurité › Résultats › Exceptions › Paramètres, via l’API :

curl -sS -X PUT https://frem.sh/_app/api/v1/orgs/acme/exceptions/settings \
  -H "Authorization: Bearer $FREMFORGE_TOKEN" -H 'Content-Type: application/json' \
  -d '{"dismissals_require_approval": true}'

ou avec exception_settings dans le module Terraform. L’activer ou le désactiver écrit les événements d’audit exceptions.dismissal_approval.enabled et .disabled. Une demande issue d’une mise à l’écart est l’événement ordinaire code-security.exception.requested avec "via": "dismissal". Les demandes déjà en attente restent dans la file lorsque vous le désactivez, et peuvent toujours être approuvées.