Aller au contenu

Sécurité

Chaque endpoint déclare qui peut l'appeler, chaque espace de travail n'atteint que ses propres données, et jetons et webhooks sont vérifiés avant tout. Limites de débit, HTTPS, sauvegardes nocturnes et alertes sont prêts pour la production.

En bref

  • Un endpoint sans ses guards fait échouer le build.
  • Un client ne voit jamais les données d'un autre, et les tests le prouvent.
  • Prêt pour la production : une checklist suffit pour passer en ligne.

Chaque endpoint dit qui peut l'appeler

Chaque query, mutation et route déclare un guard d'authentification et un guard de permission. « Public » et « aucune permission requise » s'écrivent aussi en toutes lettres, pour ressortir en revue. Un guard manquant est une erreur de lint.

  • La connexion est vérifiée une fois, dans un middleware ; les resolvers ne lisent jamais de jeton
  • Déconnecté donne UNAUTHENTICATED, une permission manquante donne FORBIDDEN
packages/app-server/src/modules/project/project.resolver.ts
@Resolver(() => ProjectDto)@UseGuards(WorkspaceAuthGuard)@UseFilters(ProjectGraphqlApiExceptionFilter)export class ProjectResolver {  constructor(private readonly projectService: ProjectService) {}   @Query(() => [ProjectDto])  @UseGuards(NoPermissionGuard)  async projects(   @Mutation(() => ProjectDto)  @UseGuards(WorkspacePermissionGuard('org:projects:create'))  async createProject(

Les espaces de travail ne se croisent pas

Les données d'un espace de travail ne sont lues et écrites que via un repository qui ajoute l'espace de travail à chaque requête. Il refuse un espace de travail écrit dans la requête, une ligne déplacée vers un autre espace, et un upsert qui pourrait tomber sur la ligne d'un autre espace.

  • L'espace de travail vient toujours du membre connecté, jamais de la requête
  • Une règle de lint refuse tout autre accès aux tables d'un espace de travail
  • Les tests de l'API vérifient qu'un autre espace de travail obtient « introuvable » pour chaque action
packages/app-server/src/engine/workspace-scoped-repository/workspace-scoped-repository.ts
// The only way services read or write tenant-owned data. Every method takes// the workspaceId first and adds it to the query itself, so forgetting// `where: { workspaceId }` is impossible and a caller can't override it.export class WorkspaceScopedRepository<TEntity extends WorkspaceRelatedEntity> {  constructor(private readonly repository: Repository<TEntity>) {}   async find(    workspaceId: string,    options: FindManyOptions<TEntity> = {},  ): Promise<TEntity[]> {    return this.repository.find({      ...options,      where: this.mergeWorkspaceIdIntoWhere(workspaceId, options.where),    });  }

Les jetons sont vérifiés, pas crus sur parole

Clerk détient les mots de passe, les sessions et la validation en deux étapes : votre base ne stocke jamais d'identifiants de connexion. Le serveur vérifie chaque jeton de session avec l'outil officiel de Clerk et la clé publique de votre instance, sans appel réseau.

  • Noms et e-mails sont lus chez Clerk par le serveur, jamais repris du navigateur
  • Les permissions découlent du rôle du membre, dans votre code
Une requête à l'API
Authorization: Bearer eyJhbGciOiJSUzI1NiIs…

Ce que le serveur vérifie avant tout

  1. Signé avec la clé de votre instance Clerk, vérifié sans appel réseau
  2. Émis pour l'adresse de votre app web
  3. Encore valide : les jetons vivent 60 secondes
  4. Les identifiants Clerk remplacés par les vôtres
Connecté en tant que Ada Lovelace, admin de Acme team

Les webhooks sont vérifiés avant tout

Stripe et Clerk signent leurs webhooks. Le serveur vérifie la signature sur les octets exacts reçus, puis enregistre l'événement une seule fois dans une table de réception : un événement rejoué n'est traité qu'une fois.

  • Un forfait ne change que sur un webhook signé de Stripe
  • Votre base ne stocke ni carte, ni adresse, ni numéro fiscal, ni facture
  • Un cron reprend tout événement laissé en route, et vous alerte si l'un d'eux échoue sans cesse
Ce que répondent les endpoints de webhooks
RequêteRéponseCe qui se passe
Signature valide200Enregistré une fois, traité par le worker
Le même événement, à nouveau200Pas traité deux fois
Signature absente ou fausse400Rien n'est enregistré
Fournisseur désactivé404Rien n'est enregistré

Les abus sont plafonnés avant de vous coûter

Le débit des actions sensibles est limité, avec des compteurs dans Redis : un script ou un bouton bloqué ne peut pas submerger Stripe, Clerk ou votre base de données. Les requêtes GraphQL démesurées sont refusées avant toute exécution, et en production l'API ne décrit son schéma à personne.

  • Au-delà d'une limite, on lit « Trop de tentatives », dans sa langue ; les expéditeurs de webhooks reçoivent un 429 et reviennent plus tard
  • La protection anti-bots de Clerk vérifie chaque inscription
Les limites de débit du kit de démarrage
ActionCompté parAutorisé
Ouvrir Stripe Checkout ou le portail de facturationEspace de travail10 par minute chacune
Renommer l'espace de travailEspace de travail10 par minute
Créer un projetPersonne30 par minute
Webhooks Stripe et ClerkAdresse IP600 par minute chacun

Une production renforcée et surveillée

Tout le produit tourne sur un serveur avec Docker. Seul le proxy est joignable : il obtient lui-même les certificats HTTPS et envoie des en-têtes de sécurité stricts. Les migrations tournent à chaque déploiement, la base de données est sauvegardée chaque nuit, et vous êtes alerté quand quelque chose casse.

  • Les conteneurs de l'app tournent sans root, sur un système de fichiers en lecture seule
  • Logs JSON avec un identifiant de requête ; alertes par e-mail via Sentry, litiges de paiement compris
  • Les secrets que vos fonctionnalités stockent sont chiffrés en AES-256-GCM

Installations renforcées, secrets hors de git

Yarn ignore les scripts d'installation des paquets téléchargés, le moyen de propagation le plus courant des logiciels malveillants npm, et refuse les versions de moins de 3 jours. Le serveur valide sa configuration au démarrage, et les secrets ne vivent que dans des fichiers ignorés par git.

  • En production, le serveur refuse de démarrer avec la connexion de remplacement, sans limites de débit ou sans sa clé de chiffrement
  • Les erreurs inattendues arrivent au navigateur en « Internal Server Error », sans détails
  • La CI installe depuis un lockfile qui ne peut pas changer pendant son exécution
.yarnrc.yml
enableConstraintsChecks: true enableHardenedMode: true enableScripts: false nodeLinker: node-modules npmMinimalAgeGate: 3d

Questions

Est-il prêt pour la production ?

Oui. Limites de débit, limites de requêtes, en-têtes de sécurité, HTTPS automatique, sauvegardes nocturnes, logs et alertes sont intégrés. Suivez le guide de déploiement, puis la checklist qui clôt le guide de sécurité : clés de production, domaines, instance de production Clerk et sa protection anti-bots, Stripe en mode live, sauvegardes copiées hors du serveur.

Où sont stockées les données de paiement ?

Chez Stripe. Votre base garde l'état de l'abonnement, jamais les cartes, les adresses, les numéros fiscaux ni les factures.

Un assistant IA peut-il modifier la production ?

Les règles disent non : les assistants ont un accès en lecture seule à la base, et la production ne change que par du code relu, livré par le déploiement normal.

Et si j'ajoute l'envoi de fichiers ou des e-mails ?

Ils ne sont pas inclus : ajoutez-les quand votre produit en a besoin. Les règles de sécurité disent déjà comment les protéger (fichiers sûrs, HTML d'e-mail nettoyé, adresses vérifiées), et votre assistant IA suit ces règles quand il construit la fonctionnalité.

Démarrez votre prochaine app sur une base propre

Payez une fois et obtenez tout le code source, avec toutes les mises à jour futures.