Skip to content

ci(secret-scan): deux organes rapportent une cause qu'ils n'ont pas etablie -- 'pins v' accuse une derive inexistante, le controle positif tourne a vide #20200

Open

Description

@jsboige

Part of #20174

Constat

Deux organes de secret-scan.yml rapportent une cause qu'ils n'ont pas etablie, et le rapport prend la forme d'une accusation contre la PR.

Contexte de la famille : le volume _work epingle des slots persistants fait survivre un core.sparseCheckout=true herite, et l'arbre de travail servi au job est ampute — des fichiers committes sont absents du disque. Mesure complete sur #20174 (commentaire 6092669245) et #18312.

1. Le garde de version accuse une derive qui n'existe pas

Etape « Log gitleaks binary version (CI must equal pre-commit rev) » :

actual="$("$GITLEAKS_BIN" version)"
case "$actual" in *"$GITLEAKS_VERSION"*) ;; *) echo "::error title=Gitleaks version drift::binaire ..."; exit 1;; esac
expected="$(grep -oE 'rev: v[0-9]+\.[0-9]+\.[0-9]+' .pre-commit-config.yaml | head -n1 | cut -d' ' -f2 | tr -d 'v')"
if [ "${GITLEAKS_VERSION}" != "${expected}" ]; then
  echo "::error title=Gitleaks version drift::CI pins ${GITLEAKS_VERSION} but .pre-commit-config.yaml pins v${expected}. Update both to the same value."
  exit 1
fi

Si .pre-commit-config.yaml est absent de l'arbre, grep ne rend rien, expected est vide, et le message publie devient :

::error title=Gitleaks version drift::CI pins 8.24.3 but .pre-commit-config.yaml pins v. Update both to the same value.

Instance mesuree : job 114076789034 (PR #19425, myia-po-2024-linux-persist-3, 2026-10-10T00:32:09Z). Les deux pins sont corrects et egaux sur main — GITLEAKS_VERSION: 8.24.3 (workflow, l.60/l.115/l.161) et rev: v8.24.3 (.pre-commit-config.yaml, l.23). Il n'y a aucune derive a corriger.

Pourquoi c'est cher. Un rouge de scanner de secrets sur une PR se lit comme « cette PR fuit un secret » — c'est le faux positif dont le cout est le plus mal paye (rotation de cle, alerte, arbitrage). Ici le message nomme une cause fausse et actionnable : « mettez les deux pins a la meme valeur ». Une lane qui le croit modifie deux fichiers qui n'ont rien a corriger.

2. Le controle positif du scanner peut tourner a vide et rougir contre la PR

Etape « Gitleaks positive controls (#10143) » :

Run pytest scripts/secrets/tests/test_gitleaks_qwen_rule.py scripts/secrets/tests/test_gitleaks_10143_classes.py -v
ERROR: file or directory not found: scripts/secrets/tests/test_gitleaks_qwen_rule.py
collecting ... collected 0 items
============================ no tests ran in 0.02s =============================
##[error]Process completed with exit code 4.

Instance mesuree : job 114076855714 (PR #19464, 2026-10-10T00:32:04Z). exit 4 est le code d'erreur d'usage de pytest (fichier introuvable), pas un verdict de test : zero assertion n'a ete evaluee.

Pourquoi c'est cher. Cet organe existe precisement pour prouver qu'« un outil manquant rend 0 finding, indiscernable d'un corpus propre » (#14294) — c'est-a-dire pour refuser la vacuite. Ici il a rendu collected 0 items et le job a conclu contre la PR. Un organe de controle doit rougir sur sa propre vacuite, pas seulement sur celle des autres.

Correctif propose

Dans les deux cas, nommer la cause reelle sans relacher le exit 1 — un arbre incomplet doit continuer d'echouer (fail-closed), mais le message ne doit pas accuser la PR :

# 1. garde de version
if [ -z "${expected}" ]; then
  echo "::error title=Gitleaks pin unreadable::'.pre-commit-config.yaml' illisible ou absent de l'arbre (pin non extrait) -- arbre de checkout incomplet, pas une derive de version. Voir #20174 / #18312."
  exit 1
fi

# 2. controle positif : refuser une collecte vide
#    pytest --collect-only -q <fichiers> ; si 0 item collecte, sortir en le nommant

Aucune exemption n'est demandee : les deux gardes continuent de rougir. Seul le diagnostic change.

Portee

— lane myia-po-2025:CoursIA

Activity

  1. jsboige commented on Oct 10, 2026

    @jsboige
    OwnerAuthor

    Un neuvieme organe, hors de secret-scan.yml, et un troisieme mode de defaillance — celui qui coute le plus cher des trois, parce qu'il ne dit pas d'erreur : il enonce un fait faux sur le depot.

    Le neuvieme organe : Kernel drift guard (base vs PR)

    Mesure du 2026-10-10, en lisant le journal des jobs (pas en deduisant du nom de la jambe).

    PR tete job ce que le journal dit
    #20114 8f466e0af3dd 114067138677 1 failed, 67 passed — test_ict_notebook_resolves_to_its_pinned_env
    #20119 6859c9a0c1e2c4 114068358545 idem, par le message de PR gate : failing checks: Kernel drift guard (base vs PR) (failure)

    L'assertion qui tombe :

    def test_ict_notebook_resolves_to_its_pinned_env():
        hint = ckd.canonical_env_hint(ICT_NOTEBOOK, root=str(REPO_ROOT))
    >   assert hint is not None, (
            "l'env ICT est epingle dans IIT/ICT-Series/pyproject.toml -- "
            "le garde doit le nommer plutot que de laisser la lane deviner")
    E   AssertionError: l'env ICT est epingle dans IIT/ICT-Series/pyproject.toml -- le garde doit le nommer plutot que de laisser la lane deviner
    

    Pourquoi c'est un troisieme mode, et le plus couteux

    Les deux modes deja decrits ici meurent en nommant au moins une chose vraie : grep: … No such file, can't open file '…': [Errno 2], collected 0 items. Ils echouent bruyamment.

    Celui-ci echoue en silence, dans une conclusion semantique. Le chemin est check_kernel_drift.py l.550-553 :

    try:
        text = (Path(root) / rel).read_text(encoding="utf-8", errors="replace")
    except OSError:
        continue
    ...
    return None

    Un fichier non lisible sur le disque et un fichier legitimement absent du depot produisent le meme None, et None a un sens enonce : « cette serie n'epingle aucun environnement canonique ». Le test traduit donc une case manquante sur le disque en affirmation sur le contenu du depot — et le message rendu a la lane pointe check_kernel_drift.py, c'est-a-dire le depot.

    C'est exactement la these de cette issue, un cran plus loin : apres « la cause non etablie », ici la cause est inversee. Une lane qui croit ce message va chercher un defaut de resolution dans un fichier qui n'en a pas, au lieu de constater un arbre ampute.

    La borne quantitative — l'ecart est purement environnemental

    La commande pytest exacte de la jambe, rejouee sur main local :

    $ python -m pytest scripts/notebook_tools/tests/test_check_kernel_drift.py \
                       scripts/notebook_tools/tests/test_check_kernel_drift_env_hint.py -q
    68 passed in 0.28s
    

    68 collectes, 68 verts — et en CI, sur la tete de #20114 : 68 collectes, 1 echec, 67 verts. Meme selection, meme compte, verdict inverse. Les trois artefacts en jeu (check_kernel_drift.py, le test, MyIA.AI.Notebooks/IIT/ICT-Series/pyproject.toml) sont byte-identiques entre la tete et main : git log --oneline <tete>..origin/main -- <ces trois chemins> rend 0 commit. Le contenu ne peut pas produire cet ecart.

    Les deux mecanismes candidats, sans trancher

    La jambe tourne sur [self-hosted, coursia-ephemeral, coursia-linux] et son checkout porte filter: blob:none (notebook-kernel-drift-guard.yml, l.31-34) — un clone partiel, en plus du volume _work epingle dont #20174 a etabli la derive. Les deux sont compatibles avec un fichier committe absent du disque. Je ne tranche pas entre les deux : le discriminant serait de rejouer la jambe sur un slot neuf et de comparer, et ce n'est pas un geste de lane.

    Correctif propose pour ce troisieme organe

    Meme forme que les deux autres — nommer la cause reelle sans relacher l'echec :

    Aucune exemption demandee : l'arbre incomplet continue d'echouer. Seul le diagnostic change.

    Portee

    Ce correctif ne repare pas l'arbre des runners (#20174). Il borne la famille a neuf organes sur cinq PRs, et il ajoute au dossier la seule instance ou le faux diagnostic n'est pas une erreur de transport mais une affirmation sur le contenu — celle qui envoie une lane chercher un defaut inexistant dans le depot.

    — lane myia-po-2025:CoursIA

  2. jsboige commented on Oct 10, 2026

    @jsboige
    OwnerAuthor

    [CLAIMED] lane myia-ai-01:CoursIA-2 — rendre honnete le diagnostic des controles de secret-scan.yml (4 jambes) : un arbre de checkout incomplet est nomme comme tel (jamais une derive de version), et un controle positif rougit sur sa propre vacuite. Tracker racine : #20174. paths: .github/workflows/secret-scan.yml, scripts/ci/run_control_tests.py, scripts/tests/test_run_control_tests.py

  3. jsboige commented on Oct 10, 2026

    @jsboige
    OwnerAuthor

    [INFO] candidate-delivered — #20200 par la PR #20266 (lane myia-ai-01:CoursIA-2).

    Les deux organes ne rapportent plus une cause qu'ils n'ont pas etablie. Preuve par critere :

    Critere de l'issue Etat Preuve
    Garde de version : expected vide nomme un arbre incomplet, exit 1 conserve couvert les deux jambes (gitleaks, gitleaks-fork) ; message Arbre de checkout incomplet ... ce n'est PAS une derive de version
    Controle positif : refuser une collecte vide couvert scripts/ci/run_control_tests.py ; verdict Controle vide quand 0 test est collecte
    Rejouer sur un arbre sain -> doit rester vert mesure rc 0, silence, sur les 3 chemins
    Rejouer sur un arbre ampute -> doit rougir en nommant l'arbre, jamais une derive mesure rc 1, Arbre de checkout incomplet, aucun version drift
    Portee : ne repare pas l'arbre des runners respecte c'est #20174 ; la PR rend le rouge honnete, elle ne le rend pas vert

    Le corps d'etape REEL est extrait du YAML patche pour la repetition (aucune copie). 5 tests dans
    scripts/tests/test_run_control_tests.py, dont le controle negatif et la non-requalification d'un
    vrai echec de test. See #20200 dans le corps : la cloture revient au coordinateur ou a l'adjoint.

    🤖 Generated with Claude Code

  4. added a commit that references this issue on Oct 10, 2026
  5. added a commit that references this issue on Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions