Le modèle propose, le moteur décide

Hi o/

Hi o/,

Aujourd’hui, je veux vous raconter où j’en suis avec sek. J’ai déjà écrit sur le projet dans un autre article si vous l’avez raté. Celui-là raconte comment l’idée est née et comment j’ai fini par construire l’outil. Cet article-ci parle de ce qui s’est passé quand j’ai commencé à lui ajouter un agent.

Un regard technique sur le scan agentique « deterministic-first » de SEK

Il y a un moment dans tout projet d’outil de sécurité où quelqu’un finit par dire : « on devrait ajouter un agent. »

C’est une idée tentante. Donner le dépôt au modèle, le laisser fouiner, lui demander de trouver des vulnérabilités, et attendre le rapport.

J’ai essayé des versions de cette idée. La première version est toujours très séduisante : tu donnes le dépôt au modèle, tu lui donnes quelques outils, tu lui dis de se comporter comme un auditeur senior, et tu attends le rapport.

Puis tu le lances quelques fois.

Un run passe la majeure partie de ses tours à comprendre l’arborescence. Un autre démarre dans un contrôleur qui a l’air intéressant et n’atteint jamais la bibliothèque qu’il appelle. Un troisième trouve un flow crédible, mais la ligne citée n’existe plus dans le fichier. Tu corriges ça, et tu découvres ensuite que le modèle a décidé de s’arrêter après une seule issue parce qu’il estime avoir « couvert » le projet.

Le problème n’est pas que le modèle est incapable de trouver des choses intéressantes. Le problème, c’est qu’un scanner de sécurité doit faire plus que trouver des choses intéressantes. Il doit être assez reproductible pour être débogué, assez borné pour être opéré, et assez explicite pour que quelqu’un puisse défendre le résultat plus tard.

C’est la raison pour laquelle le scan agentique de SEK n’est pas une seule boucle autonome. C’est un workflow fait de capteurs déterministes et de jugements non déterministes.

Ce n’était pas ma conclusion de départ. Je voulais la partie utile d’un agent — la capacité de raisonner sur du code qui ne rentre dans aucun preset — sans abandonner les parties utiles d’un scanner : la capacité d’expliquer ce qui a tourné, ce qui a été sauté, et pourquoi un résultat s’est retrouvé dans le rapport.

Le problème ennuyeux derrière le problème excitant

SEK avait déjà un Code Property Graph. Le CPG permet de poser des questions utiles sur les appels, le flot de contrôle et le flot de données. Un preset de taint peut chercher des chemins du genre :

entrée contrôlée par l'attaquant -> code applicatif -> requête SQL

C’est un très bon capteur. Ce n’est pas, en soi, un rapport d’audit.

Un preset large peut renvoyer plusieurs chemins pour la même cause racine. Certains chemins sont sanitizés. D’autres ne sont atteignables que sous une configuration exotique. D’autres sont techniquement atteignables mais pas exploitables dans le déploiement réel de l’application. Il faut quand même lire, comparer, regrouper, classer et expliquer les résultats.

C’est dans ce tri manuel que part une bonne partie du temps d’audit. Tu lances un scan, tu ouvres le premier flow, tu le suis à travers quelques wrappers, tu conclus que c’est un doublon, tu ouvres le suivant, tu remarques que c’est le même sink avec une route différente, et tu commences à te demander si celui qui avait l’air inoffensif n’était pas en fait le plus important.

La machine a déjà fait le parcours de graphe coûteux. L’humain fait encore le travail de comparaison répétitif.

Cette couche agentique est partie de ce problème précis. L’objectif n’était pas de remplacer l’analyse CPG par un LLM. L’objectif était de placer un LLM après la partie où le CPG est bon, et avant la partie qu’un humain doit normalement répéter des centaines de fois.

Autrement dit, je ne cherchais pas à automatiser l’audit entier. Je cherchais à automatiser le milieu ennuyeux.

L’architecture évidente

L’architecture évidente, c’est un gros agent unique. Il reçoit le dépôt et une boîte à outils. Il peut lire des fichiers, grepper, lancer des requêtes, prendre des notes et émettre des findings. Plus la fenêtre de contexte est grande et plus le budget de tours est long, plus il a l’air autonome.

C’est aussi un truc auquel il est étonnamment difficile de faire confiance.

Il n’y a pas de définition stable de « fini ». Pas de réponse claire à la question : si un finding manque, est-ce que le code est sain ou est-ce que l’agent a épuisé ses tours ? Un changement de prompt peut modifier non seulement l’explication, mais aussi l’ensemble des fichiers que l’agent visite. Et si l’agent peut exécuter des commandes, le dépôt audité devient une partie de la surface d’injection de prompt.

La solution n’est pas de faire semblant que le modèle est déterministe. La solution est de décider où le non-déterminisme est utile, puis de poser une interface dure autour.

Donne le dépôt à l'agent

Déterministe contre non-déterministe

La distinction a l’air abstraite, alors voici comment je l’utilise dans l’implémentation.

Le travail déterministe a une réponse qui appartient au moteur. Pour un même projet, une même requête, des mêmes règles et une même configuration, il doit produire le même résultat — ou au minimum rendre un résultat différent explicable. Ça inclut :

  • exécuter des requêtes CPGQL ;
  • matcher l’arborescence source avec des règles ast-grep ;
  • extraire des extraits de source autour d’un flow ;
  • détecter si un sanitizer apparaît dans le chemin ;
  • valider une requête, un pattern, un chemin ou une citation de code ;
  • plafonner les flows, les candidats, les tours, les tokens et les tailles de batch ;
  • stocker l’état du workflow et reprendre un run crashé ;
  • assembler et scorer un vecteur CVSS à partir de métriques qualitatives ;
  • décider si un candidat peut être promu en finding.

Le travail non déterministe, c’est là où la réponse dépend d’une interprétation. Ça inclut :

  • décider si un flow borné est réalistement exploitable ;
  • expliquer une précondition en langage naturel ;
  • décider si deux chemins différents ont la même cause racine ;
  • proposer une requête pour un pattern spécifique au projet ;
  • classer plusieurs issues survivantes les unes par rapport aux autres ;
  • composer plusieurs faiblesses en une chaîne d’exploitation ;
  • explorer du code qui ne rentre dans aucune forme source-to-sink connue.

Le modèle est utile pour la deuxième liste. C’est un mauvais remplaçant pour la première.

Ça donne une règle assez simple : si une réponse peut être calculée à partir de faits, le moteur doit la calculer. Si une réponse exige une interprétation, le modèle peut aider, mais sa réponse reste une proposition tant que le moteur ne l’a pas validée.

flowchart LR
    S[Capteurs déterministes<br/>CPG, ast-grep, findings sauvegardés] --> E[Pack de preuves<br/>faits construits par le moteur]
    E --> J[Jugement LLM<br/>verdict, dédup, classement]
    J --> V[Validation déterministe<br/>schéma, citations, plafonds, persistance]
    V --> H[Revue humaine<br/>ou auto-promotion explicite]

Ce n’est pas qu’une préférence philosophique. Ça donne à chaque étape un travail testable indépendamment. Et ça me donne un endroit où regarder quand un résultat est faux. Si le flow est faux, j’inspecte la requête ou le CPG. Si la citation de source est fausse, j’inspecte la construction des preuves. Si le flow est juste mais le verdict faux, j’inspecte le prompt, la réponse du provider et la sortie validée par schéma.

Sans ces frontières, chaque bug devient « l’agent a fait un truc bizarre. »

Lagent a fait un truc bizarre

Pourquoi ne pas laisser le modèle tout explorer ?

BIG PLAN

Un LLM peut explorer un dépôt. La version plus récente de SEK, pas encore commitée, a une lane agent qui fait exactement ça : le modèle peut lister des répertoires, lire des fichiers, grepper des patterns, exécuter des requêtes CPGQL restreintes, tenir des notes et émettre des candidats.

Ça ne veut pas dire que le reste du scanner devrait devenir une boucle agent ouverte. Je veux toujours cette lane agent — juste pas comme le truc qui définit la couverture.

La découverte exhaustive est un mauvais job pour un modèle de langage. Le modèle peut s’arrêter trop tôt, choisir un autre chemin dans l’arborescence, rater un chemin qui matchait, ou dépenser l’essentiel de son budget à cartographier le dépôt au lieu de l’auditer. Un second run peut prendre un chemin différent et produire un ensemble de candidats différent.

Cette variabilité peut être utile pour trouver des choses hors du jeu de règles connu. Ce n’est pas une bonne fondation pour des affirmations de couverture.

Il y a aussi un côté coût. On peut lancer un scan entièrement non déterministe : laisser l’agent faire la découverte, la lecture et le jugement. Ça marche. C’est aussi la façon la plus chère d’utiliser un modèle. Chaque listing de répertoire, chaque mauvaise piste, chaque fichier relu est facturé en tokens, et la facture grandit avec la taille du dépôt, pas avec le nombre de vrais problèmes qu’il contient.

Et même après avoir payé cette facture, un run unique ne dit rien sur ce qui a été raté. La seule façon de transformer une recherche stochastique en affirmation de couverture, c’est de la relancer en boucle et de compter combien de fois elle redécouvre les mêmes issues. Les nouveaux findings se tassent à peu près logarithmiquement avec le nombre de runs, donc chaque unité de confiance supplémentaire coûte plus de tokens que la précédente.

L’hybride inverse ce compromis. Les lanes déterministes achètent la couverture au prix du CPU, une fois. Le modèle ne dépense des tokens que là où les tokens achètent quelque chose que les règles ne peuvent pas : du jugement sur des preuves qui existent déjà.

Donc le scan démarre avec des lanes déterministes :

  • Les presets CPG trouvent les formes source-to-sink connues via CPGQL.
  • Les packs ast-grep trouvent des patterns structurels, localisés, que l’analyse de taint exprime mal, comme de la crypto faible, des permissions de fichiers permissives ou une config TLS dangereuse.
  • Les findings sauvegardés permettent de re-trier un résultat d’audit existant.
  • Les sondes d’hypothèses laissent un modèle proposer une requête CPGQL ou un pattern ast-grep spécifique au projet, pendant que le moteur la valide et l’exécute.
  • L’agent source explore le dépôt à la recherche de bugs de logique et d’autres findings sans forme de taint pratique.

Le détail important, c’est que ces lanes convergent. Elles n’inventent pas chacune leur propre format de rapport ni leurs propres règles de promotion.

C’est la partie qu’il m’a fallu du temps pour apprécier. « Agentique » ne veut pas forcément dire que chaque étape est agentique. Ça peut vouloir dire que le workflow a un endroit où le modèle a le droit d’explorer, pendant que le reste du pipeline reste ennuyeux et prévisible.

flowchart TD
    P[Bundle de presets CPG] --> M[Merge et plafond de candidats]
    A[Pack de règles ast-grep] --> M
    Q[Propositions d'hypothèses LLM] --> X[Valider et exécuter]
    X --> M
    G[Agent source sandboxé] --> Y[Vérification d'émission côté hôte]
    Y --> M
    M --> EP[Pack de preuves déterministe]
    EP --> T[Triage et post-traitement partagés]

C’est pour ça que je vois l’agent comme un capteur de plus, pas comme l’architecture.

Le workflow lui-même

Le scan seedé est un workflow ordonné. Chaque étape reçoit du JSON typé et émet du JSON typé. Le résultat d’une étape devient l’entrée de la suivante.

Pour un projet frais, le run ressemble à peu près à ça. L’important, c’est que les appels au modèle sont entourés d’étapes qui appartiennent au moteur, et que chaque frontière est persistée en JSON typé.

sequenceDiagram
    participant U as Utilisateur / UI
    participant E as Moteur de workflow
    participant C as Capteurs CPG et source
    participant M as Provider LLM
    participant D as État du run SQLite

    U->>E: Lancer un scan ou un dry run
    E->>D: Créer le run en file et les étapes du workflow
    E->>C: Exécuter presets CPG, ast-grep, sondes et lane agent
    C-->>E: Flux de candidats
    E->>E: Dédup structurelle, plafonds et merge
    E->>C: Construire le pack de preuves borné
    C-->>E: Preuves avec extraits de source et faits de flow
    E->>D: Persister les sorties des étapes déterministes

    par Un appel borné par candidat
        E->>M: Normaliser CWE et métriques CVSS
        M-->>E: Métriques qualitatives validées par schéma
        E->>M: Juger l'exploitabilité et le verdict
        M-->>E: confirmed, likely, uncertain ou false_positive
    end

    E->>D: Persister prompts, réponses, sorties et consommation de tokens
    E->>M: Clusteriser les candidats par cause racine
    M-->>E: Proposition de déduplication ancrée
    E->>M: Ré-attaquer les candidats confirmed et likely
    M-->>E: Verdicts du sceptique
    E->>M: Classer les survivants et synthétiser des chaînes d'exploit
    M-->>E: Rangs comparatifs et propositions de chaînes
    E->>D: Persister candidats et provenance
    E-->>U: Timeline, verdicts, rangs et file de revue
    U->>E: Promouvoir un candidat ou le rejeter
    E->>D: Créer un finding uniquement après politique explicite ou revue

Laisse-moi texpliquer

Le workflow de re-triage, c’est la même idée, mais il part des findings sauvegardés au lieu de lancer les lanes de candidats frais.

1. Générer les candidats

La lane preset résout le langage du projet, sélectionne les presets intégrés correspondants, exécute leurs requêtes CPGQL et transforme chaque flow survivant en candidat. Les résultats sont cachés par projet et par requête. Un preset large ne peut pas inonder le reste du système : les flows sont plafonnés par preset, les étapes de flow sont plafonnées, et le flux de candidats fusionné a un plafond global.

La lane ast-grep fait quelque chose de différent. Elle scanne l’arborescence source à la recherche de patterns structurels curatés, sans exiger de CPG. Ça attrape des bugs plus faciles à décrire comme une forme locale que comme un chemin de dataflow.

La lane d’hypothèses, plus récente, est volontairement asymétrique. Le modèle peut suggérer une requête, mais il ne l’exécute pas. Le moteur vérifie les tokens dangereux, les limites de taille, les contraintes de forme propres au langage et les limites de candidats avant d’appeler le moteur de requêtes CPG ou ast-grep.

L’agent source est plus ouvert, mais sa sortie entre dans le même flux. Il peut lire des fichiers et enquêter, mais chaque flow émis doit pointer vers des fichiers et des lignes réels. L’hôte relit chaque ligne citée et rejette un candidat quand le code suivi par le modèle ne correspond pas à la source.

Cette dernière vérif a l’air presque agaçante d’évidence. C’est aussi exactement le genre de vérif qui empêche une hallucination crédible en apparence de devenir une preuve.

Le même pattern apparaît dans la lane d’hypothèses. Le modèle peut dire « je pense que ce projet a un flow de parser custom qui mérite d’être vérifié ». Il peut proposer la requête. Il ne peut pas discrètement transformer cette idée en requête arbitraire contre le service. Le moteur la valide, enregistre la proposition, l’exécute sous les mêmes limites qu’une requête normale, et enregistre si elle a produit quelque chose.

2. Construire le pack de preuves

Le modèle ne devrait pas recevoir une vague instruction du type « audite ce dépôt ». C’est comme ça qu’une vraie question de sécurité se transforme en prompt de rédaction généraliste.

Pour un verdict normal, il reçoit un pack de preuves borné, construit par le moteur :

  • la classe de vulnérabilité ;
  • le flow source-to-sink, quand il y en a un ;
  • des extraits de source autour des lignes concernées ;
  • le résultat du sanitizer-dans-le-chemin ;
  • les métadonnées du candidat et les vérifs déterministes précédentes.

Les extraits sont encapsulés comme du code non fiable. Le code du dépôt est de la donnée, pas des instructions. C’est important parce que le dépôt lui-même peut contenir des commentaires, des chaînes ou des fichiers qui tentent d’influencer le modèle.

L’appel de verdict sans outils est volontairement étroit. Une injection de prompt peut toujours provoquer un mauvais jugement, mais elle ne peut pas faire exécuter une commande à cette étape précise, ni interroger l’hôte.

3. Demander un jugement borné

L’étape de verdict se déploie en éventail sur les candidats avec une concurrence bornée. Chaque appel doit renvoyer un résultat validé par schéma, du genre :

{
  "verdict": "likely",
  "severity": "high",
  "reasoning": "The request parameter reaches the SQL construction without a sanitizer."
}

Si la réponse ne valide pas, le provider reçoit une seule tentative de correction avec l’erreur de validation et la réponse invalide. Ce n’est pas une boucle de réparation infinie. Il n’y a aucune raison qu’un JSON malformé transforme le scanner en conversation sans fin.

Ça a l’air moins impressionnant qu’une boucle ReAct. C’est en partie le but. Un appel single-shot avec un petit schéma est plus facile à retenter, plus facile à comptabiliser, et plus facile à rejouer six semaines plus tard quand quelqu’un demande pourquoi un candidat a été marqué uncertain.

L’étape de normalisation suit le même principe. Le modèle juge un CWE et les métriques CVSS qualitatives. Le moteur assemble le vecteur CVSS et calcule le score numérique. Le modèle n’a pas le droit d’inventer de l’arithmétique.

Cette séparation revient en permanence dans ce système : laisser le modèle fournir la partie qui demande une interprétation, puis laisser du code ordinaire gérer la partie qui a une réponse bien définie.

4. Dédupliquer, challenger, classer

La déduplication se fait en deux étapes.

D’abord, des clés structurelles déterministes écrasent les doublons évidents. Le flow le plus court est gardé comme représentant, pendant qu’un petit nombre de chemins alternatifs sont conservés pour le contexte. C’est pas cher et ça réduit le nombre d’appels au modèle, eux coûteux.

Ensuite, le modèle fait un clustering ancré sur les candidats vraiment distincts. On lui demande si des candidats partagent une cause racine, pas si leurs textes se ressemblent. Si le modèle se trompe, le fallback garde les candidats canoniques plutôt que de les supprimer silencieusement.

La passe adversariale donne ensuite les candidats confirmed et likely à un second jugement, sceptique celui-là. Une réfutation bloque la promotion automatique. C’est important parce que les verdicts LLM ne sont pas assez stables pour être traités comme la vérité terrain. Le même candidat peut être confirmé dans un run et rejeté dans un autre.

Enfin, le classement est comparatif. Au lieu de demander au modèle d’assigner un score absolu à chaque issue, le ranker insère les candidats par rapport à des ancres et normalise le résultat en un ordre propre 1..N. Les questions comparatives sont généralement plus faciles à répondre de façon cohérente que de faire semblant qu’un chiffre de sévérité sorti par un modèle a une précision scientifique.

Pourquoi fusionner les deux mondes ?

Les approches déterministe et non déterministe compensent leurs échecs respectifs.

Analyse déterministeRaisonnement du modèle
ForceReproductibilité et couverture bornéeIntention, sémantique et explication
Bon àFormes connues, validation, exécution, comptabilitéExploitabilité, cause racine, logique inhabituelle
Mauvais àLogique métier et patterns inconnusRecherche exhaustive et sortie stable
CoûtPrévisible et cachableVariable et dépendant du provider
Mode d’échecRate ce qui n’a pas été encodéHallucine, saute, ou change d’avis
Meilleur rôleCapteur et garde-fouJuge et générateur de propositions

N’utiliser que l’analyse déterministe donne un système fiable mais incomplet. Il ratera les autorisations fail-open, les asymétries de validation et la logique spécifique au projet qui ne matche aucun preset.

N’utiliser qu’un agent donne de la flexibilité, mais rend la couverture, le coût et le débogage opaques. Impossible de savoir si le modèle a raté un bug parce qu’il n’était pas là, parce qu’il s’est arrêté, ou parce qu’il a passé la fenêtre de contexte à lire le mauvais répertoire.

Les fusionner, c’est donner au modèle la liberté dont il a réellement besoin, sans faire dépendre tout le scan de liberté partout.

Le moteur fait partie de la frontière de sécurité

Dès qu’un workflow peut appeler des providers et exécuter des actions de revue de source, l’orchestration n’est plus de la plomberie. Elle fait partie du modèle de sécurité.

Chaque étape du workflow est persistée en SQLite. Le run stocke son statut, le provider, le modèle, la consommation de tokens, le prompt, la réponse et la sortie. Ça donne à l’implémentation trois propriétés pratiques :

  • Reprise après crash : un service API redémarré réinitialise les étapes interrompues et reprend depuis la première frontière incomplète.
  • Rejeu : les prompts et réponses sont disponibles quand un verdict doit être inspecté plus tard.
  • Dry run : les étapes déterministes s’exécutent pour de vrai, pendant que les étapes modèle sont sautées et que leur nombre d’appels est estimé avant la moindre dépense chez le provider.

Le worker borne aussi la concurrence, met en pause et reprend après les rate limits, gère l’annulation pendant le fan-out, et fait remonter les erreurs d’authentification ou de contexte au lieu de tout cacher derrière « scan failed ».

Pour l’agent source, la frontière est plus stricte. L’accès aux fichiers est emprisonné dans le dépôt. CPGQL est en lecture seule et utilise la même denylist de tokens dangereux que l’étape de requête déterministe. Les commandes shell, quand le provisioner Docker est activé, tournent dans un conteneur jetable sans réseau, avec le source monté en lecture seule, les capabilities droppées, des limites de ressources et un nettoyage forcé.

L’agent a le droit de se tromper. Il n’a pas le droit de transformer une mauvaise réponse en accès illimité à l’hôte.

Pourquoi les candidats ne deviennent pas immédiatement des findings

C’était une des décisions de design les plus importantes. C’est aussi là où le mot « agentique » aurait pu causer un très mauvais raccourci.

La table de findings existante est un enregistrement d’audit durable. Elle ne devrait pas être modifiée parce qu’un provider a renvoyé un paragraphe confiant.

Les candidats agentiques vivent donc dans des tables séparées. Ils portent le verdict, le raisonnement, le cluster, le rang, les infos de provider et la provenance. Un reviewer peut promouvoir un candidat manuellement. Un seuil d’auto-promotion explicite peut aussi le promouvoir, mais seulement après les vérifs pertinentes, dont la passe adversariale.

stateDiagram-v2
    [*] --> candidate
    candidate --> reviewed: revue humaine
    candidate --> promoted: seuil explicite
    reviewed --> promoted: accepté
    reviewed --> rejected: rejeté
    promoted --> finding: snapshot de provenance

Cette séparation rend l’incertitude visible au lieu de faire semblant qu’elle n’existe pas. Et ça veut dire qu’un changement de prompt ou de provider ne réécrit pas l’historique.

Ce qui marche, et ce qui ne marche pas

L’approche marche parce qu’elle ne demande pas au modèle de tout faire.

Elle marche quand le modèle reçoit un pack de preuves assez petit pour raisonner dessus. Elle marche quand le prétraitement déterministe élimine les doublons évidents. Elle marche quand le schéma de sortie est étroit et que l’hôte valide les parties dangereuses. Elle marche quand le coût est visible et qu’un dry run peut répondre à « ça va coûter combien ? ».

Elle ne rend pas le modèle déterministe. Elle ne garantit pas qu’un finding confirmé est réel. Elle ne résout pas tous les problèmes de couverture. Un modèle peut toujours rater une mitigation dans un wrapper inconnu, surestimer l’exploitabilité, ou prendre une décision différente au run suivant.

C’est pour ça que le workflow garde l’incertitude dans la couche candidats, et que le moteur reste responsable de l’exécution et de la promotion.

Les lanes plus récentes sont utiles précisément parce qu’elles ont le droit d’être différentes. La lane preset optimise pour une couverture reproductible. La lane ast-grep couvre les erreurs structurelles locales. Les sondes d’hypothèses laissent le modèle s’adapter à un projet, mais gardent l’exécution des requêtes déterministe. L’agent source explore la logique qu’aucune règle ne sait exprimer, mais avec des tours bornés et une vérification côté hôte.

Ce ne sont pas des affirmations concurrentes selon lesquelles une technique serait l’avenir du scan. Ce sont des capteurs avec des angles morts différents, qui alimentent un seul workflow de revue.

C’est aussi pour ça que je ne veux pas rapporter un unique chiffre magique de « confiance IA ». Le résultat est une collection de faits et de jugements : quelle lane l’a trouvé, quelles lignes de source le soutiennent, ce que le modèle en a pensé, si le sceptique était d’accord, quel provider a produit la réponse, et si un humain l’a promu. Plus l’automatisation devient intéressante, plus cette provenance ennuyeuse est importante.

not the same

Le modèle propose, le moteur décide

J’ai commencé ce travail parce que je voulais moins de tri manuel, pas parce que je voulais mettre un chatbot devant une codebase.

L’architecture utile s’est révélée moins magique que ça. C’est un pipeline avec des passations claires :

  1. Du code déterministe trouve et prépare les preuves.
  2. Le modèle juge des preuves bornées ou propose une prochaine action bornée.
  3. Le moteur valide le résultat et exécute toute action approuvée.
  4. Chaque frontière est persistée, plafonnée et rejouable.
  5. Un humain, ou une politique explicite, décide de ce qui devient un finding.

C’est le compromis entre un scanner rigide et un agent incontrôlé. La partie déterministe nous donne la couverture, la sécurité et le contrôle opérationnel. La partie non déterministe nous donne du raisonnement là où les règles sont fragiles et le tri manuel coûteux.

Le modèle propose une idée, le moteur la valide, la persiste, et le workflow aboutit à un résultat reviewable. ABSOLUTE CINEMA!

Donc oui, SEK a un scan agentique maintenant. Mais l’agent n’est pas le système. C’est un composant dans un workflow qui reste redevable envers le moteur.

Le modèle propose. Le moteur décide.

see ya

>_ esc