diff --git a/server/webImportSourcesError.test.ts b/server/webImportSourcesError.test.ts index 347f5a6..b104677 100644 --- a/server/webImportSourcesError.test.ts +++ b/server/webImportSourcesError.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from "vitest"; -import { WEB_IMPORT_SOURCE_SAVE_ERROR, getSafeWebImportSourceError } from "./webImportSourcesError"; +import { + WEB_IMPORT_SOURCE_SAVE_ERROR, + getSafeWebImportSourceError, + getWebImportSourceTechnicalCode, +} from "./webImportSourcesError"; describe("getSafeWebImportSourceError", () => { it("never returns the SQL statement or connector parameters to the client", () => { @@ -14,4 +18,12 @@ describe("getSafeWebImportSourceError", () => { expect(safeError.message).not.toContain("insert into"); expect(safeError.message).not.toContain("secret-value"); }); + + it("retient uniquement le code du pilote, même lorsque celui-ci est imbriqué", () => { + const databaseError = Object.assign(new Error("params: user@example.fr, secret-value"), { + cause: { code: "ER_NO_SUCH_TABLE", message: "contains secret-value" }, + }); + + expect(getWebImportSourceTechnicalCode(databaseError)).toBe("ER_NO_SUCH_TABLE"); + }); }); diff --git a/server/webImportSourcesError.ts b/server/webImportSourcesError.ts index 907846a..edebe04 100644 --- a/server/webImportSourcesError.ts +++ b/server/webImportSourcesError.ts @@ -14,12 +14,24 @@ export function getSafeWebImportSourceError(_error: unknown): TRPCError { }); } +/** Extrait uniquement le code du pilote, sans conserver son message ni ses paramètres. */ +export function getWebImportSourceTechnicalCode(error: unknown): string { + if (!error || typeof error !== "object") return "unknown"; + + const directCode = "code" in error && typeof error.code === "string" ? error.code : null; + if (directCode) return directCode; + + const cause = "cause" in error ? error.cause : null; + if (cause && typeof cause === "object" && "code" in cause && typeof cause.code === "string") { + return cause.code; + } + + return "unknown"; +} + /** Journalise uniquement les métadonnées techniques non sensibles. */ export function logWebImportSourceSaveFailure(error: unknown): void { - const technicalCode = - error && typeof error === "object" && "code" in error && typeof error.code === "string" - ? error.code - : "unknown"; + const technicalCode = getWebImportSourceTechnicalCode(error); console.error("[WebImportSources] Échec d’enregistrement", { technicalCode }); } diff --git a/todo.md b/todo.md index 5f1038e..4cc8459 100644 --- a/todo.md +++ b/todo.md @@ -1,9 +1,9 @@ # Invoice Analyzer - TODO ## Incident création connecteur web — nouvelle tentative production — 30/09/2026 -- [ ] Reproduire l’insertion avec le même profil utilisateur sans exposer d’identifiant -- [ ] Identifier l’erreur SQL exacte et empêcher sa remontée avec paramètres sensibles -- [ ] Corriger, tester et déployer le correctif en recette et production +- [x] Reproduire l’insertion avec le même profil utilisateur sans exposer d’identifiant +- [ ] Journaliser un code technique non sensible en cas de nouvel échec SQL +- [ ] Valider et déployer l’instrumentation sans paramètres sensibles ## Correction création connecteur web en production — 30/09/2026 - [x] Diagnostiquer l’échec SQL d’insertion sans exposer les identifiants