33800 Docs

← Retour

Audit de securite approfondi - connectors-api

Date : 15/02/2026 Status : IMPLEMENTEE Auditeur : Claude Opus 4.6 Projet : connectors-api (Bun + Elysia + TypeScript + Supabase PostgREST) Methode : Lecture integrale de chaque fichier source (39 fichiers .ts), tracage des chemins d'execution reels Fichiers audites : index.ts (~5700 lignes), 22 services, 3 types, 1 data, 1 middleware, 1 db


SYNTHESE

Severite Nombre
CRITIQUE 2
HAUTE 7
MOYENNE 6
BASSE 4
TOTAL 19

FINDINGS


1. CRITIQUE - JWT signature non verifiee

Fichier : /stock_8to/33800-stack/projects/connectors-api/src/middleware/jwt.ts, lignes 29-52 Certitude : CERTAIN

Code :

export function verifyJWT(token: string): JWTPayload | null {
  try {
    const parts = token.split('.');
    if (parts.length !== 3) return null;
    const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString());
    // Check expiration
    if (payload.exp && payload.exp < Date.now() / 1000) return null;
    // For production, we should verify the signature
    // but for now we trust tokens that decode properly
    // TODO: Implement proper HMAC verification with JWT_SECRET
    return payload;
  } catch {
    return null;
  }
}

Analyse : La fonction verifyJWT() decode le payload base64 du JWT mais ne verifie JAMAIS la signature HMAC. Le commentaire "TODO" confirme que c'est intentionnel. N'importe qui peut forger un JWT avec un sub (user_id) arbitraire. Il suffit de construire un JWT sans signature valide, et la fonction le considere comme authentique.

Cette fonction est appelee par getUserFromRequest() (ligne 67) et getAuthFromRequest() (ligne 80) qui sont utilises dans ~100 endpoints de index.ts.

Impact : Usurpation d'identite totale. Un attaquant peut agir en tant que n'importe quel utilisateur en forgeant un JWT {"sub":"uuid-victime"}.

Facteur attenuant : Le nginx reverse-proxy est configure en private: true (conf.prod.gouroubleu.yml ligne 33), ce qui restreint l'acces reseau aux IPs autorisees. L'exploitation necessite un acces au reseau local ou une IP whitelistee. Cependant, les endpoints /api/oauth/callback et /health sont dans public_paths et sont accessibles depuis internet.

Fix propose :

import { createHmac, timingSafeEqual } from 'crypto';

export function verifyJWT(token: string): JWTPayload | null {
  try {
    const [headerB64, payloadB64, signatureB64] = token.split('.');
    if (!headerB64 || !payloadB64 || !signatureB64) return null;

    const secret = process.env.JWT_SECRET || process.env.SUPABASE_JWT_SECRET;
    if (!secret) throw new Error('JWT_SECRET not configured');

    const expectedSig = createHmac('sha256', secret)
      .update(`${headerB64}.${payloadB64}`)
      .digest('base64url');

    const sigBuffer = Buffer.from(signatureB64, 'base64url');
    const expectedBuffer = Buffer.from(expectedSig, 'base64url');
    if (sigBuffer.length !== expectedBuffer.length || !timingSafeEqual(sigBuffer, expectedBuffer)) {
      return null;
    }

    const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString());
    if (payload.exp && payload.exp < Date.now() / 1000) return null;
    return payload;
  } catch {
    return null;
  }
}

2. CRITIQUE - Endpoints admin sans authentification

Fichier : /stock_8to/33800-stack/projects/connectors-api/src/index.ts Certitude : CERTAIN

Endpoints affectes (aucun appel a getAuthFromRequest, getUserFromRequest, ou requireAuth) :

Ligne Endpoint Risque
1112 DELETE /api/connectors/:id Supprimer n'importe quel connecteur
1767 POST /api/connectors/:id/token Injecter un token systeme sur n'importe quel connecteur
897 POST /api/connectors/:id/provision-type Provisionner des endpoints
1183 GET /api/connectors/:id/schema Lire le schema (information disclosure)
1219 GET /api/connectors/:id/openapi Lire les specs OpenAPI
2000 GET /api/audit/logs/:id Lire n'importe quel log d'audit
2042 GET /api/connectors/:id/logs Lire les logs d'un connecteur

Analyse : Le POST /api/connectors/:id/token (ligne 1767) est le plus dangereux : il permet de definir un token systeme sans aucune authentification. Code exact :

.post('/api/connectors/:id/token', async ({ params, body }) => {
    try {
      const connector = await registry.get(params.id);
      if (!connector) {
        return { error: 'Connector not found' };
      }
      await auth.setToken(connector, body.token, body.type || 'api_key');
      return { success: true, message: `System token set for ${connector.name}` };
    } catch (err: any) {
      return { error: err.message };
    }
  })

Et DELETE /api/connectors/:id (ligne 1112) :

.delete('/api/connectors/:id', async ({ params }) => {
    try {
      await registry.delete(params.id);
      return { success: true };
    } catch (err: any) {
      return { error: err.message };
    }
  })

Facteur attenuant : nginx private: true protege au niveau reseau. Mais ces endpoints restent dangereux si un utilisateur authentifie du reseau local les appelle (pas de verification de droits admin).

Fix propose : Ajouter requireAuth() ou un middleware admin sur chaque endpoint sensible. Au minimum :

.post('/api/connectors/:id/token', async ({ params, body, request }) => {
    const auth = await getAuthFromRequest(request.headers);
    if (!auth) throw new Error('Authentication required');
    // + check isAdmin(auth.userId)

3. HAUTE - /api/fetch accessible sans auth au niveau applicatif

Fichier : /stock_8to/33800-stack/projects/connectors-api/src/index.ts, lignes 201-216 Certitude : CERTAIN

Code :

.post('/api/fetch', async ({ body, request }) => {
    const auth = await getAuthFromRequest(request.headers);
    const userId = auth?.userId || null;
    // ...
    const result = await proxyFetch.execute({
      connector: body.connector,
      instance: body.instance,
      method: body.method,
      path: body.path,
      body: body.body,
      params: body.params,
      headers: body.headers
    }, userId || undefined, auth?.token);

Analyse : getAuthFromRequest() retourne null si aucun token n'est present. Le code ne fait pas de return 401 mais continue avec userId = null. Le proxyFetch.execute() recoit userId = undefined et tente quand meme d'executer la requete avec les tokens systeme (fallback dans auth.getAuthHeader() qui cherche d'abord le token user, puis le token systeme sans user_id).

Un appel anonyme a /api/fetch peut donc utiliser les tokens systeme des connecteurs.

Facteur attenuant :

Impact moyen en pratique car la protection reseau est effective. Mais c'est un defaut de defense-in-depth.

Fix propose :

.post('/api/fetch', async ({ body, request }) => {
    const auth = await getAuthFromRequest(request.headers);
    if (!auth) {
      return { success: false, status: 401, error: 'Authentication required' };
    }
    const userId = auth.userId;

4. HAUTE - wireguard.ts utilise une API Supabase inexistante

Fichier : /stock_8to/33800-stack/projects/connectors-api/src/services/wireguard.ts Certitude : CERTAIN (bug, pas vulnerabilite directe)

Code (lignes representant le pattern repete dans toutes les fonctions) :

const { data: instance, error: fetchError } = await supabase
  .from('user_connectors')
  .select('token_metadata')
  .eq('id', instanceId)
  .eq('user_id', userId)
  .single();

Analyse : Le wrapper Supabase (src/db/supabase.ts) N'EXPOSE PAS la methode .from(). Il expose uniquement supabase.select(), supabase.insert(), supabase.update(), supabase.delete(), supabase.rpc(), supabase.count(), et supabase.storage.*.

L'appel supabase.from(...) va lancer une erreur TypeError: supabase.from is not a function a l'execution. Ce pattern est present dans TOUTES les fonctions de wireguard.ts :

Impact : Toutes les fonctions wireguard.ts crashent a l'execution. Le service VPN est inoperant via ce fichier.

Note : vpn-profiles.ts utilise le bon wrapper et semble etre le remplacement fonctionnel. Les endpoints VPN dans index.ts (lignes 3274+) appellent directement vpn-profiles.ts et fonctionnent.

Fix propose : Reecrire wireguard.ts pour utiliser le wrapper correct, ou supprimer le fichier si vpn-profiles.ts l'a entierement remplace.


5. HAUTE - Execution de code arbitraire via new Function() dans les actions Transform

Fichier : /stock_8to/33800-stack/projects/connectors-api/src/services/actions.ts, ligne ~853 Certitude : CERTAIN

Code :

const fn = new Function('input', 'ctx', `"use strict";\nreturn (async () => {\n${config.code}\n})()`);

L'objet ctx fourni a la fonction contient :

const ctx = {
  executeAction: ..., // peut executer d'autres actions
  fetch: ...,         // proxyFetch avec auth
  instances: ...,     // liste des instances
  actions: ...,       // liste des actions
};

Analyse : Un utilisateur authentifie peut creer une action de type transform avec du code JavaScript arbitraire execute cote serveur dans le processus Bun. Le new Function() n'est PAS sandboxe : le code a acces a process, require (via import dynamique), au filesystem, etc. L'objet ctx donne en plus l'acces a proxyFetch (appels API avec credentials) et executeAction (execution d'autres actions).

Exemples d'exploitation :

// Lire les variables d'environnement (credentials)
return process.env;

// Executer des commandes systeme
const { execSync } = require('child_process');
return execSync('cat /etc/passwd').toString();

// Acceder au filesystem
const fs = require('fs');
return fs.readFileSync('/proc/self/environ').toString();

Impact : RCE (Remote Code Execution) pour tout utilisateur authentifie. Acces total au processus serveur.

Fix propose : Utiliser un sandbox (vm2, isolated-vm) ou restreindre les transforms a des operations predefinies (comme le fait deja routineExecutor.ts avec ses TransformOp typees). Alternativement, limiter les actions transform aux administrateurs.


6. HAUTE - IPs hardcodees dans le code applicatif

Fichier(s) : Multiples Certitude : CERTAIN

Fichier Ligne IP hardcodee Usage
src/services/ai.ts 159 http://192.168.1.12:5501 Fallback downloadJobFile
src/services/notify.ts 5 http://192.168.1.12:5300 Fallback NOTIF_URL
src/db/supabase.ts 8 http://192.168.1.12:8000 Fallback SUPABASE_URL
src/services/userAuth.ts 6 http://supabase-kong-prod:8000 Fallback SUPABASE_AUTH_URL (Docker hostname, pas une IP - OK)

Analyse : Selon les regles (CLAUDE.md interdit les IPs dans le code applicatif), ces fallbacks ne devraient pas exister. En production, les variables d'environnement sont injectees par conf.prod.gouroubleu.yml, donc les fallbacks ne sont normalement pas utilises. Cependant :

  1. Si une variable d'environnement est manquante ou vide, le code utilise l'IP hardcodee
  2. Pi-hole split-DNS resout *.33800.nowhere84.com en local - les IPs sont donc inutiles
  3. Les IPs hardcodees sont une dette technique qui complique la portabilite

Fix propose : Remplacer les fallbacks IP par des domaines *.33800.nowhere84.com ou lever une erreur si l'env var est manquante.


7. HAUTE - OAuth state non valide contre CSRF

Fichier : /stock_8to/33800-stack/projects/connectors-api/src/services/oauth.ts et src/index.ts ligne 1917 Certitude : CERTAIN

Code dans oauth.ts :

getAuthorizeUrl(credential: OAuthCredential): string {
  const state = Buffer.from(JSON.stringify({
    credential_id: credential.id,
    user_id: credential.user_id,
    nonce: randomBytes(16).toString('hex')
  })).toString('base64');
  // ...
}

Code dans le callback (index.ts ligne 1929) :

await oauth.handleCallback(code, state);

Dans oauth.handleCallback() :

const stateData = JSON.parse(Buffer.from(state, 'base64').toString());
const credential = await this.getCredential(stateData.credential_id, stateData.user_id);

Analyse : Le state encode credential_id, user_id et un nonce aleatoire en base64. Mais ce nonce n'est JAMAIS stocke cote serveur ni valide au retour. Le callback decode simplement le state et en extrait credential_id et user_id. Un attaquant peut :

  1. Construire un state valide avec le credential_id et user_id de la victime
  2. Declencher le callback OAuth avec un code qu'il controle
  3. Associer son propre compte OAuth au credential de la victime

Le risque est attenue par le fait que le credential_id est un UUID difficile a deviner.

Fix propose : Stocker le nonce en base de donnees ou en session au moment de la generation du state, puis le verifier au callback.


8. HAUTE - SSH host key verification desactivee

Fichier : /stock_8to/33800-stack/projects/connectors-api/src/services/ssh.ts, ligne ~943 Certitude : CERTAIN

Code :

hostVerifier: (key) => {
  return true; // Accept all for now
}

Analyse : La verification de la cle hote SSH est completement desactivee. Cela rend le service vulnerable aux attaques MITM (Man-in-the-Middle). Un attaquant sur le reseau peut intercepter les connexions SSH et capturer les credentials (mots de passe, commandes, resultats).

Impact : En contexte LAN (192.168.1.x), le risque est modere car le reseau est controle. Mais pour les connexions SSH via WireGuard vers des machines externes, le risque est reel.

Fix propose :

hostVerifier: (key) => {
  const fingerprint = key.hash('sha256');
  // Compare with stored fingerprint from instance metadata
  const storedFingerprint = sshConfig.fingerprint;
  if (!storedFingerprint) {
    // TOFU: Trust On First Use - store and accept
    return true;
  }
  return fingerprint === storedFingerprint;
}

9. HAUTE - Injection de commande dans setupAutoKey (SSH)

Fichier : /stock_8to/33800-stack/projects/connectors-api/src/services/ssh.ts, ligne ~997 Certitude : CERTAIN

Code :

const installCommand = `mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '${sshPublicKey}' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys`;

Analyse : La variable sshPublicKey est interpolee directement dans une commande shell entre single quotes. Si la cle publique contient un single quote ('), la commande shell est cassee et du code arbitraire peut etre injecte.

Exemple d'exploitation : une cle publique contenant ' ; rm -rf / ; echo ' produirait :

echo '' ; rm -rf / ; echo '' >> ~/.ssh/authorized_keys

En pratique, les cles SSH generees par le code ne contiennent pas de single quotes (base64 + prefixe standard), mais si sshPublicKey provient d'une entree utilisateur, l'injection est possible.

Fix propose :

const escapedKey = sshPublicKey.replace(/'/g, "'\\''");
const installCommand = `mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '${escapedKey}' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys`;

10. MOYENNE - NODE_TLS_REJECT_UNAUTHORIZED: "0"

Fichier : /stock_8to/33800-stack/projects/connectors-api/conf.prod.gouroubleu.yml, ligne 57 Certitude : CERTAIN

Code :

env:
  NODE_TLS_REJECT_UNAUTHORIZED: "0"

Analyse : Cette variable d'environnement desactive la verification des certificats TLS pour TOUTES les requetes HTTPS sortantes du processus. Cela signifie que les appels vers les APIs externes (Google, GitHub, etc.) acceptent des certificats invalides, auto-signes, ou expires.

Le code dans fetch.ts (ligne 171) gere deja les certificats auto-signes pour les services internes de maniere ciblee :

const tlsOptions = url.startsWith('https://192.168.') || url.startsWith('https://10.')
  ? { tls: { rejectUnauthorized: false } }
  : {};

La variable globale est donc redondante avec le mecanisme cible et introduit un risque inutile pour les connexions externes.

Fix propose : Supprimer NODE_TLS_REJECT_UNAUTHORIZED: "0" de conf.prod.gouroubleu.yml. Le mecanisme cible dans fetch.ts suffit pour les services internes.


11. MOYENNE - Sel fixe pour la derivation de cle de chiffrement SSH

Fichier : /stock_8to/33800-stack/projects/connectors-api/src/services/ssh.ts, ligne ~105-110 Certitude : CERTAIN

Code :

const ENCRYPTION_KEY = process.env.SSH_ENCRYPTION_KEY || 'default-key-change-in-production-32!';

export function encrypt(text: string): string {
  const key = scryptSync(ENCRYPTION_KEY, 'salt', 32);
  const iv = randomBytes(16);
  const cipher = createCipheriv('aes-256-cbc', key, iv);
  // ...
}

Analyse :

  1. Le sel de scryptSync est la chaine fixe 'salt'. En consequence, la cle derivee est toujours la meme pour un meme ENCRYPTION_KEY. Cela ne reduit pas la securite du chiffrement directement (l'IV est aleatoire), mais annule l'interet du salt : si la cle maitre fuite, toutes les valeurs chiffrees sont immediatement dechiffrables.
  2. Le fallback 'default-key-change-in-production-32!' serait catastrophique s'il etait utilise en prod. Verifie dans conf.prod.gouroubleu.yml : SSH_ENCRYPTION_KEY: "@SSH_ENCRYPTION_KEY" est injecte via secret, donc le fallback n'est PAS utilise en production.

Fix propose : Utiliser un sel aleatoire stocke avec le ciphertext (comme l'IV l'est deja).


12. MOYENNE - ReDoS potentiel dans routineExpressions.ts

Fichier : /stock_8to/33800-stack/projects/connectors-api/src/services/routineExpressions.ts Certitude : PROBABLE

Code (dans evaluateCondition avec l'operateur matches) :

case 'matches':
  return new RegExp(right as string).test(String(left));

Analyse : L'expression reguliere vient de la configuration de la routine (champ right du noeud condition). Un utilisateur peut creer une routine avec un pattern regex malveillant qui cause un ReDoS (Regular Expression Denial of Service). Exemple : (a+)+$ avec une entree de type aaaaaaaaaaaaaaaaax.

Impact : Blocage du thread d'execution pendant la duree du ReDoS. Attenue par le MAX_EXECUTION_TIME = 300000 (5 min) dans routineExecutor.ts.

Fix propose : Utiliser un timeout sur l'execution regex ou valider les patterns contre des constructions dangereuses.


13. MOYENNE - new RegExp() depuis entree utilisateur dans transform

Fichier : /stock_8to/33800-stack/projects/connectors-api/src/services/routineExecutor.ts, ligne 653 Certitude : CERTAIN

Code :

case 'replace':
  if (typeof data !== 'string') return data;
  return data.replace(new RegExp(op.config.pattern, op.config.flags || 'g'), op.config.replacement || '');

Analyse : Meme probleme de ReDoS que le finding #12. Le pattern provient de la configuration de la routine (entree utilisateur). De plus, le replacement peut contenir des patterns de remplacement speciaux ($1, $&, etc.) mais sans risque de securite au-dela du ReDoS.


14. MOYENNE - Upload SSH avec contenu non echappe

Fichier : /stock_8to/33800-stack/projects/connectors-api/src/services/ssh.ts, ligne ~1068 Certitude : PROBABLE

Analyse : La methode upload() dans ssh.ts utilise un heredoc pour ecrire du contenu sur le serveur distant. Si le contenu contient des sequences speciales du heredoc (ex: EOF sur une ligne seule), la commande sera mal formee. La variable escapedContent est calculee mais il faudrait verifier qu'elle est bien utilisee dans la commande finale.

Impact : Corruption de fichier uploade, ou injection de commande si le contenu est malveillant.


15. MOYENNE - Commandes dangereuses non bloquees (SSH)

Fichier : /stock_8to/33800-stack/projects/connectors-api/src/services/ssh.ts Certitude : CERTAIN

Code dans index.ts (ligne 2498-2508) :

if (ssh.isDangerous(body.command)) {
  if (!body.force) {
    return {
      success: false,
      warning: true,
      message: 'This command appears dangerous. Add "force": true to execute anyway.',
      command: body.command
    };
  }
}

Analyse : Les commandes dangereuses (rm -rf, mkfs, dd, shutdown, etc.) declenchent un avertissement mais sont executables avec force: true. Le log d'audit enregistre la commande, mais il n'y a pas de blocage hard pour les commandes les plus destructives. C'est un choix de design (pas un bug), mais cela merite attention.


16. MOYENNE - Webhook sans protection anti-replay

Fichier : /stock_8to/33800-stack/projects/connectors-api/src/index.ts, lignes 5324-5354 Certitude : CERTAIN

Code :

.post('/api/webhooks/:routineId', async ({ params, body, query }) => {
    // Public endpoint (no auth required) - protected by optional token
    // ...
    if (expectedToken) {
      const providedToken = query.token || (body as any)?.token;
      if (providedToken !== expectedToken) {
        return new Response(JSON.stringify({ error: 'Invalid token' }), { status: 403 });
      }
    }

Analyse : L'endpoint webhook est public par design. La protection par token est optionnelle (if (expectedToken)). Si le webhook est configure sans token, n'importe qui peut declencher l'execution de la routine. Meme avec token, il n'y a pas de protection anti-replay : un meme webhook peut etre rejoue indefiniment.

Fix propose : Rendre le token obligatoire. Ajouter un timestamp + HMAC pour la protection anti-replay.


17. BASSE - Attachments AI accessibles sans auth via UUID

Fichier : /stock_8to/33800-stack/projects/connectors-api/src/index.ts, ligne 4793 Certitude : CERTAIN

Code :

.get('/api/ai/attachments/:id/file/*', async ({ params }) => {
    // No auth required — the UUID itself is the secret

Analyse : Les fichiers AI (images generees, etc.) sont accessibles a quiconque possede l'UUID. Les UUIDs v4 sont suffisamment aleatoires (122 bits d'entropie) pour rendre le brute-force impraticable. C'est un pattern "capability URL" acceptable pour du contenu non sensible. Cependant, les URLs sont potentiellement loguees et pourraient etre partagees involontairement.

Impact : Faible. Le contenu genere par l'IA n'est generalement pas confidentiel.


18. BASSE - Audit logs accessibles sans filtre utilisateur

Fichier : /stock_8to/33800-stack/projects/connectors-api/src/index.ts, lignes 1946-2065 Certitude : CERTAIN

Code (lignes 1946-1948) :

.get('/api/audit/logs', async ({ query, request }) => {
    try {
      const userId = getUserFromRequest(request.headers);
      const { logs, total } = await audit.list({
        userId: query.user_id || undefined,

Et (ligne 2000) :

.get('/api/audit/logs/:id', async ({ params }) => {
    try {
      const log = await audit.get(params.id);

Analyse : getUserFromRequest() est la version SYNCHRONE qui ne valide pas les API keys et ne fait pas de throw si le token est absent - elle retourne simplement null. Le userId de l'appelant n'est pas utilise pour filtrer les logs : le parametre query.user_id permet de filtrer, mais c'est un filtre optionnel fourni par le client.

GET /api/audit/logs/:id n'a AUCUNE verification d'auth. N'importe qui avec acces reseau peut lire n'importe quel log d'audit.

Les logs d'audit peuvent contenir des informations sensibles (IPs, actions effectuees, noms d'instances).

Fix propose : Ajouter requireAuth() et filtrer obligatoirement par userId de l'appelant (sauf pour les admins).


19. BASSE - client_secret OAuth stocke en clair

Fichier : /stock_8to/33800-stack/projects/connectors-api/src/services/oauth.ts Certitude : CERTAIN

Analyse : Les client_secret des credentials OAuth sont stockes en clair dans la table oauth_credentials de Supabase. Si la base de donnees est compromise, tous les secrets OAuth sont exposes. Ce n'est pas un probleme si la base de donnees est bien protegee, mais c'est une pratique sous-optimale.

Fix propose : Chiffrer les client_secret avec la meme mecanique que les credentials SSH (encrypt()/decrypt()).


POINTS VERIFIES ET NON-PROBLEMES

Pour etre exhaustif, voici les points qui ont ete verifies et sont conformes :

SSH_ENCRYPTION_KEY en production

Verifie : conf.prod.gouroubleu.yml ligne 45 injecte SSH_ENCRYPTION_KEY: "@SSH_ENCRYPTION_KEY" via un secret. Le fallback hardcode ('default-key-change-in-production-32!') n'est PAS utilise en production.

API Keys correctement implementees

Verifie : src/services/apiKeys.ts utilise SHA-256 pour hasher les cles, ne stocke jamais la cle en clair apres creation, verifie l'expiration et la revocation. L'implementation est solide.

vpn-profiles.ts utilise le bon wrapper Supabase

Verifie : Contrairement a wireguard.ts, le fichier vpn-profiles.ts utilise supabase.select(), supabase.insert(), etc. correctement.

routineExecutor.ts a des limites de securite

Verifie : MAX_NODES = 50, MAX_LOOP_ITERATIONS = 200, MAX_API_CALLS = 30, MAX_EXECUTION_TIME = 300000 (5 min). Ces limites empechent les abus via les routines.

proxyFetch gere les credentials correctement

Verifie : fetch.ts ne log pas les credentials, utilise les headers d'auth du connecteur de maniere appropriee, et gere les tokens OAuth avec refresh automatique.

userAuth.ts utilise GoTrue correctement

Verifie : L'authentification email/password passe par Supabase GoTrue (/auth/v1/token?grant_type=password). Le serveur ne gere pas les mots de passe directement.

Pas d'injection SQL

Verifie : Le wrapper supabase.ts utilise des parametres PostgREST (filtres eq, is, etc.) qui sont automatiquement parametrises. Pas d'interpolation directe de valeurs dans les requetes.


RECOMMANDATIONS PRIORITAIRES

Urgence immediate (a faire cette semaine)

  1. Implementer la verification de signature JWT (Finding #1)

    • Effort : 30 min
    • Impact : Elimine le risque d'usurpation d'identite
  2. Ajouter l'authentification aux endpoints admin (Finding #2)

    • Effort : 1h
    • Impact : Elimine l'acces non authentifie aux operations destructives
  3. Ajouter le check auth dans /api/fetch (Finding #3)

    • Effort : 5 min
    • Impact : Defense-in-depth

Court terme (dans les 2 semaines)

  1. Sandboxer les actions Transform (Finding #5)

    • Effort : 2-4h avec isolated-vm
    • Impact : Elimine le RCE
  2. Supprimer NODE_TLS_REJECT_UNAUTHORIZED: "0" (Finding #10)

    • Effort : 5 min (supprimer la ligne dans conf.prod.gouroubleu.yml)
    • Impact : Restaure la verification TLS pour les APIs externes
  3. Corriger l'echappement dans setupAutoKey (Finding #9)

    • Effort : 10 min
    • Impact : Elimine l'injection de commande

Moyen terme (dans le mois)

  1. Implementer la validation du state OAuth (Finding #7)
  2. Implementer TOFU pour les cles hote SSH (Finding #8)
  3. Supprimer les IPs hardcodees (Finding #6)
  4. Nettoyer ou supprimer wireguard.ts (Finding #4)

NOTES SUR LA METHODOLOGIE