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)
| Role | Read | Comment | Edit pages/blocks | Manage shares | Manage members | Workspace settings | Destroy 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-scopedsharesgrants extend access to specific users (potentially elevated above their workspace role for this page).
publicβ a tokenizedshare_linkexposes the page to anonymous readers under itsmode.
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 onshare_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) andexpires_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) + pagevisibility.
PermissionsResolvercomputes effective(page, user) -> role.
- Result persisted in
permissionstable (uniq(page_id, user_id)). TTL: rebuilt on demand and invalidated byInvalidatePermissionsCacheJobon:- membership change,
- role change,
- share grant/revoke,
- page visibility change,
- page move (parent change reshuffles inherited visibility).
7 Β· Threat model (selected)
| Threat | Mitigation |
|---|---|
| IDOR β reading a page across workspaces | Policies + workspace scope in every query + idor.cross_workspace error class. |
| XSS through editor content | Sanitizer::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 unfurl | Server-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 content | System prompt strips embedded βignore previousβ patterns; user-content sandwiched between <<<SELECTION>>> markers; RAG hits passed as data, not instructions. |
| File upload abuse | MIME allowlist + magic-byte check, size cap (default 50 MiB), scan with clamav if available, store under random key, signed URLs only. |
| Brute force auth | Login 5 / min / IP, 50 / day / IP; password Argon2id; 2FA optional (TOTP). |
| Mass enumeration of UUIDs | UUIDv7 (time-ordered) is not used for security boundary; PolicyGate is the boundary, not URL guessability. |
| Secret leak via RAG | SPEC-STV-07 Β§9 secret-pattern dropper. |
| DoS via large block edits | Block size cap 256 KiB; batch endpoint cap 100 blocks; page payload cap 5 MiB. |
| Public-page abuse | Rate 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
codeblocks 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>, nojavascript:URLs).
- File names sanitized to filesystem-safe strings; original_name preserved for display only.
10 Β· Rate limit catalogue
| Surface | Limit |
|---|---|
| Login | 5 / min / IP |
| AI transform | 30 / min / user, 500 / day / workspace |
| AI generate | 10 / min / user |
| Public share read | 60 / min / IP |
| File presign | 60 / min / user |
| Search | 120 / min / user |
| RAG query | 60 / 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.
permissionstable + Redis hot path.
- Queue heavy ops. Indexing, export, import, reindex, large reorders β jobs.
- Locks.
Cache::lockfor 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
/adminroute 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_adminflag onusers, separate from workspace role.
13 Β· Auth hardening
- Session cookies:
HttpOnly,Secure,SameSite=Laxfor web;SameSite=Noneonly 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.