home / skills / hoangnguyen0403 / agent-skills-standard / security-isolation

security-isolation skill

/skills/nestjs/security-isolation

This skill enforces strict multi-tenant isolation using PostgreSQL RLS and centralized validation to protect child data.

npx playbooks add skill hoangnguyen0403/agent-skills-standard --skill security-isolation

Review the files below or copy the command above to add this skill to your agents.

Files (4)
SKILL.md
2.2 KB
---
name: Data Isolation & RLS Security
description: Standards for enforcing multi-tenant isolation and PostgreSQL Row Level Security (RLS) across child-centric domains.
metadata:
  labels: [security, rls, isolation, authorization, postgresql]
  triggers:
    files: ['src/modules/**', 'SECURITY.md', 'src/migrations/**']
    keywords: [RLS, Row Level Security, childId, isolation, access policy]
---

## **Priority: P0 (CRITICAL)**

Strict multi-tenant isolation. All child-centric data must be secured via PostgreSQL RLS and service-level validation.

## RLS Enforcement Workflow

1. **Migration**: Create tables with `ENABLE ROW LEVEL SECURITY`. Define policies using `current_setting('app.current_user_id')`.
2. **Entity Logic**: Add `@Security` JSDoc to the entity class.
3. **Security Doc**: Update `SECURITY.md` with the new table and its access logic.
4. **Service Validation**: Call `childrenService.validateChildAccess(childId, userId)` before any persistence operation.

## Core Guidelines

1. **Mandatory RLS**: Every new table linking to a `child` or `family` MUST have RLS enabled in its creation migration.
2. **Centralized Validation**: Never reimplement access logic. Use `ChildrenService` for child/family membership checks.
3. **Traceable Security**: `SECURITY.md` is the source of truth. Any change to RLS policies must be reflected there immediately.
4. **Nested Route Constraint**: Data isolation is enforced at the controller level via nested routes: `/children/:childId/...`.
5. **No Direct Entity exposure**: Use Response DTOs to prevent leaking internal database IDs or metadata that could bypass security filters.

## Anti-Patterns

- **No Public Tables**: Don't create child-linked tables without RLS.
- **No Manual Policy Checks**: Don't write raw SQL access checks in services. Use the centralized validator.
- **No Stale Docs**: Don't merge RLS changes without updating `SECURITY.md` and entity JSDoc.
- **No Root IDs**: Don't use `/domain/:id` for child data. Always scope by `:childId`.

## Reference & Examples

- [Implementation Patterns](references/implementation-patterns.md)
- [RLS Migration Patterns](references/rls-patterns.md)
- [Centralized Auth Logic](references/auth-logic.md)

Overview

This skill defines standards for enforcing strict multi-tenant isolation and PostgreSQL Row Level Security (RLS) across child-centric domains. It prescribes migration patterns, service-level validation, and documentation practices to prevent unauthorized cross-tenant data access. The goal is predictable, auditable access rules and a single source of truth for child/family checks.

How this skill works

Migrations must create tables with ENABLE ROW LEVEL SECURITY and policies that reference current_setting('app.current_user_id') to bind DB queries to the active user context. Entities carry @Security JSDoc annotations and all access logic delegates to a centralized ChildrenService (childrenService.validateChildAccess) before persistence. SECURITY.md documents each table’s policies so policy changes are immediately traceable. Controllers expose data through nested routes (/children/:childId/...) and Response DTOs to avoid leaking internal identifiers.

When to use it

  • Any new table that links to a child or family record
  • When adding persistence or update paths that could affect child-scoped data
  • When introducing endpoints that read or write child-related resources
  • During schema migrations that alter child-linked tables
  • When auditing or changing RLS policies

Best practices

  • Enable RLS in the creation migration for every child-linked table
  • Use current_setting('app.current_user_id') in RLS policies to enforce DB-level scoping
  • Always call childrenService.validateChildAccess(childId, userId) before create/update/delete operations
  • Keep SECURITY.md updated to reflect policy and entity access changes — it is the source of truth
  • Expose results via Response DTOs and never return raw entity objects or internal IDs from controllers
  • Implement nested routes (/children/:childId/...) to ensure controller-level scoping and simpler authorization checks

Example use cases

  • Create a new appointments table: migration enables RLS and includes policy using current_setting('app.current_user_id')
  • Add a health-record update endpoint: controller calls childrenService.validateChildAccess before saving
  • Refactor an endpoint that returned raw entities: replace with Response DTOs to avoid exposing internal IDs
  • Audit RLS changes: update SECURITY.md and the entity @Security JSDoc in the same PR
  • Add a cross-team feature that reads child data: enforce centralized ChildrenService checks rather than ad-hoc SQL policies

FAQ

What if I need a public table with child-related metadata?

Avoid public child-linked tables. If truly necessary, separate public metadata into a table with no child FK and enforce association logic at the service layer while preserving RLS on any table that contains child identifiers.

Can I implement access checks directly in SQL within services?

No. Centralize checks through ChildrenService and RLS policies. Manual SQL checks create duplication, risk divergence, and bypass the single source of truth for authorization.