@patricio0312rev/skillset 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/bin/cli.js +37 -0
- package/package.json +55 -0
- package/src/commands/init.js +301 -0
- package/src/index.js +168 -0
- package/src/lib/config.js +200 -0
- package/src/lib/generator.js +166 -0
- package/src/utils/display.js +95 -0
- package/src/utils/readme.js +196 -0
- package/src/utils/tool-specific.js +233 -0
- package/templates/ai-engineering/agent-orchestration-planner/ SKILL.md +266 -0
- package/templates/ai-engineering/cost-latency-optimizer/ SKILL.md +270 -0
- package/templates/ai-engineering/doc-to-vector-dataset-generator/ SKILL.md +239 -0
- package/templates/ai-engineering/evaluation-harness/ SKILL.md +219 -0
- package/templates/ai-engineering/guardrails-safety-filter-builder/ SKILL.md +226 -0
- package/templates/ai-engineering/llm-debugger/ SKILL.md +283 -0
- package/templates/ai-engineering/prompt-regression-tester/ SKILL.md +216 -0
- package/templates/ai-engineering/prompt-template-builder/ SKILL.md +393 -0
- package/templates/ai-engineering/rag-pipeline-builder/ SKILL.md +244 -0
- package/templates/ai-engineering/tool-function-schema-designer/ SKILL.md +219 -0
- package/templates/architecture/adr-writer/ SKILL.md +250 -0
- package/templates/architecture/api-versioning-deprecation-planner/ SKILL.md +331 -0
- package/templates/architecture/domain-model-boundaries-mapper/ SKILL.md +300 -0
- package/templates/architecture/migration-planner/ SKILL.md +376 -0
- package/templates/architecture/performance-budget-setter/ SKILL.md +318 -0
- package/templates/architecture/reliability-strategy-builder/ SKILL.md +286 -0
- package/templates/architecture/rfc-generator/ SKILL.md +362 -0
- package/templates/architecture/scalability-playbook/ SKILL.md +279 -0
- package/templates/architecture/system-design-generator/ SKILL.md +339 -0
- package/templates/architecture/tech-debt-prioritizer/ SKILL.md +329 -0
- package/templates/backend/api-contract-normalizer/ SKILL.md +487 -0
- package/templates/backend/api-endpoint-generator/ SKILL.md +415 -0
- package/templates/backend/auth-module-builder/ SKILL.md +99 -0
- package/templates/backend/background-jobs-designer/ SKILL.md +166 -0
- package/templates/backend/caching-strategist/ SKILL.md +190 -0
- package/templates/backend/error-handling-standardizer/ SKILL.md +174 -0
- package/templates/backend/rate-limiting-abuse-protection/ SKILL.md +147 -0
- package/templates/backend/rbac-permissions-builder/ SKILL.md +158 -0
- package/templates/backend/service-layer-extractor/ SKILL.md +269 -0
- package/templates/backend/webhook-receiver-hardener/ SKILL.md +211 -0
- package/templates/ci-cd/artifact-sbom-publisher/ SKILL.md +236 -0
- package/templates/ci-cd/caching-strategy-optimizer/ SKILL.md +195 -0
- package/templates/ci-cd/deployment-checklist-generator/ SKILL.md +381 -0
- package/templates/ci-cd/github-actions-pipeline-creator/ SKILL.md +348 -0
- package/templates/ci-cd/monorepo-ci-optimizer/ SKILL.md +298 -0
- package/templates/ci-cd/preview-environments-builder/ SKILL.md +187 -0
- package/templates/ci-cd/quality-gates-enforcer/ SKILL.md +342 -0
- package/templates/ci-cd/release-automation-builder/ SKILL.md +281 -0
- package/templates/ci-cd/rollback-workflow-builder/ SKILL.md +372 -0
- package/templates/ci-cd/secrets-env-manager/ SKILL.md +242 -0
- package/templates/db-management/backup-restore-runbook-generator/ SKILL.md +505 -0
- package/templates/db-management/data-integrity-auditor/ SKILL.md +505 -0
- package/templates/db-management/data-retention-archiving-planner/ SKILL.md +430 -0
- package/templates/db-management/data-seeding-fixtures-builder/ SKILL.md +375 -0
- package/templates/db-management/db-performance-watchlist/ SKILL.md +425 -0
- package/templates/db-management/etl-sync-job-builder/ SKILL.md +457 -0
- package/templates/db-management/multi-tenant-safety-checker/ SKILL.md +398 -0
- package/templates/db-management/prisma-migration-assistant/ SKILL.md +379 -0
- package/templates/db-management/schema-consistency-checker/ SKILL.md +440 -0
- package/templates/db-management/sql-query-optimizer/ SKILL.md +324 -0
- package/templates/foundation/changelog-writer/ SKILL.md +431 -0
- package/templates/foundation/code-formatter-installer/ SKILL.md +320 -0
- package/templates/foundation/codebase-summarizer/ SKILL.md +360 -0
- package/templates/foundation/dependency-doctor/ SKILL.md +163 -0
- package/templates/foundation/dev-environment-bootstrapper/ SKILL.md +259 -0
- package/templates/foundation/dev-onboarding-builder/ SKILL.md +556 -0
- package/templates/foundation/docs-starter-kit/ SKILL.md +574 -0
- package/templates/foundation/explaining-code/SKILL.md +13 -0
- package/templates/foundation/git-hygiene-enforcer/ SKILL.md +455 -0
- package/templates/foundation/project-scaffolder/ SKILL.md +65 -0
- package/templates/foundation/project-scaffolder/references/templates.md +126 -0
- package/templates/foundation/repo-structure-linter/ SKILL.md +0 -0
- package/templates/foundation/repo-structure-linter/references/conventions.md +98 -0
- package/templates/frontend/animation-micro-interaction-pack/ SKILL.md +41 -0
- package/templates/frontend/component-scaffold-generator/ SKILL.md +562 -0
- package/templates/frontend/design-to-component-translator/ SKILL.md +547 -0
- package/templates/frontend/form-wizard-builder/ SKILL.md +553 -0
- package/templates/frontend/frontend-refactor-planner/ SKILL.md +37 -0
- package/templates/frontend/i18n-frontend-implementer/ SKILL.md +44 -0
- package/templates/frontend/modal-drawer-system/ SKILL.md +377 -0
- package/templates/frontend/page-layout-builder/ SKILL.md +630 -0
- package/templates/frontend/state-ux-flow-builder/ SKILL.md +23 -0
- package/templates/frontend/table-builder/ SKILL.md +350 -0
- package/templates/performance/alerting-dashboard-builder/ SKILL.md +162 -0
- package/templates/performance/backend-latency-profiler-helper/ SKILL.md +108 -0
- package/templates/performance/caching-cdn-strategy-planner/ SKILL.md +150 -0
- package/templates/performance/capacity-planning-helper/ SKILL.md +242 -0
- package/templates/performance/core-web-vitals-tuner/ SKILL.md +126 -0
- package/templates/performance/incident-runbook-generator/ SKILL.md +162 -0
- package/templates/performance/load-test-scenario-builder/ SKILL.md +256 -0
- package/templates/performance/observability-setup/ SKILL.md +232 -0
- package/templates/performance/postmortem-writer/ SKILL.md +203 -0
- package/templates/performance/structured-logging-standardizer/ SKILL.md +122 -0
- package/templates/security/auth-security-reviewer/ SKILL.md +428 -0
- package/templates/security/dependency-vulnerability-triage/ SKILL.md +495 -0
- package/templates/security/input-validation-sanitization-auditor/ SKILL.md +76 -0
- package/templates/security/pii-redaction-logging-policy-builder/ SKILL.md +65 -0
- package/templates/security/rbac-policy-tester/ SKILL.md +80 -0
- package/templates/security/secrets-scanner/ SKILL.md +462 -0
- package/templates/security/secure-headers-csp-builder/ SKILL.md +404 -0
- package/templates/security/security-incident-playbook-generator/ SKILL.md +76 -0
- package/templates/security/security-pr-checklist-skill/ SKILL.md +62 -0
- package/templates/security/threat-model-generator/ SKILL.md +394 -0
- package/templates/testing/contract-testing-builder/ SKILL.md +492 -0
- package/templates/testing/coverage-strategist/ SKILL.md +436 -0
- package/templates/testing/e2e-test-builder/ SKILL.md +382 -0
- package/templates/testing/flaky-test-detective/ SKILL.md +416 -0
- package/templates/testing/integration-test-builder/ SKILL.md +525 -0
- package/templates/testing/mocking-assistant/ SKILL.md +383 -0
- package/templates/testing/snapshot-test-refactorer/ SKILL.md +375 -0
- package/templates/testing/test-data-factory-builder/ SKILL.md +449 -0
- package/templates/testing/test-reporting-triage-skill/ SKILL.md +469 -0
- package/templates/testing/unit-test-generator/ SKILL.md +548 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: multi-tenant-safety-checker
|
|
3
|
+
description: Ensures tenant isolation at query and policy level using Row Level Security, automated testing, and security audits. Prevents data leakage between tenants. Use for "multi-tenancy", "tenant isolation", "RLS", or "data security".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Multi-tenant Safety Checker
|
|
7
|
+
|
|
8
|
+
Ensure complete tenant isolation and prevent data leakage.
|
|
9
|
+
|
|
10
|
+
## Row Level Security (RLS)
|
|
11
|
+
|
|
12
|
+
### PostgreSQL RLS Setup
|
|
13
|
+
|
|
14
|
+
```sql
|
|
15
|
+
-- Enable RLS on tables
|
|
16
|
+
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
|
17
|
+
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
|
|
18
|
+
ALTER TABLE products ENABLE ROW LEVEL SECURITY;
|
|
19
|
+
|
|
20
|
+
-- Create policy for users table
|
|
21
|
+
CREATE POLICY tenant_isolation_policy ON users
|
|
22
|
+
USING (tenant_id = current_setting('app.tenant_id')::INTEGER);
|
|
23
|
+
|
|
24
|
+
-- Create policy for orders table
|
|
25
|
+
CREATE POLICY tenant_isolation_policy ON orders
|
|
26
|
+
USING (tenant_id = current_setting('app.tenant_id')::INTEGER);
|
|
27
|
+
|
|
28
|
+
-- Create policy for products table
|
|
29
|
+
CREATE POLICY tenant_isolation_policy ON products
|
|
30
|
+
USING (tenant_id = current_setting('app.tenant_id')::INTEGER);
|
|
31
|
+
|
|
32
|
+
-- Force RLS even for table owners
|
|
33
|
+
ALTER TABLE users FORCE ROW LEVEL SECURITY;
|
|
34
|
+
ALTER TABLE orders FORCE ROW LEVEL SECURITY;
|
|
35
|
+
ALTER TABLE products FORCE ROW LEVEL SECURITY;
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Application-Level Tenant Context
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// middleware/tenant-context.ts
|
|
42
|
+
import { PrismaClient } from "@prisma/client";
|
|
43
|
+
|
|
44
|
+
export class TenantContext {
|
|
45
|
+
constructor(private prisma: PrismaClient) {}
|
|
46
|
+
|
|
47
|
+
async setTenant(tenantId: number): Promise<void> {
|
|
48
|
+
await this.prisma.$executeRaw`
|
|
49
|
+
SET LOCAL app.tenant_id = ${tenantId}
|
|
50
|
+
`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async withTenant<T>(
|
|
54
|
+
tenantId: number,
|
|
55
|
+
callback: () => Promise<T>
|
|
56
|
+
): Promise<T> {
|
|
57
|
+
return this.prisma.$transaction(async (tx) => {
|
|
58
|
+
// Set tenant context for this transaction
|
|
59
|
+
await tx.$executeRaw`SET LOCAL app.tenant_id = ${tenantId}`;
|
|
60
|
+
|
|
61
|
+
// Execute queries within tenant context
|
|
62
|
+
return callback();
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Usage in API route
|
|
68
|
+
app.get("/api/orders", async (req, res) => {
|
|
69
|
+
const tenantId = req.user.tenantId;
|
|
70
|
+
|
|
71
|
+
const orders = await tenantContext.withTenant(tenantId, async () => {
|
|
72
|
+
return prisma.order.findMany(); // Automatically filtered by RLS
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
res.json(orders);
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Tenant Isolation Checklist
|
|
80
|
+
|
|
81
|
+
```markdown
|
|
82
|
+
# Multi-tenant Security Checklist
|
|
83
|
+
|
|
84
|
+
## Database Level
|
|
85
|
+
|
|
86
|
+
- [ ] All tables have `tenant_id` column
|
|
87
|
+
- [ ] `tenant_id` is NOT NULL on all tables
|
|
88
|
+
- [ ] Foreign keys include tenant_id checks
|
|
89
|
+
- [ ] Row Level Security enabled on all tables
|
|
90
|
+
- [ ] RLS policies created for all tables
|
|
91
|
+
- [ ] RLS enforced even for table owners
|
|
92
|
+
- [ ] Composite indexes include tenant_id
|
|
93
|
+
|
|
94
|
+
## Application Level
|
|
95
|
+
|
|
96
|
+
- [ ] Tenant context set on every request
|
|
97
|
+
- [ ] Tenant ID validated from JWT/session
|
|
98
|
+
- [ ] No raw SQL without tenant filter
|
|
99
|
+
- [ ] All queries include tenant_id (if no RLS)
|
|
100
|
+
- [ ] API endpoints validate tenant access
|
|
101
|
+
- [ ] File uploads scoped to tenant
|
|
102
|
+
- [ ] Background jobs include tenant context
|
|
103
|
+
|
|
104
|
+
## Testing
|
|
105
|
+
|
|
106
|
+
- [ ] Cross-tenant query tests
|
|
107
|
+
- [ ] RLS bypass attempt tests
|
|
108
|
+
- [ ] SQL injection with tenant bypass tests
|
|
109
|
+
- [ ] Automated regression tests
|
|
110
|
+
- [ ] Regular security audits
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Automated Security Tests
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
// tests/tenant-isolation.test.ts
|
|
117
|
+
import { PrismaClient } from "@prisma/client";
|
|
118
|
+
|
|
119
|
+
describe("Tenant Isolation", () => {
|
|
120
|
+
let prisma: PrismaClient;
|
|
121
|
+
let tenant1Id: number;
|
|
122
|
+
let tenant2Id: number;
|
|
123
|
+
|
|
124
|
+
beforeAll(async () => {
|
|
125
|
+
prisma = new PrismaClient();
|
|
126
|
+
|
|
127
|
+
// Create test tenants
|
|
128
|
+
const tenant1 = await prisma.tenant.create({
|
|
129
|
+
data: { name: "Tenant 1" },
|
|
130
|
+
});
|
|
131
|
+
const tenant2 = await prisma.tenant.create({
|
|
132
|
+
data: { name: "Tenant 2" },
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
tenant1Id = tenant1.id;
|
|
136
|
+
tenant2Id = tenant2.id;
|
|
137
|
+
|
|
138
|
+
// Create test data
|
|
139
|
+
await prisma.user.create({
|
|
140
|
+
data: {
|
|
141
|
+
email: "user1@tenant1.com",
|
|
142
|
+
tenantId: tenant1Id,
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
await prisma.user.create({
|
|
146
|
+
data: {
|
|
147
|
+
email: "user2@tenant2.com",
|
|
148
|
+
tenantId: tenant2Id,
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should not access data from other tenants", async () => {
|
|
154
|
+
// Set tenant context to Tenant 1
|
|
155
|
+
await prisma.$executeRaw`SET app.tenant_id = ${tenant1Id}`;
|
|
156
|
+
|
|
157
|
+
// Query users
|
|
158
|
+
const users = await prisma.user.findMany();
|
|
159
|
+
|
|
160
|
+
// Should only see Tenant 1 users
|
|
161
|
+
expect(users.length).toBe(1);
|
|
162
|
+
expect(users[0].email).toBe("user1@tenant1.com");
|
|
163
|
+
|
|
164
|
+
// Should NOT see Tenant 2 users
|
|
165
|
+
expect(users.find((u) => u.email === "user2@tenant2.com")).toBeUndefined();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("should prevent cross-tenant updates", async () => {
|
|
169
|
+
await prisma.$executeRaw`SET app.tenant_id = ${tenant1Id}`;
|
|
170
|
+
|
|
171
|
+
// Try to update Tenant 2 user (should fail silently with RLS)
|
|
172
|
+
const tenant2User = await prisma.user.findFirst({
|
|
173
|
+
where: { email: "user2@tenant2.com" },
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Should not find user from other tenant
|
|
177
|
+
expect(tenant2User).toBeNull();
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("should prevent cross-tenant deletes", async () => {
|
|
181
|
+
await prisma.$executeRaw`SET app.tenant_id = ${tenant1Id}`;
|
|
182
|
+
|
|
183
|
+
// Try to delete Tenant 2 user
|
|
184
|
+
const result = await prisma.user.deleteMany({
|
|
185
|
+
where: { tenantId: tenant2Id },
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Should delete 0 rows (RLS prevents access)
|
|
189
|
+
expect(result.count).toBe(0);
|
|
190
|
+
|
|
191
|
+
// Verify user still exists
|
|
192
|
+
await prisma.$executeRaw`SET app.tenant_id = ${tenant2Id}`;
|
|
193
|
+
const user = await prisma.user.findFirst({
|
|
194
|
+
where: { email: "user2@tenant2.com" },
|
|
195
|
+
});
|
|
196
|
+
expect(user).not.toBeNull();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("should handle transaction rollback correctly", async () => {
|
|
200
|
+
try {
|
|
201
|
+
await prisma.$transaction(async (tx) => {
|
|
202
|
+
await tx.$executeRaw`SET LOCAL app.tenant_id = ${tenant1Id}`;
|
|
203
|
+
|
|
204
|
+
// Create user
|
|
205
|
+
await tx.user.create({
|
|
206
|
+
data: {
|
|
207
|
+
email: "test@tenant1.com",
|
|
208
|
+
tenantId: tenant1Id,
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
// Force error
|
|
213
|
+
throw new Error("Rollback test");
|
|
214
|
+
});
|
|
215
|
+
} catch (error) {
|
|
216
|
+
// Transaction rolled back
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// User should not exist
|
|
220
|
+
await prisma.$executeRaw`SET app.tenant_id = ${tenant1Id}`;
|
|
221
|
+
const user = await prisma.user.findFirst({
|
|
222
|
+
where: { email: "test@tenant1.com" },
|
|
223
|
+
});
|
|
224
|
+
expect(user).toBeNull();
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## RLS Audit Script
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
// scripts/audit-rls.ts
|
|
233
|
+
async function auditRLS() {
|
|
234
|
+
const tables = await prisma.$queryRaw<any[]>`
|
|
235
|
+
SELECT tablename
|
|
236
|
+
FROM pg_tables
|
|
237
|
+
WHERE schemaname = 'public'
|
|
238
|
+
AND tablename != '_prisma_migrations'
|
|
239
|
+
`;
|
|
240
|
+
|
|
241
|
+
console.log("🔍 Auditing Row Level Security...\n");
|
|
242
|
+
|
|
243
|
+
for (const { tablename } of tables) {
|
|
244
|
+
// Check if table has tenant_id
|
|
245
|
+
const columns = await prisma.$queryRaw<any[]>`
|
|
246
|
+
SELECT column_name
|
|
247
|
+
FROM information_schema.columns
|
|
248
|
+
WHERE table_name = ${tablename}
|
|
249
|
+
AND column_name = 'tenant_id'
|
|
250
|
+
`;
|
|
251
|
+
|
|
252
|
+
if (columns.length === 0) {
|
|
253
|
+
console.log(`❌ ${tablename}: Missing tenant_id column`);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Check if RLS is enabled
|
|
258
|
+
const rlsStatus = await prisma.$queryRaw<any[]>`
|
|
259
|
+
SELECT relname, relrowsecurity, relforcerowsecurity
|
|
260
|
+
FROM pg_class
|
|
261
|
+
WHERE relname = ${tablename}
|
|
262
|
+
`;
|
|
263
|
+
|
|
264
|
+
if (!rlsStatus[0]?.relrowsecurity) {
|
|
265
|
+
console.log(`❌ ${tablename}: RLS not enabled`);
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!rlsStatus[0]?.relforcerowsecurity) {
|
|
270
|
+
console.log(`⚠️ ${tablename}: RLS not forced (owners can bypass)`);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Check if policy exists
|
|
274
|
+
const policies = await prisma.$queryRaw<any[]>`
|
|
275
|
+
SELECT policyname, qual
|
|
276
|
+
FROM pg_policies
|
|
277
|
+
WHERE tablename = ${tablename}
|
|
278
|
+
`;
|
|
279
|
+
|
|
280
|
+
if (policies.length === 0) {
|
|
281
|
+
console.log(`❌ ${tablename}: No RLS policies defined`);
|
|
282
|
+
} else {
|
|
283
|
+
console.log(
|
|
284
|
+
`✅ ${tablename}: RLS configured (${policies.length} policies)`
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Composite Indexes for Performance
|
|
292
|
+
|
|
293
|
+
```sql
|
|
294
|
+
-- Composite indexes with tenant_id first
|
|
295
|
+
CREATE INDEX idx_orders_tenant_user ON orders(tenant_id, user_id);
|
|
296
|
+
CREATE INDEX idx_orders_tenant_created ON orders(tenant_id, created_at DESC);
|
|
297
|
+
CREATE INDEX idx_products_tenant_category ON products(tenant_id, category);
|
|
298
|
+
|
|
299
|
+
-- This ensures queries filtered by tenant_id are fast
|
|
300
|
+
-- SELECT * FROM orders WHERE tenant_id = 1 AND user_id = 123; -- Uses index
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## Middleware for Automatic Tenant Injection
|
|
304
|
+
|
|
305
|
+
```typescript
|
|
306
|
+
// prisma/middleware.ts
|
|
307
|
+
import { Prisma } from "@prisma/client";
|
|
308
|
+
|
|
309
|
+
export function tenantMiddleware(tenantId: number) {
|
|
310
|
+
return async (
|
|
311
|
+
params: Prisma.MiddlewareParams,
|
|
312
|
+
next: (params: Prisma.MiddlewareParams) => Promise<any>
|
|
313
|
+
) => {
|
|
314
|
+
// Inject tenant_id into all queries
|
|
315
|
+
if (params.action === "findMany" || params.action === "findFirst") {
|
|
316
|
+
params.args.where = {
|
|
317
|
+
...params.args.where,
|
|
318
|
+
tenantId,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (params.action === "create") {
|
|
323
|
+
params.args.data = {
|
|
324
|
+
...params.args.data,
|
|
325
|
+
tenantId,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (params.action === "createMany") {
|
|
330
|
+
if (Array.isArray(params.args.data)) {
|
|
331
|
+
params.args.data = params.args.data.map((item) => ({
|
|
332
|
+
...item,
|
|
333
|
+
tenantId,
|
|
334
|
+
}));
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return next(params);
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Usage:
|
|
343
|
+
const prisma = new PrismaClient();
|
|
344
|
+
prisma.$use(tenantMiddleware(req.user.tenantId));
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Security Regression Tests
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
// tests/security-regression.test.ts
|
|
351
|
+
describe("Security Regression Tests", () => {
|
|
352
|
+
it("should not allow SQL injection to bypass tenant", async () => {
|
|
353
|
+
const maliciousInput = "1 OR 1=1 --";
|
|
354
|
+
|
|
355
|
+
// This should be safely parameterized
|
|
356
|
+
const users = await prisma.user.findMany({
|
|
357
|
+
where: {
|
|
358
|
+
tenantId: parseInt(maliciousInput), // Will be NaN, safe
|
|
359
|
+
},
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
expect(users).toEqual([]);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
it("should not expose tenant data via API error messages", async () => {
|
|
366
|
+
try {
|
|
367
|
+
await prisma.user.findUniqueOrThrow({
|
|
368
|
+
where: { id: 9999 }, // Non-existent
|
|
369
|
+
});
|
|
370
|
+
} catch (error) {
|
|
371
|
+
// Error should not leak tenant information
|
|
372
|
+
expect(error.message).not.toContain("tenant_id");
|
|
373
|
+
expect(error.message).not.toContain("tenantId");
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Best Practices
|
|
380
|
+
|
|
381
|
+
1. **Always use RLS**: Don't rely on application logic alone
|
|
382
|
+
2. **Force RLS**: Even for table owners (FORCE ROW LEVEL SECURITY)
|
|
383
|
+
3. **Test thoroughly**: Automated tests for cross-tenant access
|
|
384
|
+
4. **Audit regularly**: Monthly RLS configuration audits
|
|
385
|
+
5. **Composite indexes**: tenant_id first in all indexes
|
|
386
|
+
6. **Tenant validation**: Verify user belongs to tenant
|
|
387
|
+
7. **Monitor**: Log cross-tenant access attempts
|
|
388
|
+
|
|
389
|
+
## Output Checklist
|
|
390
|
+
|
|
391
|
+
- [ ] RLS enabled on all tables
|
|
392
|
+
- [ ] RLS policies created
|
|
393
|
+
- [ ] Tenant context middleware
|
|
394
|
+
- [ ] Automated security tests
|
|
395
|
+
- [ ] RLS audit script
|
|
396
|
+
- [ ] Composite indexes created
|
|
397
|
+
- [ ] Cross-tenant access prevention tested
|
|
398
|
+
- [ ] Security regression test suite
|