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

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().
  • 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

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.