TenantAtlas/docs/security-guidelines.md
ahmido bf43dad3d1 fix: enforce workspace surface scope for customer review workspace (#366)
## Summary
- keep `/admin/reviews/workspace` workspace-scoped in shell and sidebar context
- treat `tenant` query hints on the customer review workspace as page-level filters only
- update the customer review workspace tests and Spec 311 navigation contract to match the workspace-hub IA

## Testing
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Reviews/CustomerReviewWorkspacePageTest.php`
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/Filament/WorkspaceContextTopbarAndTenantSelectionTest.php tests/Feature/Filament/PanelNavigationSegregationTest.php`
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`
- `git diff --check`

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #366
2026-05-15 20:52:37 +00:00

138 lines
5.8 KiB
Markdown

# 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()`.
- `UiEnforcement` centralizes disabled/hidden UI affordance behavior.
- `ProviderCredential` uses 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 audit` clean or explicitly risk-accepted.
- `corepack pnpm audit --audit-level moderate` clean or explicitly risk-accepted.
- `APP_DEBUG=false` in staging/production.
- `APP_KEY` present 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`, `canDelete` call 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 `*Any` policy 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()` and `maxSize()`.
- 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 `UiEnforcement` or `WorkspaceUiEnforcement`.
- 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_id` and 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
```php
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
```php
$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.