Security Considerations
This document outlines security considerations, implemented protections, and known areas requiring attention in the core-tenant package.
Multi-Tenant Data Isolation
Workspace Scope Enforcement
The primary security mechanism is the BelongsToWorkspace trait which enforces workspace isolation at the model level.
How it works:
- Strict Mode (default in web requests): Queries without workspace context throw
MissingWorkspaceContextException - Auto-assignment: Creating models without explicit
workspace_iduses current context or throws - Cache invalidation: Model changes automatically invalidate workspace-scoped cache
Code paths:
// SAFE: Explicit workspace context
Account::forWorkspace($workspace)->get();
// SAFE: Uses current workspace from request
Account::ownedByCurrentWorkspace()->get();
// THROWS in strict mode: No workspace context
Account::query()->get(); // MissingWorkspaceContextException
// DANGEROUS: Bypasses scope - use with caution
Account::query()->acrossWorkspaces()->get();
WorkspaceScope::withoutStrictMode(fn() => Account::all());Middleware Protection
| Middleware | Purpose |
|---|---|
RequireWorkspaceContext | Ensures workspace is set before route handling |
CheckWorkspacePermission | Validates user has required permissions |
Recommendation: Always use workspace.required:validate for user-facing routes to ensure the authenticated user actually has access to the resolved workspace.
Known Gaps
SEC-006: The
RequireWorkspaceContextmiddleware accepts workspace from headers/query params without mandatory user access validation. Thevalidateparameter should be the default.Cross-tenant API: The
EntitlementApiControlleraccepts workspace lookups by email, which could allow enumeration of user-workspace associations. Consider adding authentication scopes.
Authentication Security
Password Storage
Passwords are hashed using bcrypt via Laravel's hashed cast:
protected function casts(): array
{
return [
'password' => 'hashed',
];
}Two-Factor Authentication
Implemented:
- TOTP (RFC 6238) with 30-second time steps
- 6-digit codes with SHA-1 HMAC
- Clock drift tolerance (1 window each direction)
- 8 recovery codes (20 characters each)
Security Considerations:
SEC-003: TOTP secrets are stored in plaintext. Should use Laravel's
encryptedcast.- File:
Models/UserTwoFactorAuth.php - Risk: Database breach exposes all 2FA secrets
- Mitigation: Use
'secret_key' => 'encrypted'cast
- File:
Recovery codes are stored as JSON array. Consider hashing each code individually.
No brute-force protection on TOTP verification endpoint (rate limiting should be applied at route level).
Session Security
Standard Laravel session handling with:
sessionstable for database driver- IP address and user agent tracking
remember_tokenfor persistent sessions
API Security
Blesta Integration API
The EntitlementApiController provides endpoints for external billing system integration:
| Endpoint | Risk | Mitigation |
|---|---|---|
POST /store | Creates users/workspaces | Requires API auth |
POST /suspend/{id} | Suspends access | Requires API auth |
POST /cancel/{id} | Cancels packages | Requires API auth |
Known Issues:
SEC-001: No rate limiting on API endpoints
- Risk: Compromised API key could mass-provision accounts
- Mitigation: Add rate limiting middleware
SEC-002: API authentication not visible in
Routes/api.php- Action: Verify Blesta routes have proper auth middleware
Webhook Security
Implemented:
- HMAC-SHA256 signature on all payloads
X-Signatureheader for verification- 32-byte random secrets (256-bit)
Code:
// Signing (outbound)
$signature = hash_hmac('sha256', json_encode($payload), $webhook->secret);
// Verification (inbound)
$expected = hash_hmac('sha256', $payload, $secret);
return hash_equals($expected, $signature);Known Issues:
- SEC-005: Webhook test endpoint could be SSRF vector
- Risk: Attacker could probe internal network via webhook URL
- Mitigation: Validate URLs against blocklist, prevent internal IPs
Invitation Tokens
Implemented:
- 64-character random tokens (
Str::random(64)) - Expiration dates with default 7-day TTL
- Single-use (marked accepted_at after use)
Known Issues:
SEC-004: Tokens stored in plaintext
- Risk: Database breach exposes all pending invitations
- Mitigation: Store hash, compare with
hash_equals()
No rate limiting on invitation acceptance endpoint
- Risk: Brute-force token guessing (though 64 chars is large keyspace)
- Mitigation: Add rate limiting, log failed attempts
Input Validation
EntitlementApiController
$validated = $request->validate([
'email' => 'required|email',
'name' => 'required|string|max:255',
'product_code' => 'required|string',
'billing_cycle_anchor' => 'nullable|date',
'expires_at' => 'nullable|date',
'blesta_service_id' => 'nullable|string',
]);Note: blesta_service_id and product_code are not sanitised for special characters. Consider adding regex validation if these are displayed in UI.
Workspace Manager Validation Rules
The WorkspaceManager provides scoped uniqueness rules:
// Ensures uniqueness within workspace
$manager->uniqueRule('social_accounts', 'handle', softDelete: true);Logging and Audit
Entitlement Logs
All entitlement changes are logged to entitlement_logs:
EntitlementLog::logPackageAction(
$workspace,
EntitlementLog::ACTION_PACKAGE_PROVISIONED,
$workspacePackage,
source: EntitlementLog::SOURCE_BLESTA,
newValues: $workspacePackage->toArray()
);Logged actions:
- Package provision/suspend/cancel/reactivate/renew
- Boost provision/expire/cancel
- Usage recording
Not logged (should consider):
- Workspace creation/deletion
- Member additions/removals
- Permission changes
- Login attempts
Security Event Logging
Currently limited. Recommend adding:
- Failed authentication attempts
- 2FA setup/disable events
- Invitation accept/reject
- API key usage
Sensitive Data Handling
Hidden Attributes
// User model
protected $hidden = [
'password',
'remember_token',
];
// Workspace model
protected $hidden = [
'wp_connector_secret',
];Guarded Attributes
// Workspace model
protected $guarded = [
'wp_connector_secret',
];Note: Using $fillable is generally safer than $guarded for sensitive models.
Recommendations
Immediate (P1)
- Add rate limiting to all API endpoints
- Encrypt 2FA secrets at rest
- Hash invitation tokens before storage
- Validate webhook URLs against SSRF attacks
- Make user access validation default in RequireWorkspaceContext
Short-term (P2)
- Add comprehensive security event logging
- Implement brute-force protection for:
- 2FA verification
- Invitation acceptance
- Password reset
- Add API scopes for entitlement operations
- Implement session fingerprinting (detect session hijacking)
Long-term (P3)
- Consider WebAuthn/FIDO2 as 2FA alternative
- Implement cryptographic binding between user sessions and workspace access
- Add anomaly detection for unusual entitlement patterns
- Consider field-level encryption for sensitive workspace data
Security Testing
Existing Tests
WorkspaceSecurityTest.php- Tests tenant isolationTwoFactorAuthenticatableTest.php- Tests 2FA flows
Recommended Additional Tests
- Test workspace scope bypass attempts
- Test API authentication failure handling
- Test rate limiting behaviour
- Test SSRF protection on webhook URLs
- Test invitation token brute-force protection
Compliance Notes
GDPR Considerations
- Account Deletion:
ProcessAccountDeletionjob handles user data removal - Data Export: Not currently implemented (consider adding)
- Consent Tracking: Not in scope of this package
PCI DSS
If handling payment data:
stripe_customer_idandbtcpay_customer_idare stored (tokens, not card data)- No direct card handling in this package
- Billing details (name, address) stored in workspace model
Incident Response
If you discover a security vulnerability:
- Do not disclose publicly
- Contact: security@host.uk.com (hypothetical)
- Include: Vulnerability description, reproduction steps, impact assessment