πŸ›‘οΈ

SPEC-STV-11-Sharing-Security

πŸ“œ

SPEC-STV-11 Β· Spec header. Spec ID: SPEC-STV-11 Β· Title: Sharing, Permissions, Security, Scalability, Admin Β· Version: 1.0.0 Β· Status: Planned Β· Authority: Specification Β· Priority: P0 Β· Owner role: Security lead Β· Reviewers: Backend architect, DevOps architect, Frontend lead Β· Last reviewed: 2026-05-11 Β· Sync targets: app/Policies/**, app/Filament/**, docs/SECURITY.md, docs/SCALABILITY.md Β· Depends on: SPEC-STV-HUB, SPEC-STV-02, SPEC-STV-03 Β· Consumed by: every other SPEC-STV-* spec Β· Conflict rule: Hub wins. For any auth question this spec is canonical over individual feature specs. Β· Change policy: Security lead + Backend architect; Registry bump.

1 Β· Roles (workspace-level)

RoleReadCommentEdit pages/blocksManage sharesManage membersWorkspace settingsDestroy workspace
ownerβœ“βœ“βœ“βœ“βœ“βœ“βœ“
adminβœ“βœ“βœ“βœ“βœ“βœ“βœ•
editorβœ“βœ“βœ“own pages onlyβœ•βœ•βœ•
commenterβœ“βœ“βœ•βœ•βœ•βœ•βœ•
viewerβœ“βœ•βœ•βœ•βœ•βœ•βœ•

2 Β· Visibility (page-level)

  • private β€” only the author sees the page (and admins).
  • workspace β€” every member sees according to their role.
  • shared β€” page-scoped shares grants extend access to specific users (potentially elevated above their workspace role for this page).
  • public β€” a tokenized share_link exposes the page to anonymous readers under its mode.

3 Β· Share modes

  • view β€” read-only.
  • comment β€” read + comment (creates an anonymous-author comment unless the visitor authenticates).
  • edit β€” read + edit (requires authenticated user; not available on share_links).

4 Β· Tokenized public links

  • Token: 48-char Crockford-base32 from random_bytes(30) β€” URL-safe, ~232 bits.
  • Stored under share_links.token (uniq).
  • Optional password_hash (Argon2id, server-side verify) and expires_at.
  • Public URL pattern: /p/{token}. Rate-limited 60 / min / IP, 5 / sec burst.
  • The token leaks read access to a single page (and its embedded inline databases that share visibility). Recursive child pages are NOT exposed unless each has its own link.

5 Β· Policy classes

WorkspacePolicy, PagePolicy, BlockPolicy, DatabasePolicy, DatabaseRowPolicy, CommentPolicy, FilePolicy, SharePolicy, ShareLinkPolicy, TemplatePolicy, ActivityLogPolicy, SettingsPolicy. Each defines view, create, update, delete, share, admin ability set; Gate::policy() wired in AppServiceProvider.

6 Β· Permissions cache

  • Source of truth: workspace_members.role + shares + share_links (token redeem) + page visibility.
  • PermissionsResolver computes effective (page, user) -> role.
  • Result persisted in permissions table (uniq (page_id, user_id)). TTL: rebuilt on demand and invalidated by InvalidatePermissionsCacheJob on:
    • membership change,
    • role change,
    • share grant/revoke,
    • page visibility change,
    • page move (parent change reshuffles inherited visibility).

7 Β· Threat model (selected)

ThreatMitigation
IDOR β€” reading a page across workspacesPolicies + workspace scope in every query + idor.cross_workspace error class.
XSS through editor contentSanitizer::block() strips all HTML; rendering escapes; Content-Security-Policy: default-src 'self'; img-src 'self' https: data:; media-src 'self' https:.
SSRF via image / link unfurlServer-side fetcher uses an allowlist of schemes (https), private-IP block, max body size, timeout, response-type check.
Token leakage in URL/p/{token} has Cache-Control: no-store; Referrer-Policy: no-referrer-when-downgrade; passwords not in query strings.
AI prompt injection from page contentSystem prompt strips embedded β€œignore previous” patterns; user-content sandwiched between <<<SELECTION>>> markers; RAG hits passed as data, not instructions.
File upload abuseMIME allowlist + magic-byte check, size cap (default 50 MiB), scan with clamav if available, store under random key, signed URLs only.
Brute force authLogin 5 / min / IP, 50 / day / IP; password Argon2id; 2FA optional (TOTP).
Mass enumeration of UUIDsUUIDv7 (time-ordered) is not used for security boundary; PolicyGate is the boundary, not URL guessability.
Secret leak via RAGSPEC-STV-07 Β§9 secret-pattern dropper.
DoS via large block editsBlock size cap 256 KiB; batch endpoint cap 100 blocks; page payload cap 5 MiB.
Public-page abuseRate limit + Cloudflare-style WAF rules in the LB layer.

8 Β· Signed URLs

All private file downloads use S3 V4 signed URLs (default TTL 5 min). Web pages requesting a file proxy through /files/{uuid}/signed-url so the server can revalidate the policy at request time.

9 Β· Sanitization

  • Editor block content normalized to canonical JSON; HTML stripped except inside code blocks where text is preserved verbatim and rendered with safe tokenization (no <script>).
  • Comment body parsed from Markdown with a strict allowlist (no <iframe>, no <script>, no javascript: URLs).
  • File names sanitized to filesystem-safe strings; original_name preserved for display only.

10 Β· Rate limit catalogue

SurfaceLimit
Login5 / min / IP
AI transform30 / min / user, 500 / day / workspace
AI generate10 / min / user
Public share read60 / min / IP
File presign60 / min / user
Search120 / min / user
RAG query60 / min / user

11 Β· Scalability rules

  • Pagination everywhere. No list endpoint returns unbounded results. Cursor-based on hot lists.
  • Lazy expand. Page tree fetches children on demand; never the whole workspace tree.
  • Block windowing. First N blocks (default 100) returned with the page; the rest paginated by position.
  • Index discipline. Every hot-path query has a covering index documented in SPEC-STV-02 Β§4.
  • Cache permissions. permissions table + Redis hot path.
  • Queue heavy ops. Indexing, export, import, reindex, large reorders β†’ jobs.
  • Locks. Cache::lock for per-workspace heavy ops to prevent thundering herds.
  • Read replicas. Eloquent connection split; reads on replica where freshness allows.
  • CDN / edge cache. Static assets, public-page HTML (short TTL), file responses (signed URLs only).

12 Β· Filament admin surface

  • /admin route group, Filament v3.
  • Resources: Users, Workspaces, WorkspaceMembers, Pages (read-only), Files, Templates, ShareLinks, ActivityLogs, AiTextTransformations, RagChunks (read + per-source reindex action), Settings.
  • Dashboards: Queue health (Horizon iframe), Failed jobs, Storage usage per workspace, AI cost per workspace per day, Public link traffic.
  • Filament is admin-only. Not exposed to end users. Authorization: is_admin flag on users, separate from workspace role.

13 Β· Auth hardening

  • Session cookies: HttpOnly, Secure, SameSite=Lax for web; SameSite=None only on the public-share subdomain.
  • Sanctum tokens scoped to the issuing user; revocable.
  • Password reset: signed URL, single-use, 60 min TTL.
  • 2FA (TOTP) optional; recovery codes generated.

14 Β· Compliance & data lifecycle

  • Soft-delete on workspaces and pages; hard delete after 30 days (configurable).
  • Export-my-data endpoint (POST /me/export) returns a JSON+files ZIP, queued.
  • Delete-my-account endpoint anonymizes the user row, reassigns authored content to a tombstone user, and emits an audit event.