.github/9
ISSUE_TEMPLATE/3bug_report.md config.yml feature_request.md
workflows/3ci.yml clean-clone.yml release.yml
CODEOWNERS dependabot.yml pull_request_template.md
apps/630
api/250
src/243
common/30
__tests__/3authorize.test.ts bigint-serialization.interceptor.test.ts files-limits.test.ts
filters/1all-exceptions.filter.ts
guards/3auth.guard.ts internal-service.guard.ts rate-limit.guard.ts
interceptors/2actor-context.interceptor.ts bigint-serialization.interceptor.ts
middleware/2idempotency.test.ts idempotency.ts
pipes/1zod-validation.pipe.ts
actor-context.ts audit.service.ts cache.test.ts cache.ts cookies.ts core.module.ts deprecation.ts email.service.ts errors.ts event-bus.ts log-scrubber.test.ts log-scrubber.ts logger.ts permission-cache-bridge.ts prisma.service.ts redis.service.ts trace-metadata.ts types.ts
config/1env.ts
lib/11
__tests__/5crypto.test.ts filter-builder.test.ts jwt.test.ts pagination.test.ts positions.test.ts
authorize.ts crypto.ts filter-builder.ts jwt.ts pagination.ts positions.ts
modules/197
ai/28
__tests__/4ai-writing.test.ts database-ai.test.ts generate-executor.test.ts prompts.test.ts
generator/4executor.ts generate.controller.ts generate.dto.ts generate.service.ts
ai-chat.service.ts ai-rate-limit.ts ai-writing.controller.ts ai-writing.dto.ts ai-writing.service.ts ai.controller.ts ai.module.ts ai.service.ts budget-guard.service.ts database-ai.controller.ts database-ai.dto.ts database-ai.service.ts db-prompts.ts dto.ts gateway-provider.ts ledger.service.ts prompts.ts rag.controller.ts rag.dto.ts rag.service.ts
api-keys/3api-keys.controller.ts api-keys.module.ts api-keys.service.ts
audit/2audit-log.controller.ts audit.module.ts
auth/4auth.controller.ts auth.module.ts auth.service.ts dto.ts
automations/5automations.controller.ts automations.module.ts automations.service.ts dto.ts webhook-incoming.controller.ts
blocks/5
__tests__/1batch-version-cas.test.ts
block.service.ts blocks.controller.ts blocks.module.ts dto.ts
comments/4comments.controller.ts comments.module.ts comments.service.ts dto.ts
databases/9
__tests__/3normalize-select-options.test.ts option-id-canonicalization.test.ts record-open-query-shape.test.ts
database.service.ts databases.controller.ts databases.module.ts dto.ts property-cache-schema.ts query.service.ts
docs/2docs.controller.ts docs.module.ts
files/10
__tests__/1quota.test.ts
dto.ts files.controller.ts files.module.ts files.service.ts quota.ts record.ts s3.ts uploads.controller.ts uploads.service.ts
forms/18debug.controller.ts form.dto.ts forms.controller.ts forms.module.ts forms.service.ts markdown.test.ts markdown.ts optional-auth.guard.ts public-forms.controller.ts public-forms.service.ts question.dto.ts questions.service.ts rate-limit.ts recaptcha.ts slug.ts submissions.controller.ts submissions.service.ts submit-schema.ts
health/3health.controller.ts health.module.ts health.service.ts
integrations/3integrations.controller.ts integrations.module.ts integrations.service.ts
internal/12
__tests__/2flush-query-shape.test.ts internal.endpoints.test.ts
ai-test-seam.controller.ts ai-test-seam.dto.ts ai-test-seam.service.ts internal.controller.ts internal.dto.ts internal.module.ts internal.service.ts test-seam.controller.ts test-seam.dto.ts test-seam.service.ts
notifications/3notifications.controller.ts notifications.module.ts notifications.service.ts
oauth/6
__tests__/1oauth.scopes.test.ts
oauth.controller.ts oauth.module.ts oauth.service.ts scopes.ts token-keys.ts
org/15audit.service.ts embed-preview.controller.ts guards.ts members.service.ts org-admin.controller.ts org-policy.service.ts org.module.ts overview.service.ts policy-effective.controller.ts policy.schema.ts policy.test.ts retention.service.ts totp.test.ts totp.ts two-factor.service.ts
orgs/5dto.ts orgs.controller.ts orgs.module.ts orgs.service.ts util.ts
pages/4dto.ts page.service.ts pages.controller.ts pages.module.ts
scim/15
__tests__/3email-mapping.test.ts etag.test.ts filters.test.ts
etag.ts filters.ts groups.service.ts schemas.ts scim-admin.controller.ts scim-admin.service.ts scim-auth.guard.ts scim-auth.service.ts scim-errors.ts scim.controller.ts scim.module.ts users.service.ts
search/4
__tests__/1search.service.test.ts
search.controller.ts search.module.ts search.service.ts
sessions/3sessions.controller.ts sessions.module.ts sessions.service.ts
share/3share.controller.ts share.module.ts share.service.ts
sso/16
__tests__/4enforcement.test.ts oidc.verify.test.ts provisioning.test.ts saml.validate.test.ts
enforcement.ts oidc.controller.ts oidc.service.ts oidc.ts provisioning.service.ts saml.controller.ts saml.ts schemas.ts sso-admin.controller.ts sso-admin.service.ts sso-secrets.ts sso.module.ts
teams/4dto.ts teams.controller.ts teams.module.ts teams.service.ts
users/4dto.ts users.controller.ts users.module.ts users.service.ts
webhooks/3webhooks.controller.ts webhooks.module.ts webhooks.service.ts
workspaces/4dto.ts workspaces.controller.ts workspaces.module.ts workspaces.service.ts
types/1xmldom.d.ts
app.module.ts constants.ts main.ts
_probe_rawbody.cjs .env.example Dockerfile package.json README.md tsconfig.json vitest.config.ts
realtime/33
src/17auth.ts comments.ts config.ts event-source.ts http-store.ts index.ts metrics.ts notifications.ts page-updated-source.ts presence.ts prisma-store.ts protocol.ts rate-limit.ts room-key.ts rooms.ts server.ts update-log.ts
test/11auth.test.ts config.test.ts event-source.test.ts http-store.test.ts metrics-merge.test.ts page-updated-source.test.ts presence.test.ts protocol.test.ts rate-limit.test.ts rooms.test.ts task-0094-builders.test.ts
.env.example Dockerfile package.json README.md tsconfig.json
web/255
src/244
app/45
__tests__/1theme-tokens.test.ts
(app)/26
w/25
[workspace]/25
ask/1page.tsx
databases/1
[id]/1page.tsx
favorites/1page.tsx
files/1page.tsx
notifications/1page.tsx
page/4
[id]/4
[block]/1page.tsx
share/1page.tsx
layout.tsx page.tsx
search/1page.tsx
settings/10
appearance/1page.tsx
automations/3
[id]/1page.tsx
new/1page.tsx
page.tsx
billing/1page.tsx
developers/1page.tsx
members/1page.tsx
permissions/1page.tsx
teams/1page.tsx
page.tsx
shared/1page.tsx
templates/1page.tsx
trash/1page.tsx
layout.tsx page.tsx
layout.tsx
(auth)/4
login/1page.tsx
register/1page.tsx
reset-password/1page.tsx
layout.tsx
api/2
metrics/1route.ts
v1/1
health/1route.ts
f/1
[slug]/1page.tsx
health/1
ready/1route.ts
org/2
[orgId]/2
admin/2
integrations/1page.tsx
page.tsx
share/1
[id]/1page.tsx
error.tsx global-error.tsx globals.css layout.tsx not-found.tsx page.tsx providers.tsx
components/118
admin/10
org-admin/8ApiKeysTab.tsx AuditTab.tsx MembersTab.tsx OverviewTab.tsx PoliciesTab.tsx RetentionTab.tsx WebhooksTab.tsx WorkspacePicker.tsx
ScimCard.tsx SsoTabs.tsx
ai/6ai-panel.tsx ask-ai-submenu.tsx db-ai-dialog.tsx generate-page-dialog.tsx writing-actions-menu.tsx writing-diff.tsx
automations/5action-editor.tsx condition-editor.tsx lib.ts run-history.tsx trigger-editor.tsx
collaboration/6CursorLayer.tsx OfflineBanner.tsx PresenceBar.tsx README.md ReviewFailedDialog.tsx StatusChip.tsx
comments/3comment-panel.tsx comment-thread.tsx mention-input.tsx
database/27
__tests__/2cell-editor.test.tsx cell-renderer.test.tsx
AiExtractDialog.tsx AiFormulaDialog.tsx AiGenerateDialog.tsx board-view.tsx buttons-panel.tsx calendar-view.tsx cell-editor.tsx cell-renderer.tsx chart-view.tsx database-page-view.tsx database-toolbar.tsx database-view-renderer.tsx db-embedded.tsx filter-builder.tsx form-builder-view.tsx formula-editor.tsx gallery-view.tsx group-builder.tsx list-view.tsx property-editor.tsx record-header.tsx record-panel.tsx sort-builder.tsx table-view.tsx timeline-view.tsx
dialogs/7
__tests__/1move-to-dialog.test.tsx
create-dialog.tsx dialog.tsx file-permissions-dialog.tsx invite-dialog.tsx move-to-dialog.tsx share-dialog.tsx
editor/19
__tests__/3code-block-text.test.ts code-block.test.tsx expandable.test.tsx
block-card.tsx block-renderer.tsx block-save-queue.ts block-toolbar.tsx context-menu.tsx expandable.tsx history-popover.tsx inline-editor.tsx insert-dialog.tsx page-canvas.tsx page-spinner.tsx page-view.tsx selection-toolbar.tsx slash-menu.tsx template-dialog.tsx type-menu.tsx
files/4FileBlock.tsx FileBrowser.tsx FilePicker.tsx PreviewModal.tsx
form/4form-field.tsx form-renderer.tsx form-responses-view.tsx form-view.tsx
layout/8
__tests__/1page-tree-icon.test.tsx
command-palette.tsx factory-logo.tsx page-tree-node.tsx sidebar.tsx theme-provider.tsx topbar.tsx workspace-shell.tsx
mentions/3
__tests__/1mention-model.test.ts
mention-model.ts MentionPopup.tsx
search/3ask-ai-search.tsx results-list.tsx search-dialog.tsx
settings/10
api-keys/4ApiKeyCreateDialog.tsx ApiKeyRevealDialog.tsx ApiKeyTable.tsx ScopesBadge.tsx
webhooks/5CurlExamples.tsx DeliveryInspector.tsx EventExplorer.tsx OverviewTab.tsx WebhooksTab.tsx
settings-shell.tsx
ui/3confirm-dialog.tsx form.tsx toaster.tsx
hooks/21
__tests__/4realtime-frame-handler.test.ts use-blocks.test.ts use-database-options.test.ts use-workspace.test.tsx
realtime-frame-handler.ts use-ai-stream.ts use-auth.ts use-blocks.ts use-database.ts use-db-ai-stream.ts use-forms.ts use-members.ts use-mention-sources.ts use-notifications.ts use-offline-status.ts use-pages.ts use-presence-announcer.ts use-presence-toasts.ts use-presence.ts use-realtime.ts use-workspace.ts
lib/50
__tests__/21ai-database.test.ts ai-generate.test.ts ai-sse.test.ts ai-writing.test.ts api-keys.test.ts api-members.test.ts auth-refresh.test.ts auth-store.test.ts batch-queue-drop.test.ts block-content.test.ts clipboard-import.test.ts code-highlight.test.ts offline-status.test.ts presence-announcer.test.ts presence-color.test.ts presence-cursor.test.ts presence-emitter.test.ts realtime-url.test.ts search.test.ts utils.test.ts writing-diff.test.ts
files/7
__tests__/2picker.test.ts upload.test.ts
api.ts picker.ts upload-store.ts upload.ts use-file-uploads.ts
ai-database.ts ai.ts api-keys.ts api.ts auth-store.ts batch-queue.ts block-content.ts clipboard-import.ts code-highlight.ts constants.ts offline-status.ts org-admin.ts presence-announcer.ts presence-color.ts presence-cursor.ts presence-editor-views.ts presence-emitter.ts query-client.ts realtime.ts search.ts types.ts utils.ts
stores/8
__tests__/1block-patch-store.test.ts
block-patch-store.ts comment-pill-store.ts database-store.ts editor-store.ts offlineQueue.ts presence-store.ts ui-store.ts
instrumentation.ts middleware.ts
.env.example Dockerfile next-env.d.ts next.config.mjs package.json postcss.config.mjs README.md tailwind.config.ts tsconfig.json tsconfig.tsbuildinfo vitest.config.ts
worker/92
src/87
__tests__/13ai-embedding.test.ts ai-request.test.ts ai-usage-budget.test.ts audit-export.test.ts email-decision.test.ts encrypt.test.ts health-search-index-lag.test.ts mime.test.ts retention.test.ts s3-sign.test.ts search-index.test.ts smtp.test.ts webhook-sign.test.ts
health/1server.ts
lib/12
__tests__/1clamav.test.ts
clamav.ts encrypt.ts job-otel.ts mime.ts notify.ts prisma.ts redis-stream.ts redis.ts s3.ts sharp.ts smtp.ts
processors/56
automations/28
__tests__/3condition-evaluator.spec.ts executors.spec.ts run-pipeline.spec.ts
executors/17assign-user.ts call-integration.ts change-status.ts classify.ts create-notification.ts create-page.ts email-cap.ts helpers.ts integration-registry.ts permission.ts record-write.ts registry.ts run-ai-action.ts send-email.ts send-webhook.ts types.ts update-record.ts
condition-evaluator.ts dead-letter.ts log.ts matcher.ts pipeline.ts run-state.ts schedule.ts watchdog.ts
files/15
__tests__/4errors.test.ts scan-gate.test.ts status.test.ts temp.test.ts
steps/4finalize.ts metadata.ts scan.ts thumbnails.ts
cleanup.ts errors.ts generateThumbs.ts processor.ts status.ts temp.ts types.ts
search-index/4builders.ts grants.ts index.ts processor.ts
ai-embedding.ts ai.processor.ts audit-export.ts automations.processor.ts email-decision.processor.ts email.processor.ts maintenance.processor.ts retention.ts webhooks.processor.ts
config.ts events.ts index.ts logger.ts queues.ts
.env.example Dockerfile package.json README.md tsconfig.json
docs/73
api/17ai.md authentication.md automations.md blocks.md comments.md databases.md files.md forms.md openapi.yaml org-admin.md pages.md scim.md search.md sso.md versioning.md webhooks.md workspaces.md
architecture/10ai-architecture.md automation.md backend-architecture.md database-architecture.md frontend-architecture.md realtime-architecture.md search-architecture.md security-architecture.md storage.md system-architecture.md
database/2database-architecture.md redis-keys.md
development/8admin-guide.md coding-standards.md deployment.md development-guide.md files-pipeline.md perf-report.md performance.md testing.md
product/4functional-requirements.md non-functional-requirements.md product-requirements.md ux-requirements.md
requirements/1requirements-matrix.md
security/5audit-report.md files.md forms.md license-allowlist.md security-architecture.md
tasks/16phase-00-foundation.md phase-01-authentication.md phase-02-workspace.md phase-03-pages.md phase-04-editor.md phase-05-databases.md phase-06-relations.md phase-07-collaboration.md phase-08-search.md phase-09-files.md phase-10-forms.md phase-11-automation.md phase-12-ai.md phase-13-api.md phase-14-enterprise.md phase-15-production.md
ui/9ai.md automations.md design-system.md developers.md files.md forms.md org-admin.md scim.md sso.md
support-matrix.md
infrastructure/71
caddy/1Caddyfile
docker/6
init/101-extensions.sql
opensearch/1init-indices.json
.env.docker docker-compose.yml opensearch-init.sh README.md
kubernetes/40
observability/20
alertmanager/1alertmanager.yaml
grafana/8
dashboards/6ai.json api-overview.json infra.json realtime.json search.json workers-queues.json
provisioning/2
dashboards/1dashboards.yaml
datasources/1datasource.yaml
prometheus/3alerts.yaml recording-rules.yaml scrape.yaml
scripts/1sync-sources.mjs
alertmanager.yaml grafana.yaml kustomization.yaml namespace.yaml node-exporter.yaml otel-collector.yaml prometheus.yaml
postgres/7configmap.yaml cronjob-backup.yaml cronjob-opensearch-snapshot.yaml pgbouncer.yaml replica.yaml statefulset.yaml walg-iam-policy.yaml
api.yaml configmap.yaml ingress.yaml kustomization.yaml namespace.yaml networkpolicy.yaml pdb.yaml README.md realtime.yaml redis.yaml secret.example.yaml web.yaml worker.yaml
nginx/2nginx.conf README.md
observability/14
alertmanager/1alertmanager.yaml
grafana/8
dashboards/6ai.json api-overview.json infra.json realtime.json search.json workers-queues.json
provisioning/2
dashboards/1dashboards.yaml
datasources/1datasource.yaml
prometheus/3alerts.yaml recording-rules.yaml scrape.yaml
scripts/1dashboard-completeness.mjs
README.md
terraform/8compute.tf data.tf main.tf network.tf outputs.tf README.md security.tf variables.tf
packages/289
ai/24
src/20
providers/7anthropic.ts google.ts http.ts index.ts openai-compatible.ts rag.test.ts writing.test.ts
schemas/2database.ts generate.ts
adapters.test.ts config.test.ts config.ts gateway.test.ts gateway.ts index.ts tools.test.ts tools.ts types.ts usage.test.ts usage.ts
package.json README.md tsconfig.json vitest.config.ts
auth/11
src/7index.ts passwords.test.ts passwords.ts sessions.test.ts sessions.ts tokens.test.ts tokens.ts
package.json README.md tsconfig.json vitest.config.ts
collaboration/17
src/13awareness.ts codec.test.ts codec.ts index.ts lww.test.ts lww.ts mapping.ts offline.ts outbox.test.ts outbox.ts provider.test.ts provider.ts types.ts
package.json README.md tsconfig.json vitest.config.ts
config/7
src/4eslint.base.js index.ts prettier.base.json tsconfig.base.json
package.json README.md tsconfig.json
database-client/5
src/2index.test.ts index.ts
package.json README.md tsconfig.json
database-engine/33
src/29
filter/2filter.test.ts index.ts
formula/8evaluator.ts formula.test.ts functions.ts index.ts lexer.ts parser.ts types.ts validate.ts
query/6group.ts index.ts paginate.ts pivot.ts query.test.ts sort.ts
relations/2index.ts relations.test.ts
rollups/2index.ts rollups.test.ts
values/5dates.ts index.ts registry.ts types.ts values.test.ts
views/3buttons.ts index.ts views.test.ts
index.ts
package.json README.md tsconfig.json vitest.config.ts
editor/25
src/21
marks/1highlight.ts
nodes/2equation.ts mention.ts
autosave.test.ts autosave.ts clipboard.test.ts clipboard.ts codec.test.ts codec.ts context-menu.test.ts context-menu.ts diff.test.ts diff.ts factory.ts history.test.ts history.ts index.ts positions.ts slash.test.ts slash.ts types.ts
package.json README.md tsconfig.json vitest.config.ts
observability/20
src/16
__tests__/8db-redis-metrics.test.ts metrics.test.ts otel.test.ts pino-otel.test.ts prometheus.test.ts resource.test.ts scrubber.test.ts traceparent.test.ts
index.ts metrics.ts otel.ts pino-otel.ts prometheus.ts resource.ts scrubber.ts traceparent.ts
package.json README.md tsconfig.json vitest.config.ts
permissions/11
src/7index.ts levels.test.ts levels.ts resolve.test.ts resolve.ts rules.test.ts rules.ts
package.json README.md tsconfig.json vitest.config.ts
search/22
src/18chunking.test.ts chunking.ts client.test.ts client.ts embeddings.test.ts embeddings.ts hybrid.test.ts hybrid.ts index.ts indexer.test.ts indexer.ts mapping.test.ts mapping.ts queries.test.ts queries.ts rrf.test.ts rrf.ts types.ts
package.json README.md tsconfig.json vitest.config.ts
shared/61
src/57
dto/17ai.dto.ts auth.dto.test.ts auth.dto.ts block.dto.ts browser-safety.test.ts comment.dto.ts database.dto.test.ts database.dto.ts file.dto.ts formula-ast.ts index.ts notification.dto.ts page.dto.ts search.dto.ts share.dto.ts webhook.dto.ts workspace.dto.ts
events/8audit-catalog.test.ts audit-catalog.ts audit-csv.test.ts audit-csv.ts audit-scope.test.ts audit-scope.ts catalog.test.ts catalog.ts
realtime/6comment-events.ts frames.test.ts frames.ts notification-policy.ts notification-prefs.ts task-0094.test.ts
automation.test.ts automation.ts constants.ts crypto.test.ts crypto.ts env.ts errors.ts events.test.ts events.ts forms.test.ts forms.ts index.ts markdown.test.ts markdown.ts mentions.test.ts mentions.ts net.test.ts net.ts positions.test.ts positions.ts presence.test.ts presence.ts security-headers.ts webhook-delivery.test.ts webhook-delivery.ts webhook-events.ts
package.json README.md tsconfig.json vitest.config.ts
storage/16
src/12client.test.ts client.ts index.ts keys.test.ts keys.ts mime.test.ts mime.ts presign.test.ts presign.ts thumbnails.test.ts thumbnails.ts types.ts
package.json README.md tsconfig.json vitest.config.ts
ui/37
src/33
components/19avatar.tsx badge.tsx button.tsx checkbox.tsx command.tsx context-menu.tsx dialog.tsx dropdown-menu.tsx empty-state.tsx index.ts popover.tsx separator.tsx skeleton.tsx slider.tsx spinner.tsx switch.tsx tabs.tsx toast.tsx tooltip.tsx
color.ts command-filter.test.ts command-filter.ts focus.test.ts focus.ts formatters.test.ts formatters.ts index.ts theme.test.ts theme.ts toast-store.test.ts toast-store.ts tokens.test.ts tokens.ts
package.json README.md tsconfig.json vitest.config.ts
prisma/16
migrations/13
20260927000000_init/1migration.sql
20260928000000_search_documents_content_hash/1migration.sql
20260929000000_workspace_usage_storage_bytes/1migration.sql
20260930000000_form_submitted_notification_type/1migration.sql
20261001000000_ai_phase12/1migration.sql
20261002000000_webhooks_secret_encrypted_dlq/1migration.sql
20261002111436_webhook_delivery_latency/1migration.sql
20261002120000_org_admin_2fa_retention_unique/1migration.sql
20261002215124_audit_export/1migration.sql
20261003210000_perf_indexes_hot_paths/1migration.sql
20261004004500_perf_indexes_hot_paths_2/1migration.sql
migration_lock.toml README.md
schema.prisma seed.ts verify.ts
scripts/7backup-health.ts docs-completeness.mjs generate-openapi.mjs minio-init.ts opensearch-reindex.ts storage-orphans.ts verify-clean-clone.sh
tests/169
e2e/63
.auth/1user.json
fixtures/7
files/6clip.mp4 clip.wav clip.webm hero-1400.png note.docx note.txt
realtime.ts
mocks/2oidc-issuer.ts saml-idp.ts
playwright-report/3
data/2a6557bae9c2714a658968d4eb8534195c08ebda8.png e3092d07d00a16063e46385c02a559a9054fa847.webm
index.html
reports/1files-security.json
specs/34ai-database.spec.ts ai-generate.spec.ts ai-rag.spec.ts ai-security.spec.ts ai-writing.spec.ts ai.spec.ts api-keys.spec.ts appearance-theme.spec.ts auth.spec.ts automation.spec.ts collab-two-tab.spec.ts comments.spec.ts databases.spec.ts default-workspace-alias.spec.ts developers.spec.ts editor.spec.ts files.spec.ts final-flow.spec.ts forms.spec.ts oauth.spec.ts org-admin.spec.ts pages.spec.ts performance.spec.ts presence-cursors.spec.ts qa-paste-toggle.spec.ts realtime.spec.ts scim.spec.ts search-index-lag.spec.ts search.spec.ts sharing.spec.ts sso.spec.ts task-0094-comments-notifications.spec.ts webhooks.spec.ts workspace.spec.ts
test-results-final-flow/3
final-flow-Release-v1-0-fi-6c06b-w-state-carries-sequential--chromium-desktop/2test-failed-1.png trace.zip
.last-run.json
utils/4api.ts dnd.ts search-lag.ts wait.ts
.gitignore fixtures.ts global-setup.ts package.json playwright.config.ts playwright.final-flow.config.ts README.md tsconfig.json
fixtures/1
files/1make-sparse.ts
integration/60
ai-capability/2ai-app.capability.test.ts gateway.capability.test.ts
drill/2seed-drill.mjs verify-restore.mjs
fixtures/5
files/5clip.mp4 clip.wav doc.pdf hero-1400.png note.txt
helpers/3api.ts containers.ts seed.ts
realtime/11control-channel.test.ts convergence.test.ts flush-rebase-http.test.ts handshake.test.ts heartbeat-limits.test.ts lifecycle.test.ts presence.test.ts rebase.test.ts resume.test.ts rt-helpers.ts task-0094-fanout.test.ts
ai-chat.integration.test.ts ai-database.integration.test.ts ai-generate.integration.test.ts ai-rag.integration.test.ts ai-writing.integration.test.ts api-keys.integration.test.ts audit-completeness.integration.test.ts audit-export-s3.integration.test.ts auth.integration.test.ts automation-pipeline.integration.test.ts automations.integration.test.ts blocks.integration.test.ts cache-invalidation.integration.test.ts comments.integration.test.ts databases.integration.test.ts files-lifecycle.integration.test.ts files-pipeline.integration.test.ts files-security.integration.test.ts files.integration.test.ts forms-responses.integration.test.ts forms.integration.test.ts oauth.integration.test.ts openapi.contract.test.ts org-admin.integration.test.ts package.json pages.integration.test.ts permissions.integration.test.ts README.md replica-routing.integration.test.ts retention-engine.integration.test.ts scim.integration.test.ts search.integration.test.ts sso.integration.test.ts tsconfig.json vitest.config.ts webhooks.integration.test.ts workspace.integration.test.ts
performance/28
fixtures/1search-100k.ts
helpers/2seed-100k.mjs seed-1k-load.mjs
reports/7api-load-smoke.json database-load-smoke.json editor-local.json realtime-load.json search-full.json search-last.json search-reduced.json
api-load.js api.js archive.tar database-load.js database.js editor-local.mjs extract-100k-env.mjs package.json README.md realtime-1k.js realtime-load.js realtime.js run-realtime-load.sh search-search.k6.js search-summary.json seed-100k.mjs threshold-breach-demo.js verify-zero-op-loss.mjs
security/16
helpers/1app.ts
audit-report.test.ts auth-paths.test.ts deps.test.ts formula-fuzz.test.ts gitleaks-config.test.ts headers.test.ts images.test.ts oracle.ts package.json permission-fuzz.test.ts rate-limits.test.ts README.md tsconfig.json upload-abuse.test.ts vitest.config.ts
README.md
.dockerignore .env .env.bak-task0125 .env.example .eslintrc.json .gitignore .npmrc .prettierignore .prettierrc.json AGENTS.md ARCHITECTURE.md CHANGELOG.md CONTRIBUTING.md gitleaks.toml LICENSE package.json pnpm-lock.yaml pnpm-workspace.yaml PRODUCT-SPEC.md README.md ROADMAP.md SECURITY.md tsconfig.json turbo.json
security-architecture.mddocs/architecture/security-architecture.md← architecture46,008 B← Back to the app

Security Architecture

Threat model, controls, and operations. Sibling docs: system · backend · realtime · ai.

Top invariant (repeated from AGENTS.md, normative): never trust frontend authorization. Every backend operation re-validates server-side. The UI hides what you can't touch; only the server decides what you can touch.


1. Authentication

1.1 Token model

Token Form Lifetime Storage Purpose
Access token JWT (HS256, packages/auth), claims {sub: userId, wid: workspaceId, email, role, iat, exp, iss: "factory", jti} 15 min JS memory (not localStorage — XSS-resistant); optionally a non-HttpOnly cookie for SSR reads, plus the HttpOnly refresh cookie as the source of truth REST auth (Authorization: Bearer), realtime WS handshake
Refresh token Opaque 256-bit random string; SHA-256 hash stored in refresh_tokens (raw value never persisted) 30 days, sliding (each use renews the window) HttpOnly, Secure, SameSite=Lax, Path=/ cookie factory_rt Silent session renewal; rotation
Session sessions row per login (device) 30 d or revoked DB (tokenHash, userAgent, ipAddress, status) "active devices" UI, revocation, abuse detection
API key fk_live_… 40-byte random; SHA-256 hash stored (api_keys.keyHash), raw shown once until expiry/revocation client (env/header) public API auth (Authorization: Bearer fk_live_…)
Share token 256-bit random, shares.token (raw, unique index — the capability IS the token) until expiry/revocation URL public share pages (/share/:token), optional bcrypt password
SSO session IdP assertion (SAML) / OIDC code single use sso_identities links IdP externalId → User enterprise login

1.2 Full session lifecycle (login → rotation → revocation)

┌─ LOGIN ─────────────────────────────────────────────────────────────────────────┐
│ 1. POST /v1/auth/login {email, password}                                        │
│    → rate limit (10/15 min/IP, 5/15 min/account)                                │
│    → constant-time lookup + bcrypt.compare (cost 12; dummy-hash compare when     │
│      user unknown — no timing user-existence oracle)                            │
│    → user status checks: SUSPENDED → 403; UNVERIFIED → 403 EMAIL_NOT_VERIFIED   │
│      (login allowed only for verified or grace-period accounts)                 │
│ 2. create:  Session row (ACTIVE, ua, ip)  +  RefreshToken row (hash, 30 d)      │
│    → audit user.login {ip, ua, sessionAgent}                                    │
│ 3. response: Set-Cookie factory_rt=<opaque>; HttpOnly; Secure; SameSite=Lax      │
│    + JSON body: { accessToken (15 m JWT), user: UserDto }                       │
└──────────────────────────────────────────────────────────────────────────────────┘
┌─ USAGE ─────────────────────────────────────────────────────────────────────────┐
│ REST: Authorization: Bearer <JWT> (JwtAuthGuard: sig/exp/iss) · WS: token=<JWT> │
│ in the upgrade query (auth bridge, realtime §2.1). The JWT's wid/role claims are │
│ convenience only — the guard RE-DERIVES membership for every decision.          │
└──────────────────────────────────────────────────────────────────────────────────┘
┌─ REFRESH ROTATION (BFF or /v1/auth/refresh) ────────────────────────────────────┐
│ 1. POST /v1/auth/refresh  (cookie factory_rt)                                   │
│ 2. h = SHA256(raw); row = refresh_tokens WHERE tokenHash = h                    │
│ 3. row missing → 401 (token never seen: replay after revocation or forged)      │
│    row.revokedAt set AND this is a REUSE (raw != current expected)              │
│      → SECURITY EVENT: the whole session chain is compromised:                  │
│        revoke ALL of the user's sessions + refresh tokens (family kill),        │
│        audit auth.refresh.reuse_detected, notify user (email), 401              │
│    row.revokedAt set but expected rotation chain (replacedBy == row.id)         │
│      → 401 (stale retry of an already-rotated token — client re-logins)         │
│ 4. else: mark row.revokedAt = now, row.replacedBy = newId                       │
│    insert new RefreshToken (hash, expires = now + 30 d)                         │
│    mint new JWT (fresh wid/role claims — role changes take effect on next       │
│    rotation, ≤ 15 min)                                                          │
│ 5. audit auth.refresh (session id chain for the device list)                    │
└──────────────────────────────────────────────────────────────────────────────────┘
┌─ REVOCATION ────────────────────────────────────────────────────────────────────┐
│ • logout POST /v1/auth/logout → refresh row revoked + session REVOKED           │
│ • DELETE /v1/auth/sessions/:id revokes one device; "sign out everywhere" = all  │
│ • admin suspends a user → all sessions revoked + JWT denylist:                   │
│   Redis SET jwt:deny:<jti> TTL=remaining lifetime, checked in JwtAuthGuard      │
│   (one GET, 5 s cache) — closes the ≤15-min access-token window for             │
│   SUSPENDED/revoked users on state-changing ops                                 │
└──────────────────────────────────────────────────────────────────────────────────┘

Why opaque + rotating: a stolen refresh token is single-use and its reuse is detectable (family kill above); DB rows give per-device revocation ("active devices"); the short access JWT makes a stolen token worthless after 15 min.

Sensitive-op double check: operations that must honor revocation immediately (password change, permission change, workspace delete, API key creation) run Session.isActive(userId) in the same transaction — a user who just logged out can't land these even with an unexpired JWT.

1.3 Passwords & recovery

1.4 SSO (SAML / OIDC) & SCIM (Phase 14)

1.5 API-key scope enforcement (TASK-0127 — the no-creator-leak rule)

An API key is a workspace credential, not a user credential. The enforcement contract (normative; proven by tests/integration/api-keys.integration.test.ts):

Implementation: the actor is bound once per request (the outermost ActorContextInterceptor → AsyncLocalStorage); assertCan/assertRole and the actorLevel/actorMembership/actorIsAdminKey helpers in lib/authorize.ts read it. A request with kind: "api" takes the API-key branch before any membership lookup — the leak is structurally impossible, not merely prevented at each call site.


2. Authorization

2.1 The two layers

Layer Data Answers
RBAC (workspace roles) workspace_members.role ∈ OWNER/ADMIN/MEMBER/GUEST (+ org roles in organization_members) default level for workspace-level resources; who can manage members/roles/settings
Resource permissions permissions rows: grantee (user/team/role) × resource (workspace/teamspace/page/database/record/file) × level (NONE/VIEW/COMMENT/EDIT/FULL_ACCESS) per-resource overrides and extensions, with inheritance down the page tree

Level ordering: NONE < VIEW < COMMENT < EDIT < FULL_ACCESS. EDIT implies COMMENT and VIEW; FULL_ACCESS adds: share management, permission grants, delete/purge, move to trash.

Role floors (workspace-level only — the role is a floor for workspace-wide content, not a ceiling):

Role Floor on workspace content Extra rights
OWNER FULL_ACCESS transfer ownership, delete workspace, billing
ADMIN EDIT manage members/roles, manage shares at workspace level, settings, integrations, API keys, automations, templates
MEMBER EDIT (non-private pages; own content) create/edit own + shared content, comment anywhere editable
GUEST VIEW view shared content, comment where COMMENT granted

2.2 Evaluation algorithm (normative — one implementation, packages/permissions)

effectiveLevel(ctx, R ∈ {type, id, workspaceId}):
  # 1. identity
  G = { ("user", ctx.userId) } ∪ { ("team", t.id) for t in teamsOf(ctx.userId, workspace) }
      ∪ { ("role", roleOf(ctx.userId, workspace)) }        # workspace role as grantee

  # 2. collect the resource chain (top-down):
  #    record → its page (record=page, ADR-006) → parent pages … → workspace root
  #    database → embedding page (databases.pageId) → … → workspace root
  #    file   → owning page/block's page → … → workspace root
  #    teamspace/team → workspace root
  #    (max depth 50; cycle guard on pageId walk)
  chain = [R, parent(R), …, workspace(R)]

  # 3. grants for this grantee-set, for EVERY resource in the chain (one indexed
  #    query: WHERE workspace_id = w AND (grantee_type,grantee_id) IN G
  #           AND (resource_type,resource_id) IN chain)  — cached 60 s (§2.4)
  # 4. explicit deny: if any grant in the chain has level = NONE → return NONE
  #    (explicit NONE at ANY level of the chain vetoes everything below it)
  # 5. level = max of all grants found (NONE contributes nothing)
  # 6. if no grants found: level = roleFloor(ctx.role)  (workspace resources only;
  #    for deep resources with no grants at all, the floor applies through the
  #    page's isPrivate flag: isPrivate page → floor collapses to the creator +
  #    explicit grants)
  # 7. owner bypass: workspace OWNER → FULL_ACCESS (except: owner cannot grant
  #    FULL_ACCESS to others — grant cap for owners is EDIT, admin grants FULL)
  # 8. trashed: R.page.trashedAt set → cap at VIEW
  # 9. cache + return { level, reason }   (reason ∈ no_grant | explicit_none |
  #    role_floor | owner_bypass | trashed_cap | grant_max)

Inheritance semantics, precisely:

2.3 Guard integration (backend) & the decision log

2.4 Cache & invalidation

Permission decisions: Redis perm:{wid}:{resType}:{resId}:{granteeHash} TTL 60 s; permission.changed event → DEL prefix perm:{wid}:* (bounded: workspaces have thousands of decisions, not millions). Membership/role changes emit workspace.member.* events → same invalidation. Worst-case staleness 60 s; the sensitive-op double check (§1.2) never relies on the cache.


3. Transport Security

3.1 TLS

3.2 Cookies

Cookie Value Flags
factory_rt opaque refresh token HttpOnly; Secure; SameSite=Lax; Path=/; Max-Age=2592000
factory_csrf random 128-bit (double-submit) Secure; SameSite=Lax; Path=/ (NOT HttpOnly — JS reads it to echo)
factory_theme theme pref Secure; SameSite=Lax (non-sensitive)

3.3 CORS

Origin policy Rules
API (/v1) no Access-Control-Allow-Origin: * ever. Allowed origins: the app origin (env WEB_ORIGIN) + workspace custom domains (publicUrl, validated against a per-org allowlist at save time). Allow-Credentials: true only for the app origin (cookie flows). Allow-Methods: GET,POST,PATCH,DELETE,OPTIONS; Allow-Headers: Content-Type, Authorization, X-CSRF-Token, X-Request-Id; Max-Age: 600.
Realtime WS Origin checked at upgrade: must match app origin or workspace domain, else 403 close 4403.
Share pages / forms same-origin (served by the app) — no CORS needed; file preview proxy is same-origin.
Public API consumers they call with Authorization (no cookies) and need no CORS at all; browsers calling cross-origin without credentials get allow-origin of the requesting origin reflected only for Bearer-authed requests (safe: no credentials cross) — this is what makes the public API usable from serverless functions and admin tools.

3.4 Security headers (nginx + Next, defense in depth)

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-eval'
    # 'unsafe-eval' scoped: required by Next.js dev only — PRODUCTION build ships
    # WITHOUT unsafe-eval (Next 14 production compiles away eval; the dev CSP is a
    # separate env-driven policy).  img-src 'self' data: blob: <signed file host>;
    # connect-src 'self' wss:<host>; frame-ancestors 'none';
    # base-uri 'self'; form-action 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY            (frame-ancestors 'none' in CSP — no clickjacking)
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
Cache-Control: no-store          (authed JSON; public assets: long cache + hash)

4. Input Validation & Sanitization

4.1 The pipeline

HTTP body/query/params
  → ZodValidationPipe (packages/shared schemas — the SAME schemas the client uses)
      • type coercion, trimming, enum checks, cuid shape, JSONB deep schemas
        (BlockContentSchema, FilterTreeSchema, view configs, formula source text)
  → service-level domain validation (invariants Zod can't express: parent exists,
      option belongs to the select, relation cardinality, formula AST parse)
  → Prisma (parameterized — no string SQL in app code; raw SQL only in migrations)
  → (for text entering collaborative content) the editor boundary sanitizers

4.2 Per-input-class rules

Class Control
REST/WS JSON Zod schemas only; unknown keys stripped (.strict() on security-sensitive DTOs: auth, permissions, webhooks, api keys); numbers bounded (pageSize ≤ 100, batch ≤ 500); strings length-capped (title ≤ 200, comment ≤ 20k, formula ≤ 2k)
SQL Prisma parameterization only; no query( with interpolation; the one raw-SQL usage (migrations) is code-reviewed; search queries are built by packages/search from typed params (OpenSearch query DSL, not string concat)
Command/OS none exist — no shell-outs in any service (the file pipeline uses SDKs: sharp/libheif for images, a pure-JS PDF text extractor, no ffmpeg exec — thumbnails are image-only; video "thumbnails" = a static poster, Phase 9 decision)
Formula input safe parser → AST → bounded evaluator (10k step limit, 1 MB output cap, fixed function whitelist, no property access beyond prop()); never eval/Function — CI greps the whole repo for eval(, new Function(, vm.runIn (allowlist: none)
Output (JSON API) serializers whitelist fields (entity → DTO); no internal ids (S3 keys, token hashes, secret refs) in any DTO; requestId is the only cross-reference to logs
HTML/JSX rendering (web) React escapes by default; the only dangerouslySetInnerHTML is the comment markdown renderer (markdown-it with html: false, linkify sanitized to https?: scheme + rel="noopener") — no raw HTML ever from user content
Editor content (blocks) text is stored as structured runs (Tiptap marks), not HTML; paste-from-web is parsed through the importer (HTML → AST → blocks, tags/attrs allowlisted, styles dropped except a 40-color map); content JSONB is Zod-validated per block type on write, so a stored block can't carry an unexpected shape
File names length ≤ 255, control chars stripped, path traversal chars removed (.., /, \); the S3 key is server-generated ({wid}/{fileId}/{version}/{hash}.{ext}) — user names never touch the storage key
URLs (bookmarks, embeds, webhooks, file) scheme allowlist (https:, mailto: for mentions); webhook URLs additionally must be https: (public internet) or RFC1918 with an explicit org-policy opt-in (self-hosted LAN) — SSRF control, §6.4
Email/phone inputs format-validated (Zod) + lowercased; email is the unique key (case-insensitive via stored lowercase)

4.3 Content sanitization & XSS classes

Vector Control
Stored XSS (block content) content is structured (no HTML field); markdown rendered with html:false; mention/link marks carry only {href (validated), label, id}
Stored XSS (comments / share pages) markdown-it html:false; share pages add X-Frame-Options: DENY + CSP default-src 'self' (no scripts)
DOM XSS no innerHTML assignment of user data anywhere (lint no-imperative-dom); URL params Zod-parsed before use (/share/[token] shape-validated)
Prototype pollution Zod objects (no __proto__ survives parse — .strict() + key allowlists); deep-merge of untrusted objects banned (lint)
Open redirect next.redirect targets validated against app origin/workspace domain; url cells and bookmarks are display-only (clicks go through the app's link handler, rel=noopener)

5. Rate Limiting & CSRF

5.1 Rate limiting (summary — full table in backend, §10)

Redis sliding window (atomic Lua), scopes: auth (per-IP + per-account), per-user API, per-API-key (per-key override), per-IP unauthenticated, AI (per-user + budget), form submissions (per-form per-IP), realtime connects (per-IP per-user). 429 carries Retry-After; all 429s metered (rate_limited_total{scope}) and alerted.

5.2 CSRF


6. File Security

6.1 Upload pipeline (defense at each stage)

1. REQUEST  POST /v1/files {name, size, mimeType, blockId?/pageId?}
   • permission EDIT on target · plan caps (maxFileSizeMb default 200, 5 GB hard cap,
     >100 MB → multipart) · name/mime sanity → File row (UPLOADING) + presigned PUT
     (15 min TTL); S3 key server-generated: {wid}/{fileId}/{v}/{sha1-prefix}.{safe-ext}
2. UPLOAD   browser → S3 directly; presign policy locks Content-Length + Content-Type
   (the client cannot rewrite the type to smuggle)
3. PIPELINE (worker, queue 'files')
   a. VERIFY: re-fetch; sha256 (presign-mandated x-amz-meta) matches — mismatch →
      FAILED, object deleted
   b. MIME SNIFF: magic bytes (NOT the declared mimeType): mismatch → reject.
      Supported: png/jpeg/gif/webp/heic/avif, pdf, mp4/webm, mp3/m4a/wav,
      docx/xlsx/pptx, csv/tsv/json/md/txt, zip, 7z. Sniffed executable/script types
      (exe, dll, extensionless js, sh, bat, html-as-file) REFUSED outright —
      double-extension trick killed (sniffed type always wins)
   c. CLAMAV: per-file scan (sidecar, 120 s timeout) → ANY detection:
      VIRUS_DETECTED, object kept 7 d forensically then deleted, audit + alert
   d. METADATA: dimensions / duration (header parse, no decode) / page count /
      text extraction (pdf/docx → search, 1 MB cap)
   e. THUMBNAILS: sharp → thumbs bucket (400×400 webp + 1200px hero); text: icon only
   f. READY: → event file.uploaded (search + UI)
Status UI Served?
UPLOADING spinner no
SCANNING spinner no
READY normal yes (signed URL)
VIRUS_DETECTED "File blocked — virus detected" never (download + preview both 403)
FAILED "Upload failed — retry" no (object deleted)

6.2 Access control & storage isolation

6.3 Previews

6.4 SSRF (webhooks, embeds, bookmarks, custom AI endpoints)


7. Multi-Tenancy Threat Surface

Threat Control
Cross-tenant read via API repository-layer workspaceId scoping (every query method takes it — database, §2.5); TenantGuard rejects path/claim mismatches; property test in CI (random two-tenant ops, assert zero cross-reads)
Cross-tenant via id guessing ids are cuids (unpredictable, 128-bit effective) and every read re-checks the row's workspaceId against the context — an existing id from another tenant is NOT_FOUND (existence not leaked)
Cross-tenant via search workspace_id filter is a required parameter in the query builder (compile-time); permission snapshot per doc (search, §5)
Cross-tenant via realtime room key includes workspaceId; join auth checks membership per room; share-token principals are bound to the share's workspace
Cross-tenant via queue/stream events envelopes carry workspaceId; consumers scope all work to it; consumer code has no "all workspaces" path except the reconciliation sweep (which is per-workspace by design)
Tenant DoS (one workspace starving others) per-workspace: AI budgets, rate limits (per-key/per-user), file size caps, room count (realtime HPA is per-connection, and a workspace's rooms are bounded by its pages-in-edit — soft cap 5k rooms/workspace with alert), query pageSize caps; automation run concurrency cap per workspace (10 parallel runs)
Cache cross-tenant bleed every cache key includes wid (backend, §11); no tenantless cache holds tenant data (suggestions keyed by wid)

8. Secrets Management

8.1 Sources & handling

Secret Source Handling
DATABASE_URL, REDIS_URL, S3_*, JWT_SECRET, ENCRYPTION_MASTER_KEY env (secret store in prod — never image, never repo) Zod-validated at boot (fail-fast); JWT_SECRET ≥ 256-bit random, rotated via dual-secret window (JWT_SECRET_PREV for verification only, 24 h max); no secrets in logs (pino redactor: keys matching /(password
Provider AI keys (provider_secrets) created via API (admin) envelope-encrypted (below); shown once; listed by name only
Integration tokens (integrations.config) created via API same envelope encryption (the JSONB holds {"_enc": "v1:<ciphertext>"})
SSO client secrets / SAML certs (sso_providers.config) org settings UI same envelope encryption
Webhook signing secrets (webhooks) created via API, shown once raw kept envelope-encrypted in the webhook's encrypted config column (signing needs the raw secret at delivery — so secretHash is for verification of the secret by admins, the encrypted raw is for signing; both derived from the one-time value)
API keys / refresh / session / invite / reset tokens server-generated hashed at rest (SHA-256; bcrypt for user-chosen share passwords) — a DB dump yields no usable credential
Refresh/CSRF cookie values client-side HttpOnly (refresh); double-submit (CSRF) — §3.2/§5.2

8.2 Envelope encryption (at rest)

ENCRYPTION_MASTER_KEY (env, 256-bit, secret store — the ONLY key ever in env) = KEK
        │
        ▼  wraps one DEK per secret (envelope: rotation re-wraps DEKs, not all data)
DEK: AES-256-GCM(random 96-bit nonce, plaintext) → stored as {v:1, nonce, ct, tag}

8.3 Webhook signing & verification


9. Audit Logging

9.1 Mandatory event table (CI-enforced checklist)

Event Emitted by Metadata (beyond actor/ts/ip/ua)
user.registered AuthModule email (lowercased)
user.login / user.logout AuthModule sessionAgent, sessionId
user.login.failed.streak AuthModule failureCount, email
user.password_reset AuthModule resetTokenId (hash)
auth.refresh.reuse_detected AuthModule sessionId, replacedBy chain (the family-kill event)
sso.login AuthModule providerId, externalId
page.created / page.updated / page.deleted PageModule parentId, title (created/deleted)
page.shared / share.created / share.revoked ShareModule tokenHash, scope, password-set bool, expiry
permission.changed permission services resource{type,id}, grantee{type,id}, oldLevel, newLevel
workspace.member.added / workspace.member.removed / workspace.member.role_changed WorkspaceModule userId, role (old/new)
database.created DatabaseModule databaseId, propertyCount
record.created / record.updated DatabaseModule (incl. form submissions, automation actions) databaseId, changedProperties[]
file.uploaded / file.deleted / file.virus_detected FileModule (worker) fileId, size, mimeType (virus: engine + detection name)
automation.executed AutomationModule (worker) runId, status, actionType
api_key.created / api_key.revoked ApiKeyModule keyPrefix (never the key)
webhook.created / webhook.revoked WebhookModule url (host only in log lines; full in the row)
scim.user.created / scim.user.updated / scim.user.deactivated OrganizationModule externalId
ai.budget.exhausted / ai.budget.override AIModule budget kind, tokens
realtime.connection.abuse apps/realtime reason (slow consumer/oversize/malformed), ip
permission.denied (throttled) PermissionGuard requested vs effective level, reason
admin.search.reindex / admin.retention.purge admin endpoints scope, affectedCount

Rules: (1) the list is a CI test (tests/audit/mandatory-events.test.ts) that checks each service's mutation methods for their audit call — a mutation without its event fails CI; (2) audit rows commit with the mutation (same transaction) — no audit without the change; (3) append-only: no UPDATE/DELETE path in app code (retention detaches partitions — database, §5.2); (4) least-privilege content: hashes where a raw secret exists, host-not-path for URLs, diffs capped at 4 KB.

9.2 Retention & access


10. Threat Model Summary (OWASP ASVS L2 mapping)

Threat inventory (STRIDE-flavored) with the control that closes it:

# Threat (asset) ASVS L2 ref Control
T1 Credential stuffing / password brute force (accounts) 2.1, 4.x bcrypt 12, constant-time, per-IP + per-account rate limits, streak cooldown + audit, email verification gate
T2 Session hijack via stolen refresh cookie (session) 2.2, 15.x HttpOnly+Secure+SameSite, rotation with reuse detection + family kill, per-device revocation, 30 d max, denylist for revoked JTIs
T3 CSRF on cookie-auth mutations (integrity) 15.2 double-submit token + SameSite=Lax + Bearer-exempt design; WS token-in-query
T4 Cross-tenant data leak (confidentiality) 1.x, 4.x workspaceId scoping at the repository (structural), NOT_FOUND-not-403 for foreign ids, CI property test, search/realtime/queue per-workspace scoping
T5 Vertical privilege escalation (authorization) 4.x server-side guard on every op, role floors + explicit-NONE veto, owner grant cap, decision log, no frontend trust
T6 Stored XSS via content/comments/share (integrity) 15.1, 15.5 structured block content (no HTML fields), markdown html:false, CSP without prod unsafe-eval, X-Frame-Options DENY, output DTO whitelisting
T7 SQL injection (integrity) 1.1, 1.2 Prisma parameterization only, no raw SQL in app code, search DSL built from typed params
T8 Malicious file upload (integrity, availability) 6.1–6.4, 15.x presign-locked type/size, magic-byte sniff (declared ≠ sniffed → reject), executable/script refusal, ClamAV gate (VIRUS_DETECTED never served), private buckets, signed URLs, prefix isolation
T9 SSRF via webhooks/embeds/AI endpoints (confidentiality, availability) 13.3 IP-blocklist on resolved + pinned DNS, https-only public, redirect re-validation, allowlisted custom AI endpoints
T10 Malicious formula / JS execution (integrity, availability) 15.x safe AST evaluator (no eval — CI grep), step/output bounds, function whitelist
T11 Prompt injection via workspace content (integrity) 15.x (AI) context wrapped as untrusted data, server-side tool validation, user-identity execution (no elevation), budget caps, bad-citation measurement
T12 API key leak (confidentiality) 2.x, 4.x hashed at rest, shown once, scoped (READ/WRITE/ADMIN), per-key rate limit, instant revocation + denylist, lastUsedAt monitoring
T13 Webhook payload forgery (integrity) 13.x HMAC-SHA256 timestamped signatures (out + in), constant-time verify, replay window 5 min
T14 Denial of service (availability) 4.x, 14.x Redis sliding-window rate limits (all scopes), AI budgets + per-workspace concurrency, realtime per-connection backpressure + slow-consumer drop, queue dead-letters, HPA on queue depth, upload size caps
T15 Realtime abuse: oversized/malformed Yjs updates, room flooding (availability) 14.x 256 KB message cap, per-socket token buckets, 3-strike socket ban + audit, room-size cap 500, connect rate limits
T16 Secret exposure in logs/stacks (confidentiality) 13.x, 6.x pino redactor, requestId-only error bodies, secrets env-only, envelope encryption at rest, no secrets in env files committed (.env.example is the template, CI secret-scan)
T17 Supply chain (integrity) 13.x pnpm lockfile committed, pnpm audit + Renovate in CI, dependency review gate, no install scripts (onlyBuiltDependencies allowlist)
T18 Admin endpoint abuse (availability, confidentiality) 4.x admin routes behind ADMIN role + (enterprise) IP allowlist policy, all admin ops audited, search reindex is a job not a raw SQL surface
T19 Information disclosure via errors/timing (confidentiality) 14.x stable error codes (no stack leak), 404-for-foreign-ids, constant-time bcrypt (dummy hash), constant response on reset request, no count-leaking pagination for cross-tenant
T20 Backup/restore mishandling (confidentiality) 13.x PITR + S3 versioning, backups encrypted (infra-layer KMS), restore runbook with least-privilege drill, backup bucket access = restore role only

Known accepted risks (documented, reviewed each phase exit): (1) the ≤ 15 min window where a logged-out user's access JWT is valid on non-sensitive ops (closed for sensitive ops by the session double-check + JTI denylist on revocation); (2) plaintext internal pod traffic (NetworkPolicy-isolated, self-host assumption — mTLS tracked for Phase 15); (3) search permission staleness ≤ 5 min (over-surface only, open-time 403 is the hard gate); (4) client-side permission mirror can be stale (purely cosmetic).


11. Incident Response

11.1 Severity & response SLA

Severity Definition (examples) Response
SEV-1 active data breach / cross-tenant leak confirmed; auth bypass; RCE immediate: war room, comms owner; ≤ 1 h mitigation (kill switch: revoke key class, disable endpoint, feature-flag off); 24 h postmortem draft
SEV-2 vulnerability confirmed but not actively exploited (e.g. a leaked API key class, a vuln dep in a reachable path); sustained SLO breach with security angle ≤ 4 h mitigation; 72 h postmortem
SEV-3 suspicious-but-unconfirmed (abuse pattern, one malformed-event storm), single-user impact next business day; ticket + monitor

11.2 Kill switches (built for this)

Lever Scope How
JWT denylist + jwt:kill flag all access tokens Redis flag checked in JwtAuthGuard (one GET, 5 s cache) — instant global auth kill (emergency only; users re-login after clearing)
Refresh family kill (per user / per session) one user's sessions revokeAll(userId) — the standard path for credential compromise
Feature flags (config/feature-flags) AI module, realtime, webhooks, forms, public API per-flag off = clean 404/503 with stable code; no code deploy needed
Per-workspace AI budget 0, rate limits 1, workspace SUSPENDED (all members) workspace-level containment for tenant-level abuse
Provider circuit breakers AI open breaker = no external calls (data exfil path closed)
Worker pause (per queue) side effects BullMQ pause by queue name (stop webhook dispatch / automation runs while investigating)

11.3 Playbook (the standard sequence)

1. DETECT   alerts (audit anomaly, abuse metrics, CVE, SECURITY.md report) → on-call
            triage: confirm severity, open incident channel + doc
2. CONTAIN  kill switch sized to the blast radius (smallest first: user → workspace
            → tenant-class → global)
3. ASSESS   timeline from audit_logs (actor, ip, resources — the primary evidence;
            export to the incident bucket via `admin.audit.export`); scope the data
            touched (tenant? rows? fields?)
4. REMEDIATE  rotate the affected secret class (refresh family / API keys / master
            key), patch, force affected users to re-auth, re-run the exact exploit
            path to verify
5. NOTIFY   legal/privacy assessment (cross-tenant or PII ⇒ within 48 h), affected-
            user email per severity template; SEV-1 → platform status page
6. RECOVER  un-kill in stages (flag on → 24 h watch → full), confirm metrics
7. LEARN    postmortem (SEV-1 24 h / SEV-2 72 h): timeline, root cause, which
            controls caught/missed, owned action items; update the ASVS table §10

11.4 Standing security operations