Aller au contenu principal
ApiGlow v0.3.0
Copier la page

Le même audit, en contrôle de build.

apiglow audit lance l’audit de schéma en ligne de commande. C’est le même moteur et les mêmes 137 règles que la page #/audit, et le même rapport en sortie. La commande ajoute seulement ce qu’un pipeline réclame autour : un code de sortie, des rapports qu’une machine ou une plateforme de CI sait lire, et une baseline pour qu’une API existante adopte le contrôle sans d’abord solder tout son passé.

Elle est livrée dans le paquet npm, sous la même licence MIT, et tourne sur Node 24 ou plus récent, comme le bake. Pas de compte, pas de service, pas de jeu de règles à écrire.

La lancer

npx apiglow@0.3.0 audit openapi.yaml

Le rapport part sur stdout. Tout ce qui concerne le passage — chaque contrôle, le verdict, les avertissements — part sur stderr :

PASS  --fail-on error: 0 finding(s) at this severity or above
Audit passed

Épinglez la version. Une nouvelle version peut ajouter une règle, et une règle ajoutée sous un pipeline, c’est un build qui casse sans que personne n’ait rien changé. Montez l’épingle exprès, comme vous montez le tag du CDN.

Ce que la commande accepte :

  • Un schéma — un chemin ou une URL, en JSON ou en YAML, valide ou non. Un fichier abîmé a quand même son rapport (voir le code de sortie).
  • Plusieurs schémas — plusieurs chemins, ou un motif entre guillemets pour que ce soit la commande qui l’étende et non le shell : 'apis/**/openapi.{yaml,json}'. Chaque fichier est audité comme sa propre spec, sous son chemin relatif au répertoire de travail : les rapports et la baseline nomment le fichier, et ajouter un schéma ne décale jamais les autres. node_modules et .git ne sont jamais parcourus. Un motif qui ne trouve rien donne un avertissement ; --fail-on-unmatched-globs en fait un arrêt.
  • --config apidoc.config.json — l’objet JSON que votre page place dans #api-doc-config. L’audit porte alors sur ce que la documentation montre vraiment : ses overlays appliqués, ses opérations cachées marquées comme telles, chaque spec d’une installation multi-spec. Les fichiers que la configuration nomme se lisent depuis son propre répertoire, comme le bake les lit.

Ce qui fait échouer le build

Quatre contrôles, chacun sur sa propre ligne PASS / FAIL. Un seul contrôle en échec fait échouer le passage.

OptionÉchoue quandPar défaut
--fail-on error|warning|info|noneun constat de cette sévérité, ou d’une pire, est présenterror
--min-grade A|B|C|D|Fla note globale est en dessousdésactivé
--min-score 0-100le score global est en dessousdésactivé
--baseline <fichier>la baseline liste des constats qui n’existent plusactif avec une baseline

Quand --fail-on échoue, stderr liste les constats responsables — les vingt premiers, chacun en fichier:ligne:colonne, puis un décompte :

FAIL  --fail-on warning: 14 finding(s) at this severity or above
  warning responses-success — openapi.yaml:2149:9 · GET /failures/forbidden · /paths/~1failures~1forbidden/get/responses
  warning oauth-legacy-flows — openapi.yaml:2924:11 · components.securitySchemes.petstore_auth · …

Ces lignes de la commande restent en anglais quelle que soit la langue du rapport.

Le code de sortie

CodeSignification
0tous les contrôles sont passés
1un contrôle a échoué
2l’audit n’a pas pu tourner : une option inconnue, une valeur hors bornes, un schéma injoignable, une configuration de règles illisible

Chaque option est validée avant le moindre chargement : une faute de frappe dans un seuil ne coûte ni un téléchargement, ni un « pas de seuil » silencieux.

Un fichier que la commande peut ouvrir a toujours son rapport, aussi abîmé soit-il. Une clé en double, une tabulation en guise d’indentation, une virgule de trop, un fichier tronqué : chacun devient un constat document-syntax à sa ligne et sa colonne, et le reste du fichier est noté quand même. Un fichier sans aucune description OpenAPI — vide, une liste, du Swagger 1.x, de l’OpenAPI 4 — devient un constat document-openapi. Les deux sont des erreurs : le --fail-on error par défaut fait donc échouer le passage avec le code 1. Le code 2 est réservé à un schéma qu’on n’a pas pu atteindre.

L’adopter sur une API existante

Une API qui a des années d’histoire ne passera pas le premier jour, et n’a pas à le faire. Une baseline enregistre les constats que l’équipe a vus et acceptés. Versionnée à côté du schéma, elle fait que --fail-on ne compte que les constats qu’elle ne liste pas :

# une fois : enregistrer l'existant
npx apiglow@0.3.0 audit openapi.yaml --write-baseline audit-baseline.json

# en CI : n'échouer que sur la nouveauté
npx apiglow@0.3.0 audit openapi.yaml --baseline audit-baseline.json

Son comportement :

  • Un constat s’identifie par sa règle et son pointeur JSON — les deux choses qui survivent à une modification sans rapport. Les messages et les numéros de ligne, non : un paramètre renommé réécrit les deux.
  • Elle compte, ce n’est pas un ensemble. Un pointeur listé deux fois couvre deux occurrences ; une troisième est nouvelle.
  • La note et le score l’ignorent. Ils décrivent le document. Une baseline accepte des constats, pas une note plus basse.
  • Ce n’est pas une liste d’exclusion. Chaque constat reste dans le rapport ; en JSON, les connus portent known: true.
  • Le fichier est trié de bout en bout. Le réécrire sur un schéma inchangé le reproduit octet pour octet : un diff de revue ne montre que ce qui a bougé.
{ "format": "apiglow-audit-baseline", "version": 1,
  "specs": { "default": { "parameter-described": ["/paths/~1pets/get/parameters/0"] } } }

Les entrées périmées font échouer le passage. Quand un constat est corrigé, son entrée ne correspond plus à rien. Laissée là, elle masquerait un jour un nouveau constat tombé sur le même pointeur — le passage échoue donc et la liste. --prune-baseline <fichier> réécrit la baseline sans ses entrées périmées (ce peut être le fichier --baseline lui-même) et passe. --allow-stale-baseline se contente de les lister.

Un fichier qui n’est pas une baseline écrite par cette version arrête le passage avec le code 2. Il n’est jamais lu comme vide, ce qui ferait échouer chaque constat, ni comme couvrant tout, ce qui laisserait passer une régression.

Les rapports

--formatPourCe que c’est
textun terminalle rapport console, replié par règle comme la page ; le format par défaut
markdownune personnel’export Markdown de la page — un commentaire de pull request, un résumé de job GitHub
jsonun script ou un agentle rapport complet, dans un contrat versionné
sarifle code scanning de GitHubSARIF 2.1.0 : les constats s’ouvrent et se ferment comme des alertes
githubune pull request GitHubdes commandes de workflow qui annotent le diff à la ligne
codequalityune merge request GitLabGitLab Code Quality, pour le widget de merge request

Un passage, plusieurs rapports. --format et --output règlent le rapport sur stdout. --report <format>=<fichier>, répétable, écrit un rapport de plus par occurrence, depuis le même passage — le JSON pour un script, le Markdown pour le résumé du job, sans auditer deux fois :

npx apiglow@0.3.0 audit openapi.yaml --format github \
  --report markdown="$GITHUB_STEP_SUMMARY" --report sarif=audit.sarif

Chaque constat est placé. Un schéma lu depuis un fichier donne à chaque constat son fichier:ligne:colonne — l’endroit où son nœud se trouve dans le fichier que vous éditez, en suivant les $ref jusque dans d’autres fichiers. Terminaux et éditeurs en font un lien, et les trois formats de CI s’en servent pour poser le constat sur la bonne ligne.

Ce qu’un rapport liste, dans tous les formats :

  • --min-severity error|warning|info garde les constats de cette sévérité ou pire. Le schéma REST de GitHub donne quelque 5 000 constats, dont 4 000 notes.
  • --only-new, avec --baseline, ne garde que les constats que la baseline ne liste pas.

Aucun des deux ne change le verdict : les comptes, les scores, la note et chaque contrôle lisent toujours le rapport entier. Le texte et le Markdown disent combien de constats chaque filtre a laissés de côté. Un --fail-on moins sévère que --min-severity est refusé, puisqu’un job échouerait sur des constats que son rapport ne montre pas.

--language fr écrit le rapport en français : messages, justifications, correctifs. Les lignes de la commande elle-même, sur stderr, restent en anglais.

Le rapport JSON

Ce que lit un script ou un agent. format et version nomment la forme, et tout changement de forme incrémente version : un consommateur peut refuser ce qu’il ne connaît pas.

{ "format": "apiglow-audit-report", "version": 1,
  "tool": { "name": "apiglow", "version": "0.3.0" },
  "passed": false,
  "specs": [{ "id": "default", "source": "openapi.yaml", "passed": false,
              "gates": [{ "gate": "fail-on", "threshold": "error", "actual": 2, "passed": false }],
              "report": { "…": "…", "categories": [{ "findings": [{ "fingerprint": "9f3c…" }] }] } }],
  "rules": { "parameter-described": { "category": "completeness",
             "label": "…", "message": "…", "why": "…", "fix": "…" } } }
  • Chaque constat porte une empreinte qui reste la même d’un passage à l’autre tant que le document change autour de lui. C’est sur elle que s’appuie une plateforme de CI ou un agent qui suit un constat.
  • rules porte, une seule fois, les textes de chaque règle déclenchée — son libellé, pourquoi elle compte, comment corriger — si bien qu’un consommateur qui explique un constat n’a besoin de rien d’autre que le fichier.

La configuration des règles

Éteignez une règle, ou changez sa sévérité, pour tout le document ou sous certains chemins seulement. Le bloc est celui que lit la page : la note publiée et la note de CI concordent.

{
  "audit": {
    "rules": { "property-described": "off", "parameter-described": "error" },
    "overrides": [
      { "paths": ["/paths/~1legacy~1*"], "rules": { "response-example": "off" }, "reason": "frozen legacy API" }
    ]
  }
}

La CLI le lit dans la configuration passée par --config, ou dans --audit-config <fichier> — un fichier JSON qui ne contient que le bloc, pour un passage sans configuration de page, ou qui doit noter autrement que la page publiée. Le guide de l’audit détaille l’ordre de priorité.

Deux écarts avec la page. Une entrée que la CLI ne sait pas lire arrête le passage avec le code 2, chaque entrée nommée : un pipeline ne passe jamais sur une configuration que personne n’a écrite. Et features.audit: false ne s’applique pas — ce réglage retire la page de la documentation publiée, alors qu’un auteur qui lance la commande a demandé le rapport.

Une note calculée avec l’un de ces réglages porte la mention Règles personnalisées partout où elle apparaît : elle ne passe jamais pour la note par défaut.

Le réseau

Un schéma, un $ref externe ou un overlay derrière une URL sont téléchargés. Deux options rendent ça prévisible dans un job de CI :

  • --fetch-timeout <secondes> (30 par défaut) : un hôte qui accepte la connexion et ne répond jamais fait échouer le job dans sa première minute, pas au délai maximal de la plateforme.
  • --offline refuse tout téléchargement et arrête le passage avec le code 2 — même celui qui, sinon, serait ignoré avec un avertissement, comme un overlay. Un job censé rester hors réseau ne passe jamais sur un autre document.

Les règles, depuis le terminal

npx apiglow@0.3.0 audit --explain oauth-legacy-flows --language fr  # une règle : catégorie, sévérité, pourquoi, comment corriger
npx apiglow@0.3.0 audit --list-rules                                # toutes les règles, en JSON

Les deux respectent --language et refusent tout le reste sur la ligne : un passage qui aurait l’air d’un audit et imprimerait une règle tromperait quiconque lit son code de sortie.

Les recettes

GitHub Actions

L’action apiglow/audit-action lance la commande en une étape : des annotations sur le diff de la pull request, le rapport Markdown dans le résumé du job, et du SARIF vers le code scanning quand vous le demandez. Son tag est la version de la CLI qu’elle lance.

on: pull_request

jobs:
  schema-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: apiglow/audit-action@v0.3.0
        with:
          schema: openapi.yaml
          baseline: audit-baseline.json
EntréePar défaut
schema—Chemin, URL ou motif entre guillemets ; un par ligne pour plusieurs
config—La configuration JSON de la page, à la place de schema
baseline—Un fichier de baseline : seuls les constats qu’il ne liste pas font échouer le job
fail-onerrorerror, warning, info ou none
min-severityinfoNe lister que les constats de cette sévérité ou pire
annotationstrueAnnoter le diff
summarytrueÉcrire le rapport Markdown dans le résumé du job
sariffalseEnvoyer le SARIF au code scanning — le job a besoin de security-events: write
args—Toute autre option : --min-grade B --language fr
versioncelle de l’actionLa version d’apiglow à lancer
node-version24La version de Node.js sur laquelle la lancer

Ses sorties sont passed, le chemin du report JSON, et celui du fichier sarif quand il y en a un.

Le même job à la main, chaque pièce en vue :

permissions:
  contents: read
  security-events: write
steps:
  - uses: actions/checkout@v7
  - uses: actions/setup-node@v7
    with:
      node-version: 24
  - name: Schema audit
    run: |
      npx --yes apiglow@0.3.0 audit openapi.yaml --baseline audit-baseline.json \
        --format github --report markdown="$GITHUB_STEP_SUMMARY" --report sarif=audit.sarif
  - if: always()
    uses: github/codeql-action/upload-sarif@v4
    with:
      sarif_file: audit.sarif

GitHub montre dix annotations de chaque type par étape et jette le reste sans prévenir : les nouveaux constats passent donc en premier, et une dernière notice dit combien ont été laissés de côté. Le code scanning lit au plus 25 000 résultats par passage ; au-delà, les nouveaux constats passent d’abord, puis les plus sévères.

GitLab CI

Les constats dans le widget de merge request, le rapport JSON gardé comme artefact :

schema-audit:
  image: node:24
  script:
    - npx --yes apiglow@0.3.0 audit 'apis/**/openapi.yaml'
        --baseline audit-baseline.json
        --report codequality=gl-code-quality-report.json --report json=audit.json
  artifacts:
    when: always
    reports:
      codequality: gl-code-quality-report.json
    paths: [audit.json]

C’est l’empreinte qui permet au widget de distinguer les nouveaux constats des constats résolus.

pre-commit

Un hook local. pass_filenames: false, parce que la commande nomme son schéma elle-même — si on lui passait les fichiers indexés, elle n’auditerait qu’eux, sous des identifiants que la baseline ne connaît pas. Toute modification de YAML le déclenche, puisqu’un $ref peut vivre dans un autre fichier.

repos:
  - repo: local
    hooks:
      - id: apiglow-audit
        name: Schema audit
        entry: npx --yes apiglow@0.3.0 audit openapi.yaml --baseline audit-baseline.json
        language: system
        files: \.ya?ml$
        pass_filenames: false

Les ruptures de contrat

Hors périmètre, à dessein. L’audit note une version du document, et sa baseline répond à « qu’est-ce qui est nouveau », pas à « le contrat a-t-il cassé ». oasdiff compare deux versions ; lancez-le à côté de l’audit :

- uses: actions/checkout@v7
  with:
    fetch-depth: 0
- name: Breaking changes
  run: |
    git show "origin/$GITHUB_BASE_REF:openapi.yaml" > base.yaml
    docker run --rm -v "$PWD:/specs:ro" tufin/oasdiff:v1.32.1 \
      breaking /specs/base.yaml /specs/openapi.yaml --fail-on ERR

Un agent qui corrige le schéma

Donnez-lui le rapport JSON. Chaque constat est placé en fichier:ligne:colonne, le message, la justification et le correctif de sa règle sont dans rules, et l’empreinte survit aux modifications autour. La boucle : auditer, corriger, ré-auditer jusqu’à ce que chaque contrôle passe ; avec une baseline, --only-new garde l’agent sur ce qu’une modification a introduit.

npx apiglow@0.3.0 audit openapi.yaml --format json --min-severity warning

Le paquet npm livre cette boucle sous forme de skill d’agent : skills/apiglow-audit/SKILL.md. Copiez le dossier depuis node_modules/apiglow/skills/ là où votre agent lit ses skills — .claude/skills/ pour Claude Code, par exemple. Au-delà des commandes, la skill dit ce qu’un agent ne doit pas faire de lui-même : inventer ce que fait l’API pour remplir une description ou un exemple, ou faire passer le contrôle en éteignant une règle, en écrivant une baseline ou en baissant --fail-on. Chacune de ces décisions revient au propriétaire.

Toutes les options

OptionCe qu’elle fait
<spec>…Chemin, URL ou motif entre guillemets du schéma ; plusieurs sont audités chacun sous son chemin
--config <fichier>Auditer ce que la documentation montre : overlays, opérations cachées, configuration des règles, chaque spec
--audit-config <fichier>La configuration des règles seule ; remplace le bloc audit de la configuration
--fail-on-unmatched-globsS’arrêter quand un motif ne trouve aucun fichier
--fail-on <sévérité>Échouer sur un constat de cette sévérité ou pire : error (par défaut), warning, info, none
--min-grade <lettre>Échouer sous cette note
--min-score <0-100>Échouer sous ce score
--baseline <fichier>Ne compter que les constats que la baseline ne liste pas ; échouer sur les entrées périmées
--write-baseline <fichier>Enregistrer tous les constats actuels, et passer
--prune-baseline <fichier>Avec --baseline : la réécrire sans ses entrées périmées
--allow-stale-baselineAvec --baseline : lister les entrées périmées sans échouer
--format <format>text (par défaut), markdown, json, sarif, github, codequality
--output <fichier>Écrire le rapport dans un fichier plutôt que sur stdout
--report <format>=<fichier>Écrire un rapport de plus depuis le même passage ; répétable
--min-severity <sévérité>Ne lister que les constats de cette sévérité ou pire — le verdict ne change pas
--only-newAvec --baseline : ne lister que les constats qu’elle ne connaît pas
--language <code>en (par défaut) ou fr, pour les textes du rapport
--offlineNe jamais télécharger ; une URL où que ce soit arrête le passage
--fetch-timeout <secondes>Attente maximale pour chaque document téléchargé (30 par défaut)
--explain <règle>Imprimer une règle, ne rien auditer
--list-rulesImprimer toutes les règles en JSON, ne rien auditer