Audits · juin 2026Audit de vérification — 2026-06-14

Audit de vérification — 2026-06-14

Audit de l'état courant de la branche après remédiation de l'audit complet du 2026-06-13 (PR #292/#293 mergées). Objectif double : (1) vérifier au code — pas aux messages de commit : que chaque correctif…

Audit de l'état courant de la branche après remédiation de l'audit complet du 2026-06-13 (PR #292/#293 mergées). Objectif double : (1) vérifier au code — pas aux messages de commit : que chaque correctif critique/haut/moyen tient réellement ; (2) détecter les nouveaux problèmes non couverts. Réalisé en lecture seule via 6 analyses parallèles (sécurité/multi-tenancy, billing/Stripe, couche IA, base de données, ops/CI/deps, frontend/SEO/a11y). Chaque constat porte une référence fichier:ligne. Sévérités : Critique / Haute / Moyenne / Basse / Info.

Vérifications mécaniques

VérificationRésultat
pnpm typecheck✅ 0 erreur
pnpm lint✅ 0 erreur, ⚠️ 155 warnings (inchangé vs 13/06 — tout en warn, pas de --max-warnings 0)
pnpm test✅ 447 passés, 9 skippés (55 fichiers)
pnpm deadcode (knip)✅ exit 0 (fichiers/deps bloquants verts)
pnpm audit --prod✅ 0 vulnérabilité (le correctif 30→0 tient)

Statut de remédiation (session 2026-06-14)

Suite à cet audit, une passe de remédiation a été appliquée sur cette branche (17 commits, dont les 2 décisions produit M3 + N18), avec typecheck + 451 tests verts, lint 0 erreur, schema-guard régénéré et pnpm audit --prod à 0 vulnérabilité.

✅ Corrigé

  • Argent (P0) : réservation atomique des crédits (reserveCreditsForChannel)
    • cap mid-stream + settle via trackUsage(holdId) étendus aux 3 canaux (bot WhatsApp, WhatsApp per-store, API channel) : H-BILL-4 et le volet canaux de H-BILL-5 ; rate-limit ajouté sur l'API channel ; EVI gate par secret partagé EVI_CLM_SECRET (timing-safe, fail-closed) : H-SEC-7 ; bornes int4 sur les Zod money marketplace — N7. Cap de coût + abort propagés aux sous-agents délégués (addExternalUsage + parentSignal via AbortSignal.any) : N20 / volet sous-agents de H-BILL-5.
  • Marketplace : anonymat enforced au data-layer (url masquée dans tous les mappers (toCardData/search/related/recommendations-v2/sellers) + route recommendations, listings anonymes exclus du profil vendeur public, lien vendeur masqué sur la page detail) H-SEC-8.
  • Sécurité / fuite : exports org/me en select whitelist (drop Store.apiKey/stripe*), H-SEC-5/N6 ; token Notion (session requise + chiffrement AES-256-GCM), N5 ; IDOR ThemeBranch (updateMany+storeId), N16 ; bug spelling custom-app + retrait _debug — N17.
  • Injection IA (C3.4) : fence du bloc mémoire (system prompt), du DOM navigateur, des hits RAG externes, des docs scrapées, et des sorties concurrents (sections/catalog/ads/angles intelligence) ; canWrite sur les outils navigateur mutants + run* (aeo/aov/vitals) ; canRead sur les 8 commerce tools + neutralisation des labels UTM, N1/N8/N9.
  • DB : onDelete: Restrict explicite sur Order.listing, N2 ; index tierExpiresAt, Dispute.resolvedById, SavedListing/ListingVote.listingId, N3/#4 ; timeout 30s sur les transactions de suppression cascade — N10.
  • RGPD : redaction Message.content du user supprimé (convos non-possédées), N12 ; rétention StripeEvent.payload (90j) dans le cron de purge, N11 ; purge MemoryEvent par scope à la suppression compte/org + rétention 180j + @@index([ts]), N10 ; scrubbing PII (emails/téléphones) à l'extraction des facts mémoire + test unitaire — N19.
  • Ops / SEO : job build (next build) en CI, N4/H-OPS-2 ; timeout sur les self-fetch du proxy, N14 ; pathname sur l'image *.myshopify.com, N14 ; contradiction generateStaticParams+force-dynamic retirée, N13 ; /sell
    • /status ajoutés au sitemap.
  • Décisions produit (tranchées) : M3, payout vendeur en escrow, versé après la fenêtre de dispute 14j via le cron marketplace-payouts (plus au checkout) ; pendant la fenêtre aucun transfer n'existe donc un refund ne nécessite aucune reversal. N18 — relay MCP passé en lecture seule (refuse destructive + critical, writes via le dashboard).

⏳ Reporté — chantiers dédiés (hors périmètre d'une passe de remédiation)

  • H-OPS-3 : garde VERCEL_ENV sur le push/heal schéma, un gate brut casserait les déploiements preview (config par-environnement nécessaire).
  • Lint bloquant (--max-warnings 0) : nécessite de résorber les 155 warnings d'abord.
  • Auth.js v5 (~2-3 j), noUncheckedIndexedAccess (remédiation repo-wide), i18n dashboard + tokens couleur (N15), émission howToJsonLd (besoin de steps structurés dans le contenu).

Verdict d'ensemble

La remédiation du 13/06 tient sur l'essentiel : les trois critiques sont réellement corrigés au code (C1 cross-tenant, C2 comptabilité FIFO des crédits, C3.1-3 autorisation des tools IA), de même que la majorité des hauts sécurité et billing. La discipline tenant-auth reste exemplaire (getStoreAccess/ getOrgAccess centralisés et appliqués quasi partout) et aucun nouveau chemin d'accès cross-tenant aux données n'a été trouvé dans les tools IA ni les routes.

Le risque résiduel est concentré sur deux axes :

  1. Argent — les garde-fous restent limités au chat web. La réservation atomique sous pg_advisory_xact_lock (H-BILL-5) n'a toujours qu'un seul appelant : le chat web. Les sub-agents délégués, les canaux API/WhatsApp et la voix EVI lisent le solde sans verrou puis débitent après coup : course TOCTOU de surcharge non mitigée. Les canaux n'ont ni estimate pré-stream ni cap mid-stream (H-BILL-4 partiel). EVI reste sans auth ni gate crédits (H-SEC-7 partiel). Le payout vendeur part immédiatement alors que la fenêtre de dispute est de 14 j (M3, reporté).

  2. Injection IA — fenceUntrusted() existe mais sa couverture est incomplète (C3.4 partiel). Le helper est bien conçu (sentinelle aléatoire par appel) mais ~5 points d'ingress seulement sont fencés. Restent bruts : DOM navigateur, sous-outils intelligence concurrents, hits RAG, facts mémoire ré-injectés dans le system prompt, et data Shopify Admin. Le relay MCP laisse encore passer les mutations destructive sans approbation, et l'extraction mémoire ne scrub aucune PII.

Le reste est de la dette structurée (retention DB, types money, i18n dashboard, ISR marketing) et des reports produit déjà documentés.


Statut de remédiation vérifié

🔴 Critiques — tous corrigés

IDStatutPreuve
C1 prise de contrôle cross-tenant store via chat✅ CORRIGÉhandler.ts:186-194 valide storeContext.storeID via getStoreAccess et rejette 403 si storeAccess.orgId !== activeOrgId ; activeOrgId re-dérivé DB (:172-178), rôle écrasé par orgAccess.role (:201). Réserve : getStore/getStoreIntegrations (prisma-provider.ts:179,456) ne se filtrent pas par org — la défense en profondeur manque, mais le guard du handler ferme le trou.
C2 double-comptage des crédits dépensés à l'expiry✅ CORRIGÉcredit-balance.ts:80-113 : vraie comptabilité FIFO par lots, forme close fifoBalance(N,E,D)=max(0, N−max(0, D−E)) (les débits consomment le pool expiré E avant le non-expiré N). Hold release + write usage atomiques (runtime/billing.ts:167-172). Implémentation réelle, pas qu'un index.
C3.1 shopifyAdminGraphQL sans RBAC✅ CORRIGÉshopify-admin.ts:224-231 : isMutation && !canWrite(ctx.userRole) → denied.
C3.2 autonomyMode client-contrôlé✅ CORRIGÉhandler.ts:210-213 : autonomous seulement si hasMinRole(user.role,"admin") (rôle DB-dérivé), sinon clampé approval.
C3.3 approbations auto-approuvables✅ CORRIGÉservices/agent-autonomy/index.ts:208-247 : exige decidedByOrgId === existing.orgId et decidedByCanWrite (owner/admin).
C3.4 fencing anti-injection à un seul endroit⚠️ PARTIELfence.ts:22-37 : helper réel à sentinelle aléatoire randomBytes(9). Appliqué à ~5 ingress (intelligence summary/teardown, store content, webhook payloads). Non fencés : sous-outils intelligence concurrents (intelligence-tools.ts:506-516,539-545,591-602,754,774,801), hits RAG (knowledge-tools.ts:40-46,85-91), facts mémoire dans le system prompt (memory/format.ts:54,73 → handler-system-layers.ts:150), data Shopify Admin (shopify-admin.ts:170,298 = simple clé _untrusted, pas de fence), situational via sanitizeInline au lieu de fence (format-situational.ts:74-84).

🟠 Hautes — sécurité

IDStatutPreuve
H-SEC-1 preview proxy SSRF/auth✅ CORRIGÉAuth (preview/proxy/route.ts:65,213), * rejeté (:32), HTTPS-only (:57), validateScanUrl (:59), rate-limit (:68), iframe sandbox CSP (:118).
H-SEC-2 auto-promotion ADMIN✅ CORRIGÉBranche adminCount===0 retirée ; promotion sur userCount===1 ou ADMIN_EMAIL exact, sous withSessionAuth (auth/welcome/route.ts:29,92-104).
H-SEC-3 OAuth Shopify HMAC + domaine✅ CORRIGÉisValidShopDomain regex exacte *.myshopify.com + verifyShopifyOAuthHmac constant-time fail-closed + state cookie comparé (shopify-oauth.ts:18-74, callback:43,48,57).
H-SEC-4 callbacks connecteurs⚠️ PARTIELGoogle/Meta/Klaviyo/Figma : session + getStoreAccess/getOrgAccess + chiffrement tokens corrigés. Mais OAuth state = base64 JSON non signé (ni HMAC ni cookie CSRF), PKCE helpers présents mais inutilisés. Notion reste ouvert (cf. nouveaux constats).
H-SEC-5 exports dumpent les secrets⚠️ PARTIELstores/[storeId]/export corrigé (select whitelist, route.ts:37-60). organizations/[orgId]/export ouvert : include:{stores:true,subscription:true} (route.ts:31-37) expose Store.apiKey + stripeCustomerId/SubscriptionId. Idem me/export/route.ts:36,39.
H-SEC-6 écritures tokens hors chiffrement✅ CORRIGÉWizard (wizard/store/connect/route.ts:546) + Figma callback (:116,118) chiffrent avant write ; cron backfill idempotent.
H-SEC-7 EVI sans auth/rate-limit/crédits⚠️ PARTIELRate-limit ajouté (evi/chat/completions/route.ts:166, 20/min/IP). Toujours sans auth ni gate crédits — stream sur clés plateforme, brûlage de crédits borné par la seule IP.
H-SEC-8 anonymat marketplace inopérant❌ OUVERTMasquage UI-only par design (listings.ts:96-104, url brut). Domaine réel dans le Flight payload via toCardData/search.ts:126,173/sellers.ts:112/recommendations-v2.ts:124 ; recommendations/route.ts:104-121 (non-auth) renvoie url brut ; page detail expose sellerId + lien public (marketplace/[type]/[slug]/page.tsx:207-211,817-827). Dé-anonymisation triviale.

🟠 Hautes — billing

IDStatutPreuve
H-BILL-1 daily bonus dépensable ~10 min✅ CORRIGÉCron 5 0 * * * (00:05), expiresAt = startOfToday + 24h (daily-bonus/route.ts:153) → ~24 h utilisables. (Docstring :5 encore « 23:50 » = commentaire périmé, code correct.)
H-BILL-2 changement plan = 2ᵉ subscription✅ CORRIGÉCheckout dédup → portal si sub live (stripe/checkout/route.ts:94-113) ; Subscription.orgId @unique ; deleted-handler compare l'id (webhooks.ts:1137-1149).
H-BILL-3 subscription.created force active + grant✅ CORRIGÉStatut restreint (webhooks.ts:960-963), grant gaté active (:990-997) ; invoice.paid exclut trialing (:1242).
H-BILL-4 canaux API/WhatsApp hors garde-fous⚠️ PARTIELAjoutés : requirePaidPlan + floor balance<=0 (3 canaux) + rate-limit (WhatsApp ; manque sur API channel). Manquent toujours : estimate pré-stream, hold/réservation, cap mid-stream (channels/api/route.ts:72-86, bot-handlers.ts:277-301, whatsapp-per-store.ts:410-419). Solde quasi-nul → requête arbitrairement coûteuse autorisée.
H-BILL-5 réservation concurrente chat web seulement❌ OUVERTreserveCredits (advisory lock atomique) correct mais un seul appelant : handler-prestream-gate.ts:66 (chat web). Sub-agents (delegate-tool.ts:215 sans hold ; handler-team-mode.ts:119-135 trackUsage sans holdId), API channel, WhatsApp ×2, EVI : read-then-debit → course de surcharge. Non étendu.

🟠 Hautes — base de données

IDStatutPreuve
H-DB-1 Offer @@unique collision statuts terminaux⚠️ PARTIELIndex unique partiel WHERE status='PENDING' posé (pending-migrations.ts:137-151) → plus de jam. Mais l'expiry reste un updateMany en bloc (cron/marketplace-maintenance/route.ts:54-60), pas d'isolation par ligne comme spécifié (dé-risqué par l'index partiel).
H-DB-2 RGPD Conversation/Message survivent✅ CORRIGÉPurge dans delete-account/route.ts:118-135 + organizations/[orgId]/route.ts:201-205 (Message cascade).
H-DB-3 rétention AuditLog contradictoire✅ CORRIGÉcron/prune-audit-log/route.ts:44-49 exempte billing.* + shopify.webhook.*.
#4 index AuditLog/DealThread⚠️ PARTIELAuditLog(resource,resourceId,createdAt) + (userId,createdAt) + DealThread(listingId) posés (schema.prisma:939,941,2035). Mais FK Dispute.resolvedById / SavedListing.listingId toujours non indexées (contraintes ≠ index).

🟠 Hautes — ops / frontend

IDStatutPreuve
H-OPS-1 split-brain secret auth✅ CORRIGÉproxy.ts:278,324 : NEXTAUTH_SECRET || AUTH_SECRET ; miroir inverse env/server.ts:516-520,547-551 fallback sur les deux.
H-OPS-2 aucun next build avant deploy⚠️ PARTIELCI a maintenant typecheck + lint + test (.github/workflows/ci.yml:26,44,60). Mais toujours aucun job build — une casse build-only (RSC, import server/client, route config) atteint main et n'échoue qu'au deploy. ignoreBuildErrors:true toujours là (next.config.mjs:22).
H-OPS-3 previews peuvent muter le schéma prod❌ OUVERTvercel-build.mjs:44-63 (db push) et instrumentation-node.ts:52 (heal cold-start) sans garde VERCEL_ENV==='production'. Mitigé seulement opérationnellement (Neon n'expose pas l'URL au build, push non destructif).
#4-crons stampede minuit/04:00✅ CORRIGÉvercel.json étalé : pire slot = 3 jobs à 0 2 (rarement simultanés) ; reset-credits 8 0, daily-bonus 5 0. 47/47 crons mappés avec maxDuration.
H-FE-1 contact form mailto:✅ CORRIGÉcontact-form.tsx:43 → POST /api/contact (Node, zod, rate-limit, Resend).
H-FE-3 force-dynamic annule l'ISR intelligence⚠️ PARTIELintelligence/[category]/[slug] corrigé (revalidate=21_600 + noindex). Mais 16 pages marketing encore force-dynamic + contradiction generateStaticParams+force-dynamic sur [category]/page.tsx:56,60 (cf. nouveaux constats).
H-FE-2 / H-FE-4 / H-FE-5⏳ REPORTÉ (confirmé ouvert)Pas de next/dynamic sur / + oneLight toujours bundlé ; 0 useTranslations sur tout (dashboard) ; tooltips data-tip du hub (améliorés clavier via use-shared-tooltip.ts mais toujours hors registry + #fff en dark-only).

SEO / divers (déjà signalés 13/06)

  • ✅ Canonical racine / retiré (layout.tsx:90-96) ; ✅ lien affilié footer rel="...sponsored" (app-footer.tsx:111).
  • ❌ howToJsonLd toujours jamais émis (helper lib/seo/structured-data.ts:353 sans appelant : drift CLAUDE.md « Tutorial detail (How-To JSON-LD) »).
  • ❌ Sitemap toujours sans /status, /sell, /sellers/[id], détails tutorials/courses/docs (sitemap.ts:44-107).

Nouveaux constats (non couverts le 13/06)

🟠 Hautes

#ConstatRéférence
N1Outils navigateur sans RBAC + DOM brut non fencé. browserGetDOM renvoie le outerHTML (≤64 K) d'une page arbitraire au modèle, seuls les <script> retirés — vecteur d'injection texte/attributs/commentaires, non fencé (tools/browser/get-dom.ts:93). Les 6 outils navigateur (navigate/click/type/scroll/screenshot/get-dom) mutent l'état web externe sans canWrite — seul le backstop autonomie protège, un viewer peut piloter le navigateur.tools/browser/*
N2Order.listing sans onDelete → Restrict implicite. Toutes les autres relations de MarketplaceListing sont Cascade ; supprimer/purger un listing ayant une commande lève une violation FK et bloque la suppression. Incohérent avec l'intent cascade, non géré côté app.prisma/schema.prisma:2056
N3Scan cron tierExpiresAt non indexé. marketplaceListing.updateMany (featured-expiry + tier-downgrade) scanne tierExpiresAt sans index support (schema.prisma:1903-1909 couvre type/status/featured et status/tier, pas tierExpiresAt) → full scan ×2 par run quotidien.cron/marketplace-maintenance/route.ts:35-50
N4Pas de job build en CI (cf. H-OPS-2) — c'est la clé de voûte sur laquelle repose ignoreBuildErrors, et elle n'existe pas..github/workflows/ci.yml

🟡 Moyennes

#ConstatRéférence
N5Token Notion en clair dans un cookie, sans session ni liaison tenant. Écrit brut (httpOnly+secure mais non chiffré), seul IP rate-limit + CSRF state ; quiconque a la session du navigateur a un token Notion exploitable. Bypasse la couche de chiffrement DB ; lecteurs downstream (chat/actions/notion.ts, api/health/chat/notion) le lisent brut.auth/notion/callback/route.ts:77-82
N6Exports org/me fuient les secrets (= H-SEC-5 ouvert) : include brut → Store.apiKey (clair) + identifiants Stripe sérialisés dans la réponse.organizations/[orgId]/export/route.ts:31-37, me/export/route.ts:36,39
N7Overflow int4 sur les colonnes argent marketplace. Offer.amount/counterAmount, Order.amount/fee, MarketplaceListing.askingPrice/recurringPrice sont Int (max ~21,47 M$ en cents) ; les Zod des prix listing n'ont aucune borne haute et les offres autorisent .max(100_000_000_00) ($100 M) → « integer out of range » → 500 non géré.api/marketplace/offers/route.ts:32, api/vendor/marketplace/listings/route.ts:57-58, checkout/route.ts:171
N8commerce-tools (8 outils) sans canRead ni re-check orgId : revenu, segments RFM clients, attribution lisibles par un viewer. getAttributionByUtm renvoie les chaînes UTM (visiteur-contrôlables) brutes, non fencées.tools/commerce-tools.ts:~421
N9runTrackingScan / searchShopifyDocs : sans RBAC + sortie scrapée non fencée. runTrackingScan écrit un AuditLog + déclenche un scan d'URL externe sans gate, renvoie summary/stack détectés bruts ; searchShopifyDocs renvoie du markdown Firecrawl (≤4000 c.) brut.tools/tracking-scan.ts:109,159,272, tools/shopify-docs.ts:148-153
N10MemoryEvent orphelin + rétention PII. Aucune FK vers le fact parent (factId/scopeId = strings) alors que le commentaire schéma promet « cascade-delete RGPD » → supprimer un fact/org/user orphelinise chaque MemoryEvent (PII dans oldValue/newValue Json survit). Jamais pruné non plus (croissance illimitée).prisma/schema.prisma:485-498
N11StripeEvent.payload (PII) jamais purgé. Stocke le corps complet de l'event Stripe (email, adresse, métadonnées carte) indéfiniment ; commentaire schéma promet un job de rétention inexistant. L'idempotence n'a besoin que de event.id.prisma/schema.prisma:769-786
N12Message.content non purgé à la suppression de compte pour les conversations non-possédées. Seules les convos userId/orgId du user sont supprimées ; dans une convo store multi-membre possédée par un autre membre, les messages tapés par l'utilisateur supprimé survivent (authorUserId null mais content PII intact).prisma/schema.prisma:1331, delete-account/route.ts
N1316 pages marketing en force-dynamic (le correctif ISR du [slug] n'a pas été propagé) : SSR par visite, TTFB/CWV dégradés sur pages indexables ; intelligence/[category]/page.tsx:60 définit generateStaticParams() que force-dynamic annule silencieusement.intelligence/{page,radar,lookup}, [category]/page.tsx, community/{events,forum,levels}, roadmap, changelog, sponsor/*, feedback
N14Lint non-bloquant + pas de garde VERCEL_ENV + fetch proxy sans timeout : toutes les règles ESLint en warn sans --max-warnings 0 (package.json:15) ; push/heal schéma sans garde env (vercel-build.mjs:40, instrumentation-node.ts:52) ; proxy.ts:285,368 fetch /api/auth/onboarding-status sans AbortSignal.timeout (stall site-wide possible).(multi)
N15Couleurs/locale hardcodées dashboard : panneaux bg-[#000000]/bg-[#121212] (shell-client.tsx:865,899) hors tokens ; donut RFM 12 hex littéraux + label LABEL_FR français en dur (customer-segment-donut.tsx:22-36) dans un dashboard sinon 100 % anglais.(multi)

🟢 Basses / Info

#ConstatRéférence
N16IDOR cross-tenant ThemeBranch : POST vérifie l'org de storeId puis themeBranch.update({where:{id:branchId}}) avec branchId du body jamais lié à storeId — un membre peut bumper lastActiveAt de la branche d'une autre org. Utiliser updateMany({where:{id,storeId}}) + 404 (pattern déjà présent ailleurs).stores/[storeId]/branches/route.ts:144-147
N17Bug fonctionnel Custom App / MCP key : l'écriture pose metadata.type="custom-app" (tiret) mais le générateur de clé exige "custom_app" (underscore) → génération de clé MCP cassée (« Connect Custom App first ») pour toute connexion légitime ; mis-bucketing aussi dans status-by-store.custom-app/route.ts:134 vs mcp-key/route.ts:81
N18Relay MCP : mutations destructive non gardées — seul critical est refusé (register-tools.ts:200-212), les writes destructifs (productUpdate, priceUpdate, discount/inventory) s'exécutent sans approbation pour tout client OAuth externe. Inverser en allowlist.register-tools.ts:200-212
N19Extraction mémoire sans scrubbing PII : emails/téléphones/noms (y compris WhatsApp d'expéditeurs arbitraires) persistés comme facts et ré-injectés dans les prompts.memory/extract.ts
N20Cap mid-stream aveugle aux sub-agents : handler-cost-guard.ts:64-108 ne compte que le stream parent ; les generateText spécialistes ont leur propre AbortController (delegate-tool.ts:206), jamais relié au cost-guard → un tour team peut dépasser le solde / MAX_CONVERSATION_USD.handler-cost-guard.ts, delegate-tool.ts:215
N21Divers : _debug (userId/orgId) sur 403 (custom-app/route.ts:78) ; status/route.ts:84-93 métadonnées connexion sans auth (UUID-bearer, pas de secret) ; console.log non-leveled sur le hot path OAuth/email (identifiants seulement, pas de secret) ; Order.stripeTransferId sans @unique (idempotence Stripe seule) ; outils run* (aeo/aov/vitals) sans canWrite ; pool pg non configuré (database.ts:55) ; noUncheckedIndexedAccess absent du tsconfig ; image *.myshopify.com sans pathname.(multi)

Risque structurel reporté (confirmé ouvert, déjà documenté)

  • Payout vendeur immédiat vs fenêtre dispute 14 j (M3) : le transfer Connect part au checkout.session.completed (webhooks.ts:719) ; le reversal au refund est best-effort (refunds.ts:102-117) → si le vendeur a déjà retiré son solde Connect, la plateforme paie le refund. Pas d'escrow jusqu'à fin de fenêtre.
  • Webhook Stripe 500 sur échecs permanents (metadata manquante) → retries Stripe ~3 j (webhooks/stripe/route.ts:64-70) ; pas de perte d'argent (StripeEvent idempotent) mais bruit. Distinguer permanent (200+DLQ) de transitoire.
  • next-auth v4 sur Next 16 / React 19 : matrice non supportée ; migration Auth.js v5 planifiée (docs/ops/security-roadmap-2026-q3.md §2).
  • Tables sans pruning : ListingEvent, CronExecution, Scan/ScanArtifact (en plus de N10/N11).
  • Suppression org/compte : cascade ~100 tables en $transaction au timeout défaut 5 s (delete-account/route.ts:48) → P2028 sur grosse org.

Plan d'action priorisé

P0 — argent (les garde-fous doivent couvrir tous les chemins, pas juste le chat web)

  1. H-BILL-5 : étendre reserveCredits (advisory lock) aux sub-agents délégués, canaux API/WhatsApp et EVI, c'est l'exposition de surcharge la plus directe.
  2. H-SEC-7 / H-BILL-4 : auth + gate crédits + estimate pré-stream + cap mid-stream sur EVI et les canaux ; rate-limit l'API channel.
  3. N20 : relier le cost-guard parent aux spécialistes (propagation de l'abort).
  4. M3 : différer le payout vendeur post-fenêtre dispute (ou garantir le reverse_transfer).

P1 — sécurité / fuite / injection 5. N5 (Notion) : exiger une session, lier au tenant via IntegrationConnection chiffré (comme les autres connecteurs). 6. H-SEC-5 / N6 : select whitelist sur organizations/[orgId]/export + me/export (retirer Store.apiKey + ids Stripe). 7. H-SEC-8 : masquer l'anonymat au data-layer (toCardData/search/recommendations), slug opaque, exclure du profil public. 8. C3.4 / N1 / N8 / N9 / N18 / N19 : compléter fenceUntrusted() sur tous les ingress (DOM navigateur, intelligence concurrents, RAG, facts mémoire, Shopify Admin, scan/docs) ; canWrite/canRead sur browser/commerce/run* tools ; allowlist sur le relay MCP ; scrubbing PII mémoire. 9. N16 : binder themeBranch.update à storeId (updateMany+404).

P2 — base de données / intégrité 10. N2 : rendre explicite onDelete sur Order.listing (Restrict assumé ou SetNull). 11. N7 : borner les Zod money au plafond int4 (ou migrer en BigInt/Decimal). 12. N3 / #4-FK : index tierExpiresAt, Dispute.resolvedById, SavedListing.listingId. 13. N10 / N11 / N12 : FK réelles ou purge explicite MemoryEvent ; rétention StripeEvent.payload ; redaction Message.content des convos non-possédées. 14. Pool pg configuré ; timeout sur la transaction de suppression cascade.

P3 — ops / frontend / dette 15. N4 / H-OPS-2 : job build en CI (la garantie sur laquelle repose ignoreBuildErrors). 16. H-OPS-3 / N14 : garde VERCEL_ENV sur push/heal ; --max-warnings 0 (ou promouvoir les règles clés) ; timeout sur le fetch proxy. 17. N13 : remplacer force-dynamic par revalidate sur les 16 pages marketing ; résoudre la contradiction generateStaticParams. 18. SEO : émettre howToJsonLd ; compléter le sitemap (/status, /sell, /sellers, détails community). 19. N15 : tokens couleur dashboard + i18n (ou assumer le dark/anglais-only). 20. Custom App spelling (N17), _debug 403, retirer oneLight.


Ce qui reste remarquablement solide

  1. Mutation IA bien défendue : C3.1-3 corrigés, autonomie role-gated server-side, wrapper wrap-tools-with-autonomy backstop sur chaque execute, accès store cross-tenant bloqué en amont (C1).
  2. Comptabilité crédits : FIFO par lots en forme close, hold/usage atomiques, settlement exactly-once.
  3. Idempotence Stripe : machine d'états StripeEvent, plan résolu par price id, grants tax-exclusive, clawback en delta, flip PAID conditionnel sur PENDING.
  4. Tenant-auth : getStoreAccess/getOrgAccess centralisés et appliqués partout ; pattern anti-IDOR explicite (keys/[keyId], intelligence/api-keys), réponses secrets en booléens hasX.
  5. Crons : 47/47 derrière withCronAuth, mappés 1:1 à vercel.json, maxDuration partout, stampede résolu.
  6. Outillage : typecheck/lint/test/knip verts, 0 vulnérabilité prod, 0 secret commité, .env.example template propre.
  7. Code mort : knip en CI bloquant, l'audit dead-code (PR #274) tient (allowlist vide hors guard généré + vendor ai-elements).