Security at Pyctura
Pyctura turns spreadsheets into reports. This page says how one customer's data stays invisible to another, who can do what, what is recorded, and exactly what leaves our systems when the AI is used. Every statement here is backed by a mechanism in the product or a document we can share. Last reviewed 25 October 2026.
Tenant isolation, proven by a test
Every customer's data lives under a workspace. Access is enforced in the database by PostgreSQL Row Level Security, not by application code that could be bypassed. Each table that holds customer data carries the workspace it belongs to, and its policies expose a row only to signed-in members of that workspace, evaluated in the database under the user's own session. A request authenticated as one customer cannot read, change, delete or insert into another customer's workspace, even with forged identifiers.
This is proven by an automated two-tenant test, not asserted. The test creates two tenants and checks, for every table with policies, that tenant B cannot see, update, delete or insert into tenant A's workspace, and that A can. It runs against our test environment and is part of the repository (tests/security/tenancy.test.ts); we share the file and its latest run with customers on request.
The one server component with elevated database access is narrowly scoped and re-derives the workspace from the signed-in session or a server-held record before acting. No database function with elevated rights is callable without signing in; a migration asserts this and fails if it is not so.
Six roles, enforced in the database
Two independent roles describe a member: a management role (owner, admin, member) and a content role (creator, contributor, viewer). Both are database constraints, there is exactly one owner per workspace, and every server route re-checks membership and the required capability before acting. Controls a role may not use are not shown. The table is generated from the same code the product uses.
| Capability | Owner | Admin | Creator | Contributor | Viewer |
|---|---|---|---|---|---|
| View reports in the workspace | Yes | Yes | Yes | Yes | Yes |
| Add notes, pin insights, share and export | Yes | Yes | Yes | Yes | Yes |
| Run AI analysis | Yes | Yes | Yes | Yes | No |
| Create and edit reports and flows | Yes | Yes | Yes | Yes | No |
| Publish reports | Yes | Yes | Yes | Yes | No |
| Delete reports | Yes | Yes | Yes | No | No |
| Invite members, remove members, change roles | Yes | Yes | No | No | No |
| Transfer ownership, delete the workspace, billing | Yes | No | No | No | No |
Creator, Contributor and Viewer are members. An admin or owner can also hold a viewer content role; the table shows the common case.
Audit log: append-only, exportable
Every workspace has a tamper-evident log of who changed what. The log is append-only for everyone, workspace owners and our own service account included: there is no update or delete permission, and a database trigger rejects any attempt regardless of role. Owners and admins read their workspace's log in Settings, filter it by person, action and date, and export the filtered range as CSV.
Recorded automatically on every insert, update and delete:
- reports
- flows
- workspace members and invitations
- the workspace record itself
Recorded as named actions, with the actor, IP address and user agent:
- each AI analysis call (with token counts and cost)
- connecting or disconnecting an external data source
- a member accepting an invitation
The log never holds secret values. For an update it records the names of the changed fields, so uploaded rows and connector credentials never land in it.
The AI boundary: exactly what leaves
AI features (report generation, summaries, column tagging) call one model vendor, Anthropic, through a single server route. The API key never reaches the browser; the model and its parameters are fixed on the server and cannot be changed by a request. Every call requires a signed-in member of the workspace, is metered against a per-workspace daily spending cap, and writes an audit row.
What leaves for a call:
- column names and row counts
- a bounded sample of the upload: at most the first 200 rows when a report is generated from a prompt, and 5 rows with up to 10 distinct values per column for tagging
- the numbers already computed for a chart, when a summary is written
- the prompt the user typed
What never leaves: a full table. The server cuts every request at 50,000 characters before it is sent, so no feature can send more than that bounded sample.
Anthropic's handling of API data is governed by its commercial terms. A per-workspace switch that turns AI features off, and bringing your own model key, are on the roadmap below.
Accounts and sessions
Sign-in is email and password, or Google, through Supabase Auth, with password reset by email. In Settings, every user sees where they are signed in (browser, device, location, last active), can end any session they do not recognise, and can sign out of every device at once. Invitations are single-use links tied to an email address and a role.
Your data
Uploaded files are parsed in the browser; the rows and the report built from them are stored under your workspace and nowhere else. A shared link is a random token you create per report: you choose whether it is private (sign-in required, expires after 30 days, renewable) or public, and whether viewers can see the data behind the charts or only the charts; you can revoke it at any time, after which the link shows nothing. Deleting a report deletes its rows.
Data is stored and processed in the United States (see the subprocessor list). Everything in transit uses TLS; everything at rest is encrypted by the providers below.
Subprocessors
| Provider | Purpose | Location |
|---|---|---|
| Supabase | Database, authentication, file storage | AWS us-east-1 (United States) |
| Vercel | Application hosting and server functions | United States |
| Anthropic | AI model for analysis, summaries and tagging | United States |
| Resend | Transactional email (invitations, password reset) | United States |
| GitHub | Source code and build pipeline | United States |
We give customers notice before adding a subprocessor that handles their data.
Roadmap planned, not yet done
These are commitments with work behind them, listed so the line between done and planned is visible:
- SOC 2 Type I: planned; the audit quarter is set when the first customer questionnaire arrives, with the Type II observation window started the same day.
- External penetration test of the application and API, with a publishable summary letter.
- Vulnerability disclosure policy and a stated response window.
- Per-workspace switch that turns AI features off, enforced on the server; bring your own model key.
- Browser-only data mode: the server keeps schema, profile and chart series, never the rows.
- Workspace export and deletion with a 30-day window, both audited.
- For enterprise: single sign-on (SAML, Entra ID), SCIM provisioning, enforced MFA, IP allowlists.
Questions and questionnaires
Write to security@pyctura.ai. We answer security questionnaires from this page and the documents behind it, and we share the isolation test, its latest run and the audit log design with customers under evaluation.