Synthèse automatique
Cette compétence garantit que toutes les requêtes MongoDB incluent obligatoirement le champ `communityId` extrait du token JWT, afin de prévenir les fuites de données entre locataires.
Points forts
- Piste claire pour extraire le communityId depuis le token JWT et le propager jusqu'aux repositories.
- Liste détaillée des patterns corrects pour les méthodes find, update, delete, et les index.
- Identifie et interdit explicitement les anti-patterns courants comme les requêtes globales ou le communityId optionnel.
Limites
- Ne couvre pas la gestion des accès inter-communautés légitimes (par ex. administrateur système).
- Suppose que le JWT contient toujours un communityId valide, sans cas de test ou fallback.
- N'aborde pas les performances ou l'impact sur les index composés.
Lors du développement de nouvelles routes, services ou repositories dans une application NestJS multi-locataire utilisant MongoDB.
Pour des projets mono-locataire, des APIs publiques sans isolation par communauté, ou si le mécanisme d'authentification ne repose pas sur JWT.
Analyse de sécurité
SûrThis skill provides code-level guidelines for enforcing multi-tenancy in MongoDB queries. It does not instruct any dangerous operations like file deletion, network calls, or data exfiltration. It is purely instructional text with example TypeScript code.
Aucun point d'attention détecté
Comment cette analyse est faiteExemples
Write a repository method for Payment model that finds payments by household ID, ensuring multi-tenancy by including communityId from the JWT context.The following repository method lacks multi-tenancy enforcement: `async findAll() { return this.model.find({}); }`. Rewrite it to include communityId from the token.This Payment schema index only has `householdId`. Update it to be a compound index with communityId first for query efficiency.name: tenancy-enforcer description: Use this skill when writing MongoDB queries, repository methods, or service logic in Aegis. It enforces strict multi-tenancy by ensuring communityId is always included.
Aegis Multi-Tenancy Enforcer
When This Skill Applies
- Writing ANY MongoDB query (find, update, delete)
- Creating repository methods
- Implementing service layer logic
- Adding new indexes to schemas
- Creating aggregate pipelines
The Absolute Rule
Every database query MUST include communityId in the filter.
No exceptions. Cross-community access is a security breach.
Source of communityId
Correct: From JWT Token
// Controller extracts from token
@Get()
async findAll(@CurrentUser() user: JwtPayload) {
return this.service.findAll(user.communityId);
}
// Service passes to repository
async findAll(communityId: string) {
return this.repository.findAll(communityId);
}
// Repository includes in query
async findAll(communityId: string) {
return this.model.find({
communityId: new Types.ObjectId(communityId),
});
}
Wrong: From Request Body
// NEVER DO THIS
@Post()
async create(@Body() dto: CreateDto) {
// dto.communityId could be forged by attacker
return this.service.create(dto.communityId, dto);
}
Repository Method Patterns
Find Methods
// Always require communityId as parameter
async findById(id: string, communityId: string): Promise<Document | null> {
return this.model.findOne({
_id: new Types.ObjectId(id),
communityId: new Types.ObjectId(communityId), // REQUIRED
});
}
async findByHousehold(householdId: string, communityId: string): Promise<Document[]> {
return this.model.find({
householdId: new Types.ObjectId(householdId),
communityId: new Types.ObjectId(communityId), // REQUIRED
});
}
Update Methods
async updateById(
id: string,
communityId: string,
data: UpdateDto,
): Promise<Document | null> {
return this.model.findOneAndUpdate(
{
_id: new Types.ObjectId(id),
communityId: new Types.ObjectId(communityId), // REQUIRED in filter
},
{ $set: data },
{ new: true },
);
}
Delete Methods
async deleteById(id: string, communityId: string): Promise<boolean> {
const result = await this.model.deleteOne({
_id: new Types.ObjectId(id),
communityId: new Types.ObjectId(communityId), // REQUIRED
});
return result.deletedCount > 0;
}
Common Anti-Patterns (FORBIDDEN)
Global Queries
// WRONG - No communityId filter
async findAll(): Promise<Document[]> {
return this.model.find({}); // SECURITY BREACH
}
// WRONG - ID-only lookup
async findById(id: string): Promise<Document | null> {
return this.model.findById(id); // Can access ANY community's data
}
Optional communityId
// WRONG - communityId should never be optional
async findById(id: string, communityId?: string): Promise<Document | null> {
const filter: any = { _id: new Types.ObjectId(id) };
if (communityId) {
filter.communityId = new Types.ObjectId(communityId);
}
return this.model.findOne(filter); // Dangerous when omitted
}
Trusting Request Body
// WRONG - Request body can be forged
@Post()
async create(@Body() dto: CreatePaymentDto) {
return this.paymentService.create({
...dto,
communityId: dto.communityId, // Attacker can set any communityId
});
}
// CORRECT - Use JWT context
@Post()
async create(
@Body() dto: CreatePaymentDto,
@CurrentUser() user: JwtPayload,
) {
return this.paymentService.create({
...dto,
communityId: user.communityId, // From authenticated token
});
}
Index Patterns
All compound indexes MUST have communityId as the first field for query efficiency and logical isolation.
// CORRECT - communityId first
@Schema()
export class Payment {
// ...
}
PaymentSchema.index({ communityId: 1, householdId: 1 });
PaymentSchema.index({ communityId: 1, state: 1 });
PaymentSchema.index({ communityId: 1, householdId: 1, billingPeriodStart: 1 }, { unique: true });
// WRONG - communityId not first
PaymentSchema.index({ householdId: 1, communityId: 1 }); // Query won't use index efficiently
PaymentSchema.index({ state: 1 }); // Global index, no tenant isolation
Aggregate Pipeline Rules
// CORRECT - $match with communityId as first stage
async aggregateByHousehold(communityId: string) {
return this.model.aggregate([
{
$match: {
communityId: new Types.ObjectId(communityId), // FIRST stage
},
},
{
$group: {
_id: '$householdId',
total: { $sum: '$amount' },
},
},
]);
}
// WRONG - Missing communityId or not first
async aggregateAll() {
return this.model.aggregate([
{ $group: { _id: '$householdId', total: { $sum: '$amount' } } }, // No tenant filter
]);
}
Service Layer Enforcement
Services should always require communityId from controllers, never have default values or lookups.
// CORRECT - Explicit communityId parameter
@Injectable()
export class PaymentService {
async findByHousehold(householdId: string, communityId: string) {
return this.repository.findByHousehold(householdId, communityId);
}
}
// WRONG - Fetching communityId from related entity
@Injectable()
export class PaymentService {
async findByHousehold(householdId: string) {
const household = await this.householdService.findById(householdId);
// What if household doesn't exist? What if wrong community?
return this.repository.findByHousehold(householdId, household.communityId);
}
}
Exception: Community Collection
The Community collection itself does not have a communityId field - it IS the tenant root. When querying communities:
// Community queries use _id directly (admin operations only)
async findCommunityById(id: string): Promise<Community | null> {
return this.communityModel.findById(id);
}
Verification Checklist
Before committing any repository/service code:
- [ ] Every
find()call includescommunityIdin filter - [ ] Every
findOne()call includescommunityIdin filter - [ ] Every
updateOne()/updateMany()includescommunityIdin filter - [ ] Every
deleteOne()/deleteMany()includescommunityIdin filter - [ ] All aggregate pipelines have
$matchwithcommunityIdas first stage - [ ]
communityIdcomes from JWT/controller, not request body - [ ] No methods have optional
communityIdparameter - [ ] New indexes have
communityIdas first field in compound indexes
Du fichier au premier essai
Le téléchargement contient exactement le contenu affiché sur cette fiche. Cette copie peut différer de la dernière version du dépôt source.
Consulter la source et ses fichiers annexesCe téléchargement contient uniquement SKILL.md. Les scripts, références, modèles et dépendances éventuels ne sont pas inclus. Récupérez les fichiers nécessaires depuis la source en conservant leur arborescence avant de poursuivre.
1. Télécharger le fichier
Télécharger SKILL.mdDepuis la racine d’un projet de test, enregistrez le fichier sous .skill-downloads/aegis-multi-tenancy-enforcer-mmio70nb/SKILL.md, ou utilisez la commande suivante. Ce dossier sert à examiner la skill avant son installation.
Commande pour macOS/Linux ou WSL : un shell POSIX et curl sont nécessaires. Sous Windows sans WSL, utilisez le bouton de téléchargement et créez le dossier manuellement.
mkdir -p '.skill-downloads/aegis-multi-tenancy-enforcer-mmio70nb' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/aegis-multi-tenancy-enforcer-mmio70nb/download' --output '.skill-downloads/aegis-multi-tenancy-enforcer-mmio70nb/SKILL.md.part' && mv '.skill-downloads/aegis-multi-tenancy-enforcer-mmio70nb/SKILL.md.part' '.skill-downloads/aegis-multi-tenancy-enforcer-mmio70nb/SKILL.md'2. Vérifier les prérequis
Lisez SKILL.md avant de l’utiliser. Si le fichier cite des scripts ou services externes, examinez-les et préparez les accès nécessaires dans votre environnement de test.
Aucune section de prérequis reconnue dans ce fichier. Cela ne signifie pas qu’il est autonome : les bibliothèques, outils et accès peuvent être cités ailleurs dans le contenu ou dans le dépôt source.
Les plateformes de la fiche sont des indications du catalogue. Les chemins ci-dessous suivent la documentation des outils ; ils ne prouvent pas que cette skill y a été exécutée. Aucune version minimale de ces outils n’est attestée pour cette fiche.
3. Charger la skill
Après vérification, copiez le dossier complet dans le projet avec l’une des commandes ci-dessous, puis ouvrez une nouvelle session de l’outil. La commande s’arrête si un dossier du même nom existe déjà : comparez les fichiers avant de le remplacer.
Claude Code
test -f '.skill-downloads/aegis-multi-tenancy-enforcer-mmio70nb/SKILL.md' && test -s '.skill-downloads/aegis-multi-tenancy-enforcer-mmio70nb/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/tenancy-enforcer' && cp -R '.skill-downloads/aegis-multi-tenancy-enforcer-mmio70nb' '.claude/skills/tenancy-enforcer'Cursor
test -f '.skill-downloads/aegis-multi-tenancy-enforcer-mmio70nb/SKILL.md' && test -s '.skill-downloads/aegis-multi-tenancy-enforcer-mmio70nb/SKILL.md' && mkdir -p '.cursor/skills' && test ! -e '.cursor/skills/tenancy-enforcer' && cp -R '.skill-downloads/aegis-multi-tenancy-enforcer-mmio70nb' '.cursor/skills/tenancy-enforcer'OpenSkills
OpenSkills doit déjà être installé. La procédure locale install/read est contrôlée avec OpenSkills 1.5.0, qui exige Node.js ≥ 20.6.0. Aucun slug du catalogue n’est résolu automatiquement.
Installation officielle d’OpenSkillsopenskills install './.skill-downloads/aegis-multi-tenancy-enforcer-mmio70nb'openskills read 'aegis-multi-tenancy-enforcer-mmio70nb'La lecture doit afficher le contenu du fichier installé et son dossier de base. Elle ne lance ni la skill ni ses dépendances.
Pour un agent qui lit AGENTS.md, synchronisez ensuite l’index. Cette commande modifie AGENTS.md : relisez le diff avant de l’utiliser.
openskills syncMode manuel ou autre outil : joignez SKILL.md à une conversation, ou ouvrez-le dans un agent capable de lire les fichiers. Ajoutez les ressources annexes nécessaires. Il n’existe pas de dossier universel reconnu par tous les outils.
4. Faire un premier essai
Utilisez une entrée courte et fictive : un endpoint d’API, un petit document ou un brief d’interface. Dans une nouvelle conversation, joignez le fichier ou donnez son chemin à l’agent, puis adaptez cette demande :
Lis .skill-downloads/aegis-multi-tenancy-enforcer-mmio70nb/SKILL.md et applique ses instructions à cet exemple : [décrire une entrée de test]. Indique les fichiers, dépendances ou accès qui manquent. Produis le résultat demandé par la skill sans inventer de données ni exécuter de script non examiné.Résultat attendu : une sortie correspondant aux instructions de la skill, ou une liste précise des prérequis manquants. Une simple confirmation de lecture ne valide pas le premier usage. Si une ressource externe est indisponible, ce parcours reste à tester.
Bonnes pratiques React Hook Form
Developpement
Maîtrisez React Hook Form : évitez les anti-patterns, gérez les abonnements, et traitez correctement reset, dirty state et entrées contrôlées.
Migration Emotion vers Mantine
Developpement
Migrer les styled-components Emotion vers des composants Mantine avec style props et modules CSS. Pour convertir .styled.tsx ou supprimer les imports @emotion.
Détecteur de dérive de périmètre
Developpement
Analyse les diffs git par rapport à une intention pour détecter la dérive de périmètre, fichiers non liés et PR trop larges, puis suggère de garder, diviser ou justifier.