33800 Docs

← Retour

Audit Pass 2 — ulias-org (Architecture, Interactions, Cycle de Vie)

Date : 15-02-2026 03:00 Status : IMPLEMENTEE Auditeur : Claude Opus 4.6 (pass 2, regard neuf) Scope : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/ (16 fichiers .ts) Angle : Architecture, interactions inter-composants, WebSocket, cycle de vie agents, gestion sessions


Table des matieres

  1. Cycle de vie d'un message
  2. Gestion des sessions
  3. Cycle de vie des agents
  4. Findings
  5. Synthese et recommandations

1. Cycle de vie d'un message

Trace complete : Message WS -> Reponse

Client WS
  |
  v
index.ts:handleWsMessage(ws, raw)            [L213]
  |-- Parse JSON (ou utilise l'objet auto-parse de Bun)
  |-- type='auth' -> getOrCreateSession() -> auth_success
  |-- type='set_conversation' -> session.activeConversationId
  |-- type='message' :
  |     |-- Lookup session via sessions.get(msg.apiKey)     [L253]
  |     |-- touchSession()                                   [L259]
  |     |-- Auto-create conversation si aucune active        [L265-274]
  |     |-- Save user message dans DB                        [L279-289]
  |     |-- Check briefing actif -> handleBriefingMessage()  [L310-341]
  |     |-- OU director.handleMessage()                      [L344]
  |     |   |
  |     |   v
  |     director/index.ts:handleMessage()                    [L170]
  |     |-- sessionHistory.push() + trim a 20               [L176-177]
  |     |-- Cree objective en DB                             [L197-208]
  |     |-- interpreter.route(message)                       [L224]
  |     |   |
  |     |   v
  |     |   interpreter.ts:route()                           [L209]
  |     |   |-- fastKeywordMatch() -> si match, skip LLM    [L214-218]
  |     |   |-- OU llmChat(qwen3:8b, routing prompt)         [L222-232]
  |     |   |-- OU keywordFallback()                         [L251]
  |     |   |-- checkInstanceAmbiguity()                     [L257-259]
  |     |   |-- Retourne RoutingDecision
  |     |   |
  |     |-- Si needs_briefing -> start briefing, return pending [L235-256]
  |     |-- Create AbortController                           [L273-274]
  |     |-- Si pipeline -> pipelineRunner.runSequential()    [L278-301]
  |     |-- Si multi_step -> handleMultiStep()               [L302-304]
  |     |-- Sinon -> handleSingleStep()                      [L306-307]
  |     |   |
  |     |   v
  |     |   handleSingleStep()                               [L369]
  |     |   |-- buildAgentConfig() -> registry lookup        [L378]
  |     |   |-- fetchRelevantLessons() -> embeddings         [L405]
  |     |   |-- contextBuilder.build()                       [L406]
  |     |   |-- runner.run()                                 [L411]
  |     |   |   |
  |     |   |   v
  |     |   |   agent-runner/index.ts:run()                  [L70]
  |     |   |   |-- Boucle ReAct (max 10 iterations)
  |     |   |   |-- Check abort signal                       [L109]
  |     |   |   |-- Check timeout (10 min)                   [L124]
  |     |   |   |-- llmChatWithRetry() (3 retries, backoff)  [L396]
  |     |   |   |   |
  |     |   |   |   v
  |     |   |   |   tools/client.ts:llmChat()                [L306]
  |     |   |   |   |-- POST /api/jobs (ai-orchestrator)     [L332]
  |     |   |   |   |-- waitForCallback() (callback-based)   [L347]
  |     |   |   |   |   |-- Timeout -> poll fallback         [L421-437]
  |     |   |   |   |-- Retourne LLMResponse
  |     |   |   |   |
  |     |   |   |-- Si tool_calls -> executeTool() en parallel [L193]
  |     |   |   |   |-- Circuit breaker check                [L200]
  |     |   |   |   |-- compactToolResult() si trop gros     [L427]
  |     |   |   |   |-- Repeated error auto-stop (3x)        [L279]
  |     |   |   |-- Si content -> return success             [L296-306]
  |     |   |   |-- Si vide -> nudge LLM                    [L310-316]
  |     |   |
  |     |-- translateOutput() -> traduction FR               [L314-316]
  |     |-- Cleanup abort controller                         [L311]
  |     |-- Save lesson on failure                           [L355-361]
  |     |-- Return Objective
  |     |
  |-- ws.send(objective_result)                              [L351-360]
  |-- Save assistant message + update conversation           [L362-396]
  |-- Auto-title (1st msg)                                   [L385-386]
  |-- Auto-summary (every 10 msgs)                           [L389-391]

2. Gestion des sessions

Creation

Maintien

Nettoyage

Stockage WS


3. Cycle de vie des agents

Enregistrement

Selection

Execution

Limitation


4. Findings


FINDING-P2-01 : WS deconnecte laisse des sessions orphelines avec references mortes

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts Lignes : L1219-1221 (close handler CLI), L1232-1234 (close handler WebUI) Classification : BUG PROBABLE

Probleme : Quand un client WS se deconnecte (close handler, L1220/L1233), le code ne fait QUE logger. Il ne :

Consequence : Apres deconnexion WS :

  1. session.ws pointe vers un WS ferme. Tout ws.send() appele depuis un fire-and-forget (generateConversationTitle L86, generateConversationSummary L127, events via onEvent L294) va crash silencieusement ou lever une exception non catchee.
  2. Les agents en cours d'execution continuent a tourner inutilement (timeout 10 min) sans que personne ne lise leurs resultats.
  3. La session reste en memoire jusqu'au cleanup 30 min (gaspillage de ressources + jobs LLM inutiles).

Detail critique : Le onEvent callback ferme sur le ws capture au moment du message [L293-307]. Si le WS se ferme pendant l'execution de l'agent, chaque event envoye va tenter ws.send() sur une connexion fermee.


FINDING-P2-02 : Pas de lien WS <-> session dans le close handler

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts Lignes : L1219-1221 Classification : BUG CERTAIN

Probleme : Le close handler WS ne sait pas QUEL apiKey est associe au WS qui se ferme. La Map sessions est indexee par apiKey, mais le close handler n'a pas acces a l'apiKey — seulement a l'objet ws.

De plus, comme documente dans MEMORY.md (bug connu Elysia/Bun WS), l'objet ws dans les handlers open/message/close n'est PAS la meme reference. Donc meme un Map<ws, apiKey> ne fonctionnerait pas.

Consequence : Il est structurellement impossible, dans l'architecture actuelle, de nettoyer la session quand le WS se ferme. C'est un defaut de conception.

Solution : Utiliser une string key (ex: apiKey ou sessionId) stockee dans ws.data lors du message auth, puis chercher/nettoyer par cette cle dans le close handler.


FINDING-P2-03 : Circuit breaker global partage entre toutes les sessions

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/agent-runner/index.ts Ligne : L13 Classification : BUG PROBABLE

Probleme : toolCircuitBreaker et responseCache sont declares comme constantes globales (singletons). Le circuit breaker est keye par nom d'outil (ex: exec, dockerPs).

Si un outil echoue 3x pour une session (ex: instance SSH temporairement indisponible pour l'utilisateur A), le circuit s'ouvre pour TOUS les utilisateurs/sessions. L'utilisateur B, qui a des instances SSH differentes et fonctionnelles, sera bloque.

Gravite actuelle : Faible en pratique (mono-utilisateur), mais c'est un defaut architectural si le systeme est utilise par plusieurs utilisateurs.


FINDING-P2-04 : Agent en cours d'execution non annule lors du cancel d'un objectif via REST

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts Lignes : L486-512 Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/agent-runner/index.ts Lignes : L109-121 Classification : RISQUE THEORIQUE

Probleme : Le cancel REST [index.ts:500] appelle director.cancelObjective(id) qui abort le AbortController. Cependant, l'abort signal est seulement verifie en DEBUT de boucle d'iteration [agent-runner:109]. Si l'agent est en plein milieu d'un llmChat() (qui attend un callback AI job avec timeout 5 min), le cancel ne prendra effet qu'au retour du LLM, pas immediatement.

De plus, les jobs LLM soumis a l'AI orchestrator ne sont PAS annules. Le job continue sur le GPU, consomme des ressources, et le callback arrive sur un callbackId qui n'a plus de pending resolver (callback perdu silencieusement, ce qui est OK, mais le GPU a travaille pour rien).

Amelioration : Verifier le signal aussi dans waitForCallback(), et optionnellement envoyer un DELETE sur le job AI orchestrator.


FINDING-P2-05 : Les pending jobs (callback LLM) ne sont jamais nettoyes en cas de crash

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/tools/client.ts Lignes : L88, L416-441 Classification : RISQUE THEORIQUE

Probleme : pendingJobs (Map globale) stocke les callbacks en attente. Le seul mecanisme de nettoyage est le setTimeout dans waitForCallback() [L418]. Si le process crash ou si une exception non geree empile des pending jobs sans que le timeout se declenche, la Map croit indefiniment.

En pratique, le setTimeout devrait toujours nettoyer. Mais si de nombreux jobs sont soumis en parallele (ex: multi-step avec sous-taches rapides), la Map pourrait avoir des dizaines d'entrees simultanees. Ce n'est pas un leak mais c'est une zone sans monitoring.

Observation : L'endpoint GET /internal/pending-jobs [index.ts:1207-1209] expose les IDs en attente, ce qui est bien pour le debug.


FINDING-P2-06 : Hash du cache de reponses trop fragile — risque de collisions

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/tools/response-cache.ts Lignes : L23-37 Classification : BUG PROBABLE

Probleme : Le hash utilise un djb2 simple sur 32 bits. La cle est construite avec model + systemPrompt[:200] + lastUserMessage. Deux problemes :

  1. Troncature du system prompt a 200 chars : Deux agents differents avec le meme prefixe de prompt (200 premiers chars identiques) et le meme message utilisateur auront le MEME hash. Le cache retournera la reponse de l'agent A pour l'agent B.

  2. Hash 32-bit : Avec 50 entrees max, le birthday problem donne ~0.03% de collision. Faible mais non-nul.

Impact : Un agent pourrait recevoir une reponse cachee d'un autre agent, avec des tool calls inadaptees. Cela dit, le cache n'est utilise que sur l'iteration 1, et le contexte complet (y compris les tool results) n'est pas cache, donc l'impact est limite a la premiere reponse LLM.


FINDING-P2-07 : La traduction automatique peut corrompre des donnees techniques

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/director/index.ts Lignes : L314-316, L666-684 Classification : BUG PROBABLE

Probleme : translateOutput() est appelee sur CHAQUE reponse agent reussie [L314-316]. Elle envoie l'output complet a qwen3:8b pour traduction. Le prompt dit "Garde le formatage Markdown intact".

Risques :

  1. Donnees JSON ou code : Si l'agent retourne du JSON, du YAML, du code, ou des IDs techniques, le LLM peut les alterer ("traduire" des noms de variables, des chemins, des UUIDs).
  2. Hallucination : Le LLM peut ajouter ou modifier du contenu en "traduisant".
  3. Latence inutile : Si le texte est deja en francais, un appel LLM est quand meme effectue (le code dit "Si le texte est deja en francais, renvoie-le tel quel" dans le prompt, mais c'est le LLM qui decide, pas du code deterministe).
  4. Double-traduction : Rien n'empeche l'output d'etre deja traduit par l'agent lui-meme (les prompts sont souvent en francais).

Impact : Un output comme Container "nginx-prod" restarted on 192.168.1.12 pourrait devenir Conteneur "nginx-prod" redemarre sur 192.168.1.12 (OK) ou pire, le LLM pourrait "traduire" l'IP ou le nom du container.


FINDING-P2-08 : Prompt overrides charges globalement, pas par session

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts Lignes : L159-171 Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/agents/registry.ts Lignes : L641-655 Classification : BUG PROBABLE (en multi-utilisateur)

Probleme : promptOverrides est une Map globale dans registry.ts. Quand la session d'un utilisateur est creee [index.ts:159-171], ses prompt overrides sont charges depuis la DB et appliques GLOBALEMENT via setPromptOverride(). Si deux utilisateurs ont des overrides differents pour le meme agent, le dernier a se connecter ecrase les overrides du premier.

Impact actuel : Nul (mono-utilisateur), mais c'est une bombe a retardement architecturale.


FINDING-P2-09 : Pas de validation d'ownership sur les conversations/messages

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts Lignes : L986-998 (delete conversation), L1053-1086 (patch message), L1089-1108 (fork) Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/db/index.ts Classification : RISQUE THEORIQUE (mono-utilisateur) / BUG CERTAIN (multi-utilisateur)

Probleme : Les endpoints REST pour conversations et messages ne verifient pas que la conversation/message appartient au user authentifie. Exemples :

Le meme probleme existe pour les objectifs : DELETE /api/objectives/:id [index.ts:515] et POST /api/objectives/:id/cancel [index.ts:486] ne verifient pas l'ownership.

Attenuation : PostgREST peut avoir du RLS, mais le code ulias-org utilise une API key partagee via connectors-api, donc le RLS ne s'applique probablement pas au niveau user.


FINDING-P2-10 : Injection de commande SSH via les parametres d'outils LLM

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/tools/client.ts Lignes : L176-202 (exec), L204-218 (readFile/writeFile) Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/agent-runner/index.ts Lignes : L481-602 (executeTool) Classification : RISQUE THEORIQUE

Probleme : Le LLM decide quels outils appeler et avec quels arguments. L'outil exec [client.ts:176] execute directement la commande sur un serveur SSH. Les arguments viennent du LLM, qui interprete le message utilisateur.

Scenario d'attaque :

  1. L'utilisateur envoie : "Execute la commande suivante sur prod-portainer : rm -rf /"
  2. Le LLM route vers monitor ou incident (qui ont exec dans leur toolset)
  3. Le LLM genere un tool_call exec("prod-portainer-SSH", "rm -rf /")
  4. executeTool() [agent-runner:487] appelle toolsClient.exec() sans aucune validation/sanitization
  5. La commande est executee

Attenuation existante :

Second vecteur : writeFile [client.ts:212-218] utilise un heredoc avec echappement des single quotes, mais ne sanitise pas le contenu contre l'injection de commandes shell via $() ou backticks dans le contenu.


FINDING-P2-11 : Le onDecision callback n'est jamais branche

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/agent-runner/index.ts Lignes : L70-76 (signature run), L356-391 (requestDecision) Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/director/index.ts Lignes : L411 (appel runner.run sans onDecision) Classification : BUG CERTAIN

Probleme : AgentRunner.run() accepte un onDecision?: DecisionCallback [L74]. Mais Director.handleSingleStep() [L411] et handleMultiStep() [L529] appellent toujours runner.run(agentConfig, task, onEvent, undefined, signal) — le 4eme argument (onDecision) est toujours undefined.

Consequence : Tous les agents avec requiresApproval (deployer, incident, infra, patcher, strategist, gitlab-dev) ont leurs tool calls systematiquement REFUSES [agent-runner:371-375 "No decision handler, auto-denying"]. Les outils dockerRestart, gitPush, exec (pour patcher), gitlabCreateProject, gitlabTriggerPipeline ne peuvent JAMAIS etre executes par ces agents.

Cela signifie que :

Gravite : Ces agents sont fondamentalement casses pour leurs operations critiques.


FINDING-P2-12 : Pas de protection contre les messages concurrents sur la meme session

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts Lignes : L251-397 (handler message) Classification : BUG PROBABLE

Probleme : Si le client envoie 2 messages WS rapidement, handleWsMessage sera appele 2 fois en parallele (async). Les 2 messages vont :

  1. Acceder a la meme session
  2. Potentiellement les 2 auto-creer une conversation (race condition L265-274)
  3. Lancer 2 director.handleMessage() en parallele
  4. Les 2 directors vont acceder et modifier sessionHistory en parallele
  5. director.activeBriefing peut etre lu/ecrit par les 2 handlers simultanement

Cas specifique briefing : Si un message est en cours de traitement (briefing actif) et un second message arrive, le check hasBriefing() [L310] peut etre vu comme true par les 2, mais un seul devrait traiter le briefing.

Solution : Ajouter un semaphore/lock par session, ou une file d'attente FIFO pour serialiser les messages d'une meme session.


FINDING-P2-13 : Le context builder peut etre contourne par des conversations tres longues

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/context-builder/index.ts Lignes : L23-25, L79-119 Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/agent-runner/index.ts Lignes : L81-84 Classification : RISQUE THEORIQUE

Probleme : Le context builder a un budget de ~21000 chars (~6000 tokens). Mais ce budget ne concerne que le system prompt. Le messages array dans le runner [agent-runner:81] croit a chaque iteration : system prompt + user prompt + tool_calls + tool_results + assistant messages.

Avec 10 iterations et des tool results de 6000 chars chacun (apres compaction), l'array total peut atteindre 60000+ chars. Le num_ctx est fixe a 16384 tokens dans llmChat() [client.ts:324]. Si le contexte depasse cette limite, le LLM recevra un contexte tronque cote serveur (Ollama tronque silencieusement).

Le context builder gere bien la partie system prompt, mais ne controle pas la taille totale de la conversation du runner.


FINDING-P2-14 : Mots de passe et tokens en clair dans les URLs et logs

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/auth.ts Lignes : L74-78 (login), L98 (log avec userId) Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/tools/client.ts Lignes : L148 (API key dans header), L327 (log de chaque LLM call) Classification : RISQUE THEORIQUE

Probleme :

  1. L'API key est transmise dans chaque message WS (msg.apiKey) en clair dans le JSON. Si le transport WS est compromis (meme en WSS, les logs serveur le capturent), la cle est exposee.
  2. L'API key est stockee dans ceo_profile.preferences.connector_api_key en clair dans Supabase [auth.ts:150, 185].
  3. Les logs console affichent les messages utilisateur [index.ts:252], les details de routing, etc. Si ces logs sont collectes par Loki, les messages potentiellement sensibles sont archives.

FINDING-P2-15 : editFile ne gere que le premier match (replace simple)

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/tools/client.ts Lignes : L220-233 Classification : RISQUE THEORIQUE

Probleme : editFile() utilise content.replace(oldString, newString) [L231] qui ne remplace que la PREMIERE occurrence. Si le LLM veut remplacer une string qui apparait plusieurs fois, seule la premiere sera modifiee, ce qui peut laisser le fichier dans un etat incoherent.

Ce n'est pas un bug de securite mais un comportement surprenant qui peut mener des agents a croire qu'un edit a ete fait alors qu'il est incomplet.


FINDING-P2-16 : Le pipeline runner ne passe pas l'AbortSignal

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/pipelines/index.ts Lignes : L123-223 (runSequential) Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/director/index.ts Lignes : L280-289 (appel runSequential) Classification : BUG CERTAIN

Probleme : Director.handleMessage() cree un AbortController [L273] et le passe a handleSingleStep() et handleMultiStep(). Mais quand un pipeline est utilise [L280-289], pipelineRunner.runSequential() est appele SANS signal. Et PipelineRunner.runSequential() [pipelines:181] appelle runner.run() SANS signal.

Consequence : Si l'utilisateur annule un objectif qui utilise un pipeline (create_frontend, verification_chain, etc.), le cancel REST [index.ts:500] va abort le controller, mais le pipeline runner ne recevra jamais le signal. L'agent continue a tourner jusqu'a completion naturelle ou timeout.


FINDING-P2-17 : processAnswer() dans le briefing permet de skip les questions requises

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/briefing/index.ts Lignes : L143-161 Classification : BUG PROBABLE

Probleme : Le code a L149 :

if (!currentQ.required || !skipWords.includes(answer.trim().toLowerCase())) {
  session.answers.set(currentQ.id, answer);
}

La logique est inversee. Le || signifie : "enregistre la reponse SI la question n'est PAS required OU si le mot n'est PAS un skip word". Cela veut dire :

MAIS le probleme est que le currentQuestionIndex est incrementee quand meme [L153]. Donc la question required est sautee sans reponse, et le briefing continue avec la question suivante. Une question required sans reponse ne bloque pas le briefing.

Impact : Un utilisateur peut repondre "passer" a toutes les questions, y compris les required, et le briefing generera un summary avec des champs manquants. L'enriched prompt sera incomplet.


FINDING-P2-18 : Le search conversations est vulnerable a l'injection SQL via ilike

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/db/index.ts Lignes : L516-547 Classification : RISQUE THEORIQUE

Probleme : searchConversations() construit un filtre PostgREST avec ilike.*${query}* [L519, L527]. Le parametre query vient directement du query string HTTP [index.ts:935] avec seulement un check de longueur minimale (2 chars).

Si query contient des caracteres speciaux PostgREST (%, _, *), ils seront interpretes comme des wildcards. Ce n'est pas de l'injection SQL a proprement parler (PostgREST parametrise les queries), mais un utilisateur pourrait forcer des patterns tres larges (% seul = retourne tout).


FINDING-P2-19 : Les agents avec toolSet vide ne sont pas correctement geres

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/tools/definitions.ts Ligne : L683 Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/agent-runner/index.ts Ligne : L146 Classification : FAUX POSITIF

Observation : L'agent simplifier a un toolSet vide [] [definitions.ts:683]. Dans le runner [agent-runner:146], config.tools.length > 0 ? config.tools : undefined passe undefined au LLM, ce qui est correct (pas de tool_choice). L'agent simplifier est concu pour ne faire que du text processing.


FINDING-P2-20 : La Map pendingDecisions n'est jamais nettoyee

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts Lignes : L143, L401-408 Classification : RISQUE THEORIQUE

Probleme : pendingDecisions [L143] est une Map globale qui stocke les resolver functions pour les decisions CEO. Les entries sont ajoutees... quelque part (le code qui les ajoute n'est pas visible dans le codebase — le pendingDecisions n'est jamais set()). Les entries sont supprimees quand le CEO repond [L406].

Mais comme le code ne contient aucun pendingDecisions.set() dans les 16 fichiers audites, cette Map est en fait JAMAIS utilisee. C'est du dead code.

Le decision_response message type [L401] est gere mais ne peut jamais recevoir de decision valide car aucun decisionId n'est enregistre.

Cela confirme le FINDING-P2-11 : le systeme de decisions/approbations CEO est completement non-fonctionnel.


FINDING-P2-21 : Race condition sur message_count

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts Lignes : L374-379 Classification : RISQUE THEORIQUE

Probleme : Le code fait GET + calcul + UPDATE pour incrementer message_count :

const conv = await db.getConversation(convId);    // GET
const prevCount = conv?.message_count || 0;
await db.updateConversation(convId, {
  message_count: prevCount + 2,                    // UPDATE
});

C'est un pattern read-modify-write sans transaction. En cas de messages concurrents (FINDING-P2-12), deux handlers pourraient lire le meme count et chacun ecrire +2, resultant en un count de +2 au lieu de +4.

Impact : Le message_count serait incorrect, ce qui affecterait l'auto-summary (trigger tous les 10 messages).


FINDING-P2-22 : CONNECTORS_API_URL fallback hardcode avec IP

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/index.ts Ligne : L16 Fichier : /stock_8to/33800-stack/projects/ulias-org/conf.prod.gouroubleu.yml Ligne : L8 Classification : FAUX POSITIF (en contexte)

Observation : Le fallback http://192.168.1.12:5400 [index.ts:16] est different de la valeur en prod http://192.168.1.12:5403 [conf.yml:8]. Mais comme la variable d'env est toujours definie en prod, le fallback n'est jamais utilise. Cependant, si quelqu'un lance le serveur en dev sans definir la variable, il pointerait vers le mauvais port.


FINDING-P2-23 : CALLBACK_BASE_URL fallback peut etre incorrect

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/tools/client.ts Ligne : L112 Classification : RISQUE THEORIQUE

Probleme : CALLBACK_BASE = process.env.CALLBACK_BASE_URL || 'http://192.168.1.12:5515'. Le callback URL est envoyee a l'AI orchestrator qui doit pouvoir atteindre ulias-org. Si ulias-org tourne sur un reseau different ou derriere un reverse proxy, le fallback hardcode pourrait ne pas etre accessible depuis l'orchestrator.

En pratique, les deux services sont sur le meme reseau Docker (prod-portainer), donc ca fonctionne.


FINDING-P2-24 : La notification service est instantiee mais jamais utilisee

Fichier : /stock_8to/33800-stack/projects/ulias-org/packages/server/src/notifications/index.ts Classification : FAUX POSITIF (design intent)

Observation : NotificationService est definie avec des methodes completes (objectiveCompleted, objectiveFailed, decisionNeeded, proactiveAlert), mais n'est importee ou utilisee nulle part dans index.ts ou director/index.ts. Le director envoie les notifications directement via les events WS et les fire-and-forget DB saves.

Ce fichier semble etre du code preparatoire pour une integration future, pas du dead code problematique.



5. Synthese et recommandations

Bugs certains (a corriger)

# Finding Impact Complexite fix
P2-02 WS close handler ne peut pas identifier la session Resources orphelines, ws.send() sur connexion fermee Moyenne
P2-11 onDecision jamais branche Agents avec requiresApproval completement casses Faible
P2-16 Pipeline runner ne passe pas AbortSignal Cancel d'objectif pipeline impossible Faible

Bugs probables (a verifier et corriger)

# Finding Impact Complexite fix
P2-01 WS deconnecte laisse sessions orphelines Jobs LLM inutiles, ws.send() crash Moyenne
P2-03 Circuit breaker global (cross-session) Blocage inter-utilisateur Faible
P2-06 Hash cache fragile, collision possible Reponse LLM incorrecte cachee Faible
P2-07 Traduction automatique peut corrompre les donnees Output altere pour l'utilisateur Faible
P2-08 Prompt overrides globaux (cross-session) Ecrasement overrides entre utilisateurs Faible
P2-12 Pas de lock sur messages concurrents Race condition briefing/conversation Moyenne
P2-17 Briefing skip questions required Briefing incomplet sans erreur Faible

Risques theoriques

# Finding Impact
P2-04 Cancel n'annule pas les jobs GPU Ressources GPU gaspillees
P2-05 Pending jobs pas nettoyes si crash Memory leak theorique
P2-09 Pas de validation ownership REST Acces cross-user en multi-tenant
P2-10 Injection commande SSH via LLM Execution commandes arbitraires
P2-13 Context overflow dans le runner Troncature silencieuse par Ollama
P2-14 Credentials en clair dans logs/DB Exposition API key
P2-18 Injection pattern dans search Requetes DB trop larges
P2-21 Race condition message_count Counter incorrect

Priorites de correction recommandees

  1. P2-11 (onDecision) : Fix immediat. Sans ce fix, 6 agents sont fondamentalement casses. Il suffit de brancher le mecanisme de decision via WS (envoyer un event decision_request et attendre une reponse decision_response via pendingDecisions).

  2. P2-02 + P2-01 (WS lifecycle) : Utiliser ws.data dans Elysia pour stocker l'apiKey lors du message auth, puis dans le close handler, nettoyer la session, invalider session.ws, et optionnellement cancel les objectifs en cours.

  3. P2-16 (Pipeline abort) : Passer le signal au PipelineRunner.runSequential() et le propager a runner.run().

  4. P2-12 (Concurrence) : Ajouter un flag processing ou une file par session pour serialiser les messages.

  5. P2-07 (Traduction) : Ajouter un check heuristique (detecter si le texte est deja en francais avant d'appeler le LLM, ou exclure les outputs qui contiennent du JSON/code).


Fin de l'audit pass 2. 16 fichiers lus, 24 findings classes.