5.8 KiB
5.8 KiB
TenantPilot Security Guidelines
Status: 2026-05-15 Reference model: OWASP ASVS 5.0.0, OWASP Top 10, NIST SSDF, Laravel 12, Filament 5.
Security Target
TenantPilot manages critical Intune configuration and restore workflows. Treat tenant data, backup payloads, provider credentials, policy snapshots, audit logs, and operation runs as sensitive enterprise data.
Current Strengths
- Workspace and tenant isolation are constitutional non-negotiables.
- Many policies return
Response::denyAsNotFound(). UiEnforcementcentralizes disabled/hidden UI affordance behavior.ProviderCredentialuses encrypted array casts and hides payloads.- Graph access is routed through
GraphClientInterface. - Audit and operation-run models already provide traceability.
Top Security Findings
| Risk | Evidence | Priority | Control |
|---|---|---|---|
| Vulnerable dependencies | composer audit, pnpm audit |
P0 | Patch, audit gates, approved exceptions only. |
| Inconsistent policy coverage | Some resource-backed models lack obvious policies | P1 | Resource-policy matrix and tests. |
| Production session/debug defaults need gating | .env.example has APP_DEBUG=true, SESSION_ENCRYPT=false for local |
P1 | Deployment checklist enforces production env. |
| File upload future risk | Filament warns about file path tampering and filenames | P2 | Private disks, random names, MIME validation, path tamper prevention. |
| Graph beta default | config/graph.php defaults to beta |
P2 | Endpoint-level version registry and contract tests. |
Release Security Checklist
composer auditclean or explicitly risk-accepted.corepack pnpm audit --audit-level moderateclean or explicitly risk-accepted.APP_DEBUG=falsein staging/production.APP_KEYpresent and not rotated casually.- Session cookies are secure, same-site, and domain-scoped for production.
- Provider credentials remain encrypted and never logged.
- No secrets in config, docs, tests, fixtures, screenshots, or audit metadata.
- Every write operation has policy authorization, explicit confirmation, and audit log.
- Backup and restore flows have dry-run/preview where applicable.
- Queue payloads contain identifiers, not secrets or raw credential payloads.
- Health endpoint and uptime monitor are active.
Checklist for New Filament Resources
- Policy exists for the model or a spec documents why no policy is needed.
canViewAny,canCreate,canEdit,canDeletecall policies or capability resolver consistently.- Tenant-owned resources scope queries by workspace and managed environment.
- Global search is disabled unless View/Edit pages are safe and scoped.
- Tables eager-load relationships shown in columns.
- Empty states do not leak tenant existence.
- Mutating actions are confirmation-gated and tested.
- Bulk actions intentionally choose
*Anypolicy semantics or per-record authorization.
Checklist for File Uploads
- Store on a private disk by default.
- Use random storage filenames.
- Store original filenames in a separate column if needed.
- Restrict
acceptedFileTypes()andmaxSize(). - Use Laravel file validation rules for server-side validation.
- Use
preventFilePathTampering()when the workflow does not intentionally allow choosing existing disk files. - Do not render uploaded HTML/SVG inline unless sanitized and explicitly approved.
- Signed URLs must be short-lived and tenant-authorized.
Checklist for Admin Actions
- Action name describes the business effect.
- UI state uses
UiEnforcementorWorkspaceUiEnforcement. - Server handler calls
Gate::authorize()or a policy method. - Destructive/high-impact action has
requiresConfirmation(). - Handler writes an audit event with actor, workspace, managed environment, target, outcome, and safe metadata.
- Long-running work dispatches a job and creates/updates an
OperationRun. - Duplicate clicks are idempotent or guarded by locks/unique run identity.
- Test covers allowed, disabled/denied, side effect, audit, and tenant isolation.
Checklist for Multi-Tenancy
- Workspace context is established before tenant context.
- Non-members receive deny-as-not-found.
- Queries filter by
workspace_idand tenant id before access. - Cross-tenant surfaces are explicit and aggregation-based.
- IDs from request/query strings are resolved through scoped resolvers.
- Tests include tenant A cannot see or mutate tenant B.
- Audit logs include workspace and tenant context when applicable.
Security Code Pattern: Policy
namespace App\Policies;
use App\Models\BackupSet;
use App\Models\User;
use Illuminate\Auth\Access\Response;
final class BackupSetPolicy
{
public function view(User $user, BackupSet $backupSet): Response
{
if (! $backupSet->workspace || ! $user->belongsToWorkspace($backupSet->workspace)) {
return Response::denyAsNotFound();
}
return $user->can('tenant.view', $backupSet->managedEnvironment)
? Response::allow()
: Response::denyAsNotFound();
}
public function restore(User $user, BackupSet $backupSet): Response
{
if ($this->view($user, $backupSet)->denied()) {
return Response::denyAsNotFound();
}
return $user->can('tenant.restore.run', $backupSet->managedEnvironment)
? Response::allow()
: Response::deny('Missing restore capability.');
}
}
Security Code Pattern: Audit Event
$audit->record(
action: 'backup_schedule.run_requested',
actor: $actor,
workspace: $schedule->workspace,
managedEnvironment: $schedule->managedEnvironment,
target: $schedule,
metadata: [
'operation_run_id' => $run->getKey(),
'schedule_id' => $schedule->getKey(),
],
);
Never include tokens, client secrets, raw credential payloads, or raw Graph error bodies in audit metadata.