Pyctura

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.

CapabilityOwnerAdminCreatorContributorViewer
View reports in the workspaceYesYesYesYesYes
Add notes, pin insights, share and exportYesYesYesYesYes
Run AI analysisYesYesYesYesNo
Create and edit reports and flowsYesYesYesYesNo
Publish reportsYesYesYesYesNo
Delete reportsYesYesYesNoNo
Invite members, remove members, change rolesYesYesNoNoNo
Transfer ownership, delete the workspace, billingYesNoNoNoNo

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:

Recorded as named actions, with the actor, IP address and user agent:

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:

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

ProviderPurposeLocation
SupabaseDatabase, authentication, file storageAWS us-east-1 (United States)
VercelApplication hosting and server functionsUnited States
AnthropicAI model for analysis, summaries and taggingUnited States
ResendTransactional email (invitations, password reset)United States
GitHubSource code and build pipelineUnited 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:

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.