SECURITY_REVIEW.md — Confluence Expansion Security Review (Phase 0)
Scope: security impact of the planned features, against the existing security architecture (
docs/security/security-architecture.md, TASK-0134 ASVS L2 baseline). Rule of thumb applied: every new surface is a new trust-boundary question; the default answer is "reuse the existing enforcement point".
1. Security invariants (binding, from ARCHITECTURE.md + spec §9/§20)
- S-1: Authorization is evaluated server-side on every handler via
packages/permissions; the UI is a convenience layer. No endpoint, search query, export, attachment URL, background job, or AI call may bypass it. - S-2: Deny-by-default for protected resources.
- S-3: Multi-tenant isolation: every query scoped by
workspaceIdat the repository layer; no cross-tenant id access (IDOR protection). - S-4: All new input validated by shared Zod DTOs (FE/BE same schemas).
- S-5: Security-relevant operations emit
AuditLogevents (actor, resource, action, IP/UA, metadata). - S-6: No new secrets in code; no new external data exfiltration paths.
2. Per-feature threat analysis & controls
Spaces (directory, stats, cross-workspace move)
- Risk: directory/stats leak metadata about non-member spaces; cross-space move bypasses source/destination permissions.
- Controls:
GET /workspacesreturns only workspaces the user is a member of (same scoping as existing workspace APIs); stats computed only aftercanViewon the workspace; cross-workspace move requirescanEdit(source)andcanCreate(destination)in one service method; audit event with both workspace ids.
Page duplicate & bulk actions
- Risk: duplicate as read-only user (privilege escalation to edit), bulk delete of unauthorized pages, partial-failure state confusion.
- Controls: duplicate requires
canEditon source; new copies owned by actor (implicit viewer/edit per existing creator model); bulk action checks each id independently, returns per-id failure codes, never "all or nothing" silently; audit per batch + per failed item.
Backlinks / related pages
- Risk: backlink enumeration leaks the existence of pages the requester cannot view (an unauthorized page linking to a private page).
- Controls: backlink results filtered by
canViewon each source page before returning (fail-closed); the endpoint 404s if the target page itself is not viewable.
Review workflow
- Risk: approving as non-reviewer; state transition to skip review; deadline manipulation.
- Controls: transition matrix enforced in service (draft→in_review→
approved|changes_requested→published; only assigned reviewers can
approve/request-changes; submit requires
canEdit); every transition audited with before/after state; notifications only to reviewers + editor.
Tasks
- Risk: reading another user's unassigned/inbox tasks (privacy), due-date spam, reminder injection.
- Controls: inbox =
assigneeIds @ mescoped query; task pages get normal page permissions (sidecar query always re-checks the page grant); reminders capped (one per task per dueAt, idempotent job keytask-due:{taskId}:{dueAtMs}); dueAt validated (past dates rejected for new tasks, max horizon 1 year).
Whiteboards
- Risk: canvas JSON is user-controlled → stored XSS via rendered object labels; unbounded canvas size (DoS); schema confusion.
- Controls: canvas rendered as SVG with text nodes created via
textContent(neverinnerHTML); Zod schemaWhiteboardCanvasSchemavalidated on write and read boundary (defensive); hard limits: ≤ 5,000 objects, ≤ 5 MB canvas, freehand point decimation;schemaVersiongate — unknown version → read-only render with "unsupported version" banner (never silent data loss, spec §5). - Authorization: whiteboard = its page; all existing page permission checks apply untouched. Comments attach to the page (blockId null).
Templates & variables
- Risk: template injection (variables rendered into executable content), built-in template tampering, variable value overflow.
- Controls: variable substitution is plain string replacement into block
text — no code/HTML interpretation; variable values Zod-validated
(type/length per definition);
isBuiltintemplates cannot be edited or deleted by non-admins (server check); instantiation requirescanCreatein target location; audit on builtin template management.
Version compare & retention
- Risk: history as an exfiltration path after permission revocation (a revoked user comparing old versions of a page they can no longer view).
- Controls: compare/preview/restore all re-check
canView/canEditat request time (history never grants access); pruned revisions keep the author-ship audit trail. - Risk: retention pruning deleting a revision a user needs.
- Controls: retention = keep latest N + one per 7 days (configurable); pruning is a worker job with a 24 h grace + admin-visible audit event.
Search additions (recent, saved, related, reindex)
- Risk: recently-viewed of others leaked; saved search stores privileged query; reindex floods index with stale grants.
- Controls:
user_recent_itemsstrictly per-user (no cross-user query path exists); saved searches per-user; reindex rebuilds grant snapshots from live permissions (same code path as normal indexing — no new grant logic).
AI capabilities
- Risk (primary): AI retrieval bypassing permissions (data leak to the model or to the wrong user); fabricated citations; prompt content stored indefinitely.
- Controls (invariant L-1, reuses existing RAG enforcement):
- Retrieval set = intersection(requester's viewable docs, workspace
docs) computed via
packages/permissionsbefore any embedding lookup; cached embeddings carry their doc's grant snapshot and the answer path re-filters by current permissions (cached results never served without recheck — spec §15). - Tool outputs reference only
citedSourceIdsthe service resolved (no free-form URLs/ids in prompts). extract-actions/generate-faqswrite via normal domain services as the requesting user (their permissions, their ownership).- Logs: capability, token counts, latency, source doc ids — not full prompt bodies (existing AI logging policy, kept).
- Retrieval set = intersection(requester's viewable docs, workspace
docs) computed via
- Provider credentials: existing per-workspace config + env secrets; no change. No confidential doc leaves the configured provider boundary (existing policy, enforced by provider config being the only egress).
Notifications / activity / watch
- Risk: activity feed leaks actions on private pages to non-members; watch notification spam.
- Controls: feed queries filter events by the requester's view grants on
the event's resource (fail-closed: unviewable → excluded); watch fan-out
only to watchers who still have
canViewat fan-out time (recheck, not snapshot).
CSV import
- Risk: malicious CSV (formula injection in downstream export, size DoS, path traversal in filename).
- Controls: upload goes through the existing file pipeline (size cap, MIME check, scan); import parses server-side (no client JS eval); output file name sanitized (existing download path); cell values validated per property type; row count capped (default 10k, configurable).
Admin health tabs (jobs / search / notifications)
- Risk: org-admin surface exposure.
- Controls: org-admin role required (existing guard), read-only, all actions (reindex) audited; queue inspection is in-process read (no new credentials).
3. Attack-surface delta summary
| New exposure | Mitigation summary |
|---|---|
| 18+ new API endpoints | All behind existing auth + permission guard + Zod DTOs + rate limits; audit events on sensitive ones |
| 7 new tables | All workspace/user-scoped; no public surfaces; indexes on tenant columns |
| Whiteboard canvas content | SVG textContent render, schema-validated both boundaries, hard size/object limits |
| Template variables | Plain-text substitution, no interpretation; Zod-typed |
| AI capabilities | Permission-filtered retrieval + current-permission recheck + citation-only sources + write-as-requester |
| New events (webhooks) | Existing signed delivery; new event types cataloged in single source |
4. Test obligations (security)
Per spec §22: negative tests are mandatory for:
- Directory/stats/recent/feeds for a non-member → 404/empty (no leak).
- Backlinks on a page referencing a private page → private source filtered.
- Review: approve as non-reviewer → 403 + audit; skip-transition → 400.
- Duplicate/bulk as view-only → 403 per item with code.
- Version compare/preview after permission revocation → 403.
- Whiteboard: XSS vector in object label → escaped; oversize canvas → 413.
- CSV import: oversized, bad-type rows, formula-like cells → rejected/escaped.
- AI: user without view access to doc X → X never retrieved/cited (extend
existing
ai-security.spec.tspattern). - Cross-tenant: every new endpoint probed with a valid id from another workspace → 404 (no-leak, existing convention).
5. Residual risks (documented, accepted)
- Group (team) mentions deferred → team-level mention notifications absent this round (documented limitation).
- Whiteboard single-user → concurrent whiteboard edits resolve LWW on canvas save (documented; realtime follow-up A-004).
- Backlinks computed-scan has a brief staleness window after reference changes (cache TTL ≤ 60 s) — acceptable for a discovery feature.