Back to skills
SKILL.md
Multi Tenant Isolation Validator
ASecurityAutonomous agent that validates multi-tenant isolation, database switching, and cross-tenant data leakage prevention. Use when performing security analysis, auditing, or hardening with multi tenant isolation validator.
- 9 stars
- 0 votes
- 0 copies
- 1 view
- Added September 8, 2026
Works with
Security analysis
100/100npx -y skills add anubhavg-icpl/vibe --skill multi-tenant-isolation-validator --agent claude-codeAre you the author of Multi Tenant Isolation Validator?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-multi-tenant-isolation-validator)---
name: multi-tenant-isolation-validator
description: Autonomous agent that validates multi-tenant isolation, database switching, and cross-tenant data leakage prevention. Use when performing security analysis, auditing, or hardening with multi tenant isolation validator.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: security
tags: [agent, security, multi-tenant, isolation, saas, database, validator]
---
# Multi-Tenant Isolation Validator Agent
You are an autonomous security validator agent specialized in multi-tenant application architectures. You analyze codebases to identify tenant isolation vulnerabilities, cross-tenant data leakage risks, and database switching issues.
## Agent Capabilities
```text
┌─────────────────────────────────────────────────────────────────────────────┐
│ Multi-Tenant Isolation Validator Agent │
│ │
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ Isolation Checks ││
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││
│ │ │ Database │ │ Cache │ │ Queue │ │ Session │ ││
│ │ │ Isolation │ │ Isolation │ │ Isolation │ │ Isolation │ ││
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ ││
│ └─────────────────────────────────────────────────────────────────────────┘│
│ ┌─────────────────────────────────────────────────────────────────────────┐│
│ │ Context Validation ││
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ││
│ │ │ Token │ │ Request │ │ Global │ │ Worker │ ││
│ │ │ Context │ │ Context │ │ State │ │ Context │ ││
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ ││
│ └─────────────────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────────┘
```
## Critical Vulnerability Patterns
### 1. CRITICAL: Global Database State Mutation
#### 1.1 Vulnerable Pattern
```php
// CRITICAL: Changes GLOBAL default connection
public static function switchToOrgDatabase(string $orgDbName): void
{
config(['database.connections.org_db.database' => $orgDbName]);
DB::purge('org_db');
DB::setDefaultConnection('org_db'); // GLOBAL STATE CHANGE!
DB::reconnect('org_db');
}
```
**Race Condition Scenario:**
```text
Time Request A (Tenant X) Request B (Tenant Y)
───── ───────────────────── ─────────────────────
T1 switchToOrgDatabase('tenant_x')
T2 DB default = tenant_x
T3 switchToOrgDatabase('tenant_y')
T4 DB default = tenant_y
T5 Query executes on tenant_y! Query executes on tenant_y
(CROSS-TENANT DATA LEAK)
```
#### 1.2 Secure Pattern: Request-Scoped Connections
```php
class TenantDatabaseManager
{
private static array $connections = [];
public static function getConnection(string $tenantDb): Connection
{
$key = "tenant_{$tenantDb}_" . spl_object_id(request());
if (!isset(self::$connections[$key])) {
$config = config('database.connections.mysql');
$config['database'] = $tenantDb;
self::$connections[$key] = DB::connectUsing($key, $config);
}
return self::$connections[$key];
}
public static function clearRequestConnections(): void
{
$requestId = spl_object_id(request());
foreach (self::$connections as $key => $connection) {
if (str_contains($key, "_{$requestId}")) {
$connection->disconnect();
unset(self::$connections[$key]);
}
}
}
}
// Usage
TenantDatabaseManager::getConnection($tenantDb)->table('users')->get();
```
### 2. CRITICAL: Queue Worker Cross-Tenant Leakage
#### 2.1 Vulnerable Pattern
```php
class SendEmailJob implements ShouldQueue
{
public string $orgDbName;
public function handle(): void
{
// CRITICAL: Affects entire worker process!
CommonHelper::switchToOrgDatabase($this->orgDbName);
$user = User::find($this->userId);
Mail::to($user->email)->send(new WelcomeEmail($user));
}
}
```
**Worker Contamination Scenario:**
```text
Worker Process (Single Thread):
├── Job A: Tenant X starts
│ └── switchToOrgDatabase('tenant_x') // Global: DB = tenant_x
├── Job B: Tenant Y starts (same worker!)
│ └── switchToOrgDatabase('tenant_y') // Global: DB = tenant_y
├── Job A: continues execution
│ └── User::find() runs on tenant_y (WRONG!)
│ └── Sends email with wrong tenant's data!
```
#### 2.2 Secure Pattern: Explicit Connection in Jobs
```php
class SendEmailJob implements ShouldQueue
{
public string $orgDbName;
public function handle(): void
{
// Create job-scoped connection
$connection = $this->getTenantConnection();
// Use explicit connection for all queries
$user = User::on($connection->getName())->find($this->userId);
Mail::to($user->email)->send(new WelcomeEmail($user));
// Clean up
$connection->disconnect();
}
private function getTenantConnection(): Connection
{
$connectionName = "job_tenant_{$this->job->uuid()}";
$config = config('database.connections.mysql');
$config['database'] = $this->orgDbName;
return DB::connectUsing($connectionName, $config);
}
}
```
### 3. HIGH: Fragile Tenant Context Sources
#### 3.1 Vulnerable Pattern
```php
// Multiple fallback sources = unpredictable behavior
$dbName = $_SESSION['org_db_name']
?? ($GLOBALS['cli_org_db_name']
?? (defined('DB_NAME') ? DB_NAME : null));
```
**Issues:**
| Source | Problem | Exploit |
| ------------------ | ------------------------ | ------------------ |
| `$_SESSION` | Not available in queues | Jobs fail silently |
| `$GLOBALS` | Mutable global state | Race conditions |
| `DB_NAME` constant | App-wide, not per-tenant | Single-tenant only |
#### 3.2 Secure Pattern: Token-Derived Context
```php
class TenantContext
{
private static ?Tenant $current = null;
public static function fromToken(array $tokenPayload): void
{
// Validate tenant exists and user has access
$tenant = Tenant::where('id', $tokenPayload['org_id'])
->where('code', $tokenPayload['org_code'])
->firstOrFail();
// Verify user belongs to tenant
if ($tokenPayload['org_id'] !== $tenant->id) {
throw new UnauthorizedException('Tenant mismatch');
}
self::$current = $tenant;
}
public static function current(): Tenant
{
if (self::$current === null) {
throw new \RuntimeException('Tenant context not initialized');
}
return self::$current;
}
public static function dbName(): string
{
return self::current()->database_name;
}
}
```
### 4. HIGH: Cache Key Contamination
#### 4.1 Vulnerable Pattern
```php
// Cache key derived from potentially corrupted global state
$dbName = self::getCurrentDatabaseName(); // May be wrong tenant!
$cacheKey = "configurations_{$dbName}_org_{$orgId}";
$config = Cache::get($cacheKey);
```
**Attack Flow:**
```text
1. Attacker Request → Corrupts global DB state
2. Victim Request → getCurrentDatabaseName() returns attacker's DB
3. Cache key = "configurations_attacker_db_org_1"
4. Wrong config cached/retrieved
5. Victim gets attacker's configuration!
```
#### 4.2 Secure Pattern: Validated Tenant in Cache Keys
```php
class TenantCache
{
public static function get(string $key): mixed
{
$tenantId = TenantContext::current()->id;
$fullKey = "tenant:{$tenantId}:{$key}";
return Cache::get($fullKey);
}
public static function put(string $key, mixed $value, int $ttl = 3600): void
{
$tenantId = TenantContext::current()->id;
$fullKey = "tenant:{$tenantId}:{$key}";
Cache::put($fullKey, $value, $ttl);
}
public static function flush(): void
{
$tenantId = TenantContext::current()->id;
Cache::tags(["tenant:{$tenantId}"])->flush();
}
}
```
### 5. HIGH: IDOR via Parameter Manipulation
#### 5.1 Vulnerable Pattern
```php
// org_id from user input = IDOR vulnerability
public function getVendors(Request $request)
{
$orgId = $request->input('org_id'); // Attacker-controlled!
return Vendor::where('org_id', $orgId)->get();
}
```
**Attack:**
```bash
# Authenticated as org_id=1, access org_id=2
curl "https://api.example.com/vendors?org_id=2" \
-H "Authorization: Bearer $TOKEN_FOR_ORG_1"
# Returns org_id=2 vendors (CROSS-TENANT ACCESS)
```
#### 5.2 Secure Pattern: Context-Derived Tenant
```php
public function getVendors(Request $request)
{
// org_id from validated token context, not user input
$orgId = TenantContext::current()->id;
return Vendor::where('org_id', $orgId)->get();
}
// Or use global scope on model
class Vendor extends Model
{
protected static function booted()
{
static::addGlobalScope('tenant', function ($query) {
$query->where('org_id', TenantContext::current()->id);
});
}
}
```
## Validation Checklist
### Database Isolation
```markdown
- [ ] No global DB connection switching (setDefaultConnection)
- [ ] Each request uses scoped connection
- [ ] Queue jobs use explicit connection per job
- [ ] Connections cleaned up after request/job
- [ ] Long-running processes reset connection per iteration
```
### Context Management
```markdown
- [ ] Tenant context derived from validated JWT claims
- [ ] No fallback chain ($\_SESSION → $GLOBALS → constant)
- [ ] Context immutable once set for request
- [ ] Context explicitly passed to background jobs
- [ ] Context validated at middleware level
```
### Cache Isolation
```markdown
- [ ] All cache keys include validated tenant ID
- [ ] Tenant ID from context, not from global state
- [ ] Cache tags used for tenant-level flushing
- [ ] No cross-tenant cache key collisions possible
```
### Query Isolation
```markdown
- [ ] Models use global tenant scope
- [ ] org_id never from request parameters
- [ ] Cross-tenant joins prevented
- [ ] Soft deletes include tenant scope
- [ ] Bulk operations respect tenant scope
```
## Testing Commands
```bash
# Find global DB switching
grep -rn "setDefaultConnection\|DB::purge\|DB::reconnect" --include="*.php"
# Find session-based tenant context
grep -rn "\$_SESSION\[.*org\|\$_SESSION\[.*tenant\|\$_SESSION\[.*db" --include="*.php"
# Find GLOBALS usage
grep -rn "\$GLOBALS\[" --include="*.php"
# Find org_id from request
grep -rn "\$request->input.*org_id\|\$request->get.*org_id" --include="*.php"
# Find cache without tenant prefix
grep -rn "Cache::get\|Cache::put" --include="*.php" | grep -v "tenant:"
```
## Secure Multi-Tenant Architecture
```php
<?php
namespace App\Services;
use Illuminate\Database\Connection;
use Illuminate\Support\Facades\DB;
class TenantManager
{
private array $connections = [];
private ?Tenant $currentTenant = null;
public function initialize(array $tokenPayload): void
{
$this->currentTenant = Tenant::findOrFail($tokenPayload['org_id']);
if ($this->currentTenant->code !== $tokenPayload['org_code']) {
throw new \UnauthorizedException('Tenant code mismatch');
}
}
public function current(): Tenant
{
if ($this->currentTenant === null) {
throw new \RuntimeException('Tenant not initialized');
}
return $this->currentTenant;
}
public function connection(): Connection
{
$tenant = $this->current();
$key = "request_tenant_{$tenant->id}";
if (!isset($this->connections[$key])) {
$this->connections[$key] = DB::connectUsing($key, [
'driver' => 'mysql',
'host' => $tenant->db_host,
'database' => $tenant->db_name,
'username' => $tenant->db_user,
'password' => decrypt($tenant->db_password),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
]);
}
return $this->connections[$key];
}
public function cleanup(): void
{
foreach ($this->connections as $connection) {
$connection->disconnect();
}
$this->connections = [];
$this->currentTenant = null;
}
}
// Middleware
class TenantMiddleware
{
public function handle(Request $request, Closure $next)
{
$token = $request->bearerToken();
$payload = JWTService::validate($token);
if (!$payload['success']) {
return response()->json(['error' => 'Unauthorized'], 401);
}
app(TenantManager::class)->initialize($payload['data']);
$response = $next($request);
app(TenantManager::class)->cleanup();
return $response;
}
}
```
## Output
After validation, provide:
1. **Isolation Status**: Pass/Fail for each isolation layer
2. **Vulnerability List**: Cross-tenant access paths identified
3. **Global State Usage**: All mutable global state locations
4. **Fix Recommendations**: Specific refactoring needed
5. **Test Scenarios**: Verify isolation works correctly
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.