33800 Docs

← Retour

Audit PASS 3 — ulias-org : INTEGRATION & CONTRATS INTER-SERVICES

Date : 15/02/2026 Status : IMPLEMENTEE Auditeur : Claude Opus 4.6 Projet : /stock_8to/33800-stack/projects/ulias-org/ Scope : Integration connectors-api, ai-orchestrator, WebSocket lifecycle, Agent lifecycle, Session management, Configuration


Resume executif

Severite Nombre
BUG CERTAIN 4
BUG PROBABLE 8
RISQUE THEORIQUE 9
TOTAL 21

Les principaux problemes identifies sont :

  1. Incoherence instance id vs name dans les appels connectorFetch (Supabase utilise .id, GitLab/AI utilise .name, ntfy alterne) -- contrat fragile
  2. Fuite memoire pendingJobs : les timers et promesses ne sont jamais nettoyes si l'appelant est GC sans resolution
  3. CALLBACK_BASE_URL manquante dans conf.prod.gouroubleu.yml -- le fallback IP:port est utilise mais jamais defini en env
  4. WebSocket lifecycle sans dissociation session -- quand un WS se ferme, la session conserve une reference ws morte
  5. Briefing skip logic inversee -- les questions non-required sautent les reponses valides

Findings

F-01 : Incoherence instance parameter (id vs name) dans connectorFetch


F-02 : CALLBACK_BASE_URL jamais definie dans env/config


F-03 : Reference WS morte apres deconnexion

// close: ne nettoie PAS session.ws close(ws) { console.log([WS] CLI disconnected); }

// Appel asynchrone sur ws potentiellement ferme ws.send(JSON.stringify({ type: 'conversation_title_updated', conversationId: convId, title, tags, }));

- **Impact** : Crash ou exception non gere quand un titre/resume est genere APRES la deconnexion du WS. Le try/catch dans `generateConversationTitle` masque l'erreur, mais le message est perdu.
- **Fix suggere** : Dans les handlers `close(ws)`, retrouver la session associee et mettre `session.ws = undefined`. Avant chaque `ws.send()`, verifier que le WS est encore ouvert (readyState).

---

### F-04 : Fuite memoire pendingJobs -- jamais nettoye si caller abandonne
- **Severite** : BUG PROBABLE
- **Fichier** : `packages/server/src/tools/client.ts` : lignes 88, 440
- **Description** : `pendingJobs` est un `Map<string, PendingJob>` global. Chaque appel `llmChat`/`llmEmbed`/`whisperTranscribe`/`analyzeImage` ajoute une entree avec un timer de timeout. Si le timeout expire, l'entree est supprimee. MAIS : si le process appelant crash/est annule (AbortSignal) AVANT le timeout, la promesse reste suspendue et le PendingJob avec son timer reste en memoire. Il n'y a aucun cleanup periodique ni integration avec AbortSignal.
- **Code concerne** :
```typescript
pendingJobs.set(callbackId, { resolve, reject, timer, createdAt: Date.now() });
// Pas de AbortSignal listener
// Pas de cleanup periodique des pendingJobs stales

F-05 : Briefing processAnswer skip logic inversee


F-06 : CONNECTORS_API_URL fallback incorrect (port 5400 vs 5403)


F-07 : editFile ne remplace que la PREMIERE occurrence


F-08 : Circuit breaker et response cache partages entre sessions (cross-user)


F-09 : Pas de validation du callbackId dans le endpoint /internal/job-callback


F-10 : Pas de timeout sur le WS handleWsMessage (async handler)


F-11 : Agent boucle ReAct -- nudge injecte un message 'user' qui fausse l'historique


F-12 : Supabase dbQuery -- filtre ilike.*query* non escape


F-13 : Auth login -- credential API key stockee en clair dans ceo_profile


F-14 : dbInsert/dbUpdate -- pas de gestion du retour vide


F-15 : Session cleanup ne ferme pas les WS ni les abort controllers


F-16 : Supabase instance identifiee par .id dans connectorFetch mais auth.ts utilise 'Supabase' (name)

// client.ts : utilise l'ID dynamique return this.connectorFetch('supabase', sbInstance.id, rest/v1/${tableName}, ...);

- **Impact** : Si le nom de l'instance Supabase n'est pas exactement `'Supabase'`, les operations de persistance d'API key echoueront silencieusement (try/catch avale l'erreur).
- **Fix suggere** : Dans `auth.ts`, passer l'instance de maniere dynamique via un parametre plutot que de hardcoder `'Supabase'`.

---

### F-17 : Prompt overrides globaux (pas par session/user)
- **Severite** : RISQUE THEORIQUE
- **Fichier** : `packages/server/src/agents/registry.ts:641`, `packages/server/src/index.ts:160-171`
- **Description** : Les prompt overrides sont stockes dans un `Map<string, string>` global au niveau module. Quand un user charge ses overrides lors de `getOrCreateSession`, `setPromptOverride` ecrase les overrides existants pour TOUS les utilisateurs. Si deux utilisateurs ont des overrides differents pour le meme agent, le dernier a se connecter gagne.
- **Code concerne** :
```typescript
// global au module
const promptOverrides = new Map<string, string>();

// Lors du login de chaque user :
for (const ov of overrides) {
  setPromptOverride(ov.agent_name, ov.prompt);
}

F-18 : translateOutput appele pour chaque agent -- latence inutile et tokens gaspilles


F-19 : Pas de rate limiting sur les endpoints REST


F-20 : writeFile escape incomplet -- contenu avec 'ULIAS_EOF' casse le heredoc


F-21 : ResponseCache hash collision -- hash 32-bit DJB-like tronque a 200 chars du system prompt


Synthese par axe d'audit

1. Integration connectors-api

2. Integration ai-orchestrator

3. WebSocket lifecycle

4. Agent lifecycle

5. Session management

6. Configuration et env vars

7. Securite data