Security Roadmap — Q3 2026
Items identifies par l'audit complet du 2026-05-12. Les Critical / High ont ete shippes (frame-src 'self', session-auth migration sur 8 endpoints, cron timing-safe, withInternalAuth prod guard, vercel.json CSP align…
Items identifies par l'audit complet du 2026-05-12. Les Critical / High ont ete shippes (frame-src 'self', session-auth migration sur 8 endpoints, cron timing-safe, withInternalAuth prod guard, vercel.json CSP align, proxy preview headers extension, org-config Data Cache).
Ce doc trace les items effort L restants : pas urgents mais qui elevent la securite / observabilite vers le baseline Vercel 2026+.
1. Vercel Firewall + BotID : Effort S (config dashboard)
Pourquoi
Pas de WAF edge configure aujourd'hui. Le rate-limit applicatif (Upstash) tourne apres l'edge, donc un attaquant peut saturer la latence Lambda avant qu'on reject. BotID + managed rules attrappent les bots avant la function.
Et il ne compte plus par IP partout, ce qui deplace la frontiere plutot
que de la fermer. Les routes authentifiees par session comptent contre
l'utilisateur depuis security-identity/0364 : l'IP y etait lue en
position 0 de x-forwarded-for, donc choisie par l'appelant, donc un
seau neuf a chaque requete. Un attaquant non authentifie reste
compte sur l'IP de confiance (dernier saut), et c'est precisement le
trafic que cette section propose de bloquer avant la function : le
limiteur applicatif ne peut pas etre la premiere ligne contre lui, il
n'a d'identite fiable qu'apres la session.
Plan
Etape 1 — activer Vercel Firewall (Dashboard → Settings → Firewall)
- Active
OWASP Top 10 Managed Rules(free tier inclus) - Active
Block Bad Bots - Optionnel : ajout custom rules pour
/api/auth/*(max 30/min/IP)
Etape 2 — integrer @vercel/botid
pnpm add @vercel/botid
// src/proxy.ts — apres `recordVisit`
import { botid } from "@vercel/botid"
const decision = await botid(req)
if (decision.isBot) {
return new NextResponse(null, { status: 403 })
}
Cibles prioritaires : /auth, /api/marketplace/listings/* POST,
/api/credits/redeem. Pas sur les endpoints publics SEO (blog,
sitemap) : googlebot doit passer.
Refs
2. La bibliotheque d'authentification apres next-auth v4 — decision, pas migration
Ce que cette section disait, et pourquoi c'etait faux
Elle planifiait une migration en cinq phases vers Auth.js v5, sur la
phrase « Auth.js v5 est GA depuis fin 2024, recommande par Vercel pour
Next 16 ». La contradiction etait dans la section elle-meme : sa phase 1
disait pnpm add next-auth@beta. On n'installe pas en @beta ce qui est
GA.
Les faits, verifies contre le registre npm le 2026-09-06 :
next-auth:dist-tags.latest = 4.24.15(publie le 2026-07-20),dist-tags.beta = 5.0.0-beta.32. v5 n'est pas sortie de beta.- L'annonce officielle
nextauthjs/next-authdiscussion #13252 du 26 septembre 2025 : « Auth.js (formerly NextAuth.js) will now be maintained by the Better Auth team », « We don't have any immediate plans for v5 », et, pour un nouveau projet, « we recommend Better Auth ». - Ce depot est fige sur
next-auth4.24.15 (package.json), un numero desormais derive parpnpm docs:claims: c'est ce qui empeche cette page de re-affirmer une version que le lockfile contredit.
Le risque reel, qui n'etait ecrit nulle part
Ce n'est pas « on a une majeure de retard ». C'est qu'une bibliotheque en
mode maintenance (« security and critical fixes ») porte notre unique
porte de connexion, et que nous la rustinons deja a la main parce que
l'amont ne livre pas dans notre branche : src/modules/auth/normalize.ts
et src/proxy.ts portent chacun un durcissement maison (cf. 0128 /
0130). Chaque avis de securite futur suit ce chemin-la.
Ironie a garder en tete avant de proposer Better Auth comme evidence :
src/modules/auth/index.ts documente que cette codebase a migre DE
Better Auth VERS NextAuth v4.
Ce qu'il faut faire, dans l'ordre
- Ne rien migrer sur cette page. Un plan de migration vers une version en beta est un plan qui ne peut pas etre execute, et il occupe la place de la decision qui, elle, doit etre prise.
- Ouvrir une ADR « bibliotheque d'authentification 2027 » dans
docs/decisions/, avec les trois options reelles et leur cout : rester sur v4 en absorbant les patchs a la main (le statu quo, dont nous connaissons deja le prix) ; Better Auth (l'equipe qui maintient maintenant l'amont, mais un aller-retour) ; une session maison surjose+ Prisma (le moins de surface, le plus de code a nous). - Surveiller le tag
latest.scripts/dependency-watch.mjsle fait deja toutes les semaines et reporte une majeure de retard sans la traiter ; le jour oudist-tags.latestdenext-authpasse a 5.x, c'est le declencheur de l'ADR, pas d'unpnpm add.
Refs
- https://github.com/nextauthjs/next-auth/discussions/13252 (26 sept. 2025)
- https://registry.npmjs.org/next-auth (dist-tags)
3. Playwright e2e — Tenant lateral movement — Effort L
Pourquoi
Notre threat model interdit qu'un user de l'orgA accede aux ressources
de l'orgB. Les wrappers getOrgAccess, getStoreAccess, requireOrg
sont en place mais aucun test e2e ne prouve qu'ils sont appliques
sur chaque endpoint tenant-scoped. Un nouveau endpoint qui oublie
le check passe le code review et reste broken en silence.
Plan
Setup fixture multi-user
// e2e/fixtures/tenants.ts
export const tenantA = {
email: "tenanta@e2e.boostecom.dev",
orgSlug: "tenant-a",
storeSlug: "store-a",
apiKey: process.env.E2E_TENANT_A_API_KEY,
}
export const tenantB = { /* idem */ }
Test suite — chaque endpoint tenant-scoped doit retourner 403/404 quand userA appelle avec orgBId :
test.describe("tenant lateral movement", () => {
const protectedEndpoints = [
{ path: "/api/organizations/{orgId}", method: "PUT" },
{ path: "/api/organizations/{orgId}/rules", method: "PUT" },
{ path: "/api/organizations/{orgId}/memory/metrics", method: "GET" },
{ path: "/api/stores/{storeId}/integrations/{provider}", method: "GET" },
{ path: "/api/stores/{storeId}/export", method: "GET" },
// ... ~30 endpoints au total
]
for (const ep of protectedEndpoints) {
test(`${ep.method} ${ep.path} blocks cross-tenant`, async ({ request }) => {
const url = ep.path
.replace("{orgId}", tenantB.orgId)
.replace("{storeId}", tenantB.storeId)
const res = await request[ep.method.toLowerCase()](url, {
headers: { cookie: tenantA.sessionCookie },
})
expect([403, 404]).toContain(res.status())
})
}
})
CI — run cette suite sur chaque PR qui touche src/app/api/**.
Infra
- Playwright deja install (
@playwright/testdep ? Vérifier) - Fixture DB : seed tenantA / tenantB orgs + stores au setup global
- Cleanup : truncate apres each suite
Refs
4. revalidateTag — Marketplace listings : Effort M (deferre)
Pourquoi (et pourquoi deferre)
listListings() prend un viewerId qui change la query (savedBy
overlay). Cacher direct = cacher des resultats user-specific = leak.
Plan complexe :
- Cacher la query SANS
viewerIdavec un tagmarketplace:list - Faire le
savedByoverlay en memoire apres lecture cache - Invalider depuis
seller-actions.tsapresacceptOffer/rejectOffer/etc.
Pour les pages /marketplace/[type] qui sont publiques (anonymous
viewer), c'est trivial. Pour /marketplace/[type]/[slug] detail,
idem (savedBy n'est pas dans la query principale).
Decision : reporte tant qu'on n'a pas de signal perf (Vercel Speed Insights p95 > 1s sur ces pages). Aujourd'hui le bottleneck est ailleurs.
5. Items secondaires non bloquants
process.env.NODE_ENVdirect danssrc/proxy.ts(3 occurrences) → pas critique mais incoherent avecserverEnv. Les numeros de ligne cites ici jusqu'en 2026-09 (152,175,196) avaient perime : chercher par nom, pas par ligne.- Audit inline scripts
/workspace:1CSP violation : probablement un script injecte par extension Chrome user (BoostEcom Spy / similaire), pas notre code. Verifier via/api/security/csp-reportlogs apres deploy du frame-src fix - Marketplace public
GET /api/registry/skillsreste public : OK mais reviewer une fois qu'un quota est expose
Statut
| Axe | Severity | Status |
|---|---|---|
| Auth wrappers — endpoints dashboard | Critical | ✅ Shippe (commits 8dfebaf0, e15dd235) |
| CSP frame-src 'self' | Critical | ✅ Shippe (43f8db29) |
| Cron timing-safe | High | ✅ Shippe (d843022a) |
| withInternalAuth prod guard | High | ✅ Shippe (8dfebaf0) |
| vercel.json CSP drift | High | ✅ Shippe (8c9b3121) |
| Proxy preview BLOCKING_HEADERS | Medium | ✅ Shippe (8c9b3121) |
| Org config Data Cache | Perf | ✅ Shippe (cd1cee37) |
| Vercel Firewall + BotID | Medium | ⏳ Plan ici (effort S) |
| Bibliotheque d'auth apres v4 | Medium | ⏳ Decision a prendre (ADR), pas une migration |
| Playwright tenant e2e | High | ⏳ Plan ici (effort L) |
| revalidateTag marketplace | Low | ⏳ Deferre (effort M) |