Skip to content

Paridity data-flow & sub-processor disclosure

Procurement and data-protection teams ask two questions by name: where does our data flow and who are your sub-processors. Because Paridity's architecture runs entirely inside the customer's own cloud account, the honest answers are unusually short - and that is the point.


1. The one-sentence answer

Customer data never leaves the customer's cloud account, and no Paridity sub-processor receives customer source data, schema/object names, or evidence values. Two things can leave, neither of them customer data: a licence check to EGMW's own licence service, which carries licence data only, and a support email with an optional redacted diagnostic bundle, only when an operator chooses to send one (§2, §3). EGMW uses two sub-processors for that data, named in §4: Railway hosts the licence service, and Microsoft 365 hosts EGMW's support mailbox. The optional AI features are bring-your-own-key: when a customer enables them, prompts go to the AI provider the customer configures and contracts with directly (§4) - a vendor relationship the customer owns, not a Paridity sub-processor.


2. Data classes (what data exists, and where it lives)

Data class Examples Where it lives Leaves customer VPC?
Source/target row data The actual values being reconciled Read from the customer's own systems by in-VPC workers; only hashes/diffs are retained No
Evidence hash_total, row_hash, field_diff, bundles Customer's own PostgreSQL (ev schema), in the customer's cloud. Optionally, signed evidence-bundle containers are written instead to the customer's own object store (S3, Azure Blob, or GCS) named by a tenant-configured destination pointer - the customer's bucket, the deployment's own credential chain; the database then retains only the object URI, size, hash, and the store's receipt. No
Metadata Object/schema/column names, mapping config, resolved_config_snapshot, known_column_set Customer's own PostgreSQL (cfg/run) No
Credentials Source/target DB logins, keys Never stored by Paridity - connection_ref is a URI pointer into the customer's own secrets backend; resolved at runtime No
Audit trail cfg.audit_log, actor/IP, config audit Customer's own PostgreSQL No
Telemetry (optional) OpenTelemetry traces/metrics Customer's own observability stack; span attributes never include row values No (customer-controlled)
Licence Licence key; a machine id (a SHA-256 of a random installation id that the platform's own database mints - never a host name, network adapter, or hardware id); the activation id the licence service issued; the product name (paridity); the operating-system platform and CPU architecture The key stays in the customer's own secrets backend (the platform holds a pointer to it, plus a hash and a masked form). The values are sent over HTTPS to the Paridity licence service: activation sends the key, machine id, platform, architecture, and product; the daily refresh sends the key, machine id, and product; deactivation sends the activation id and machine id. The licence service records the request's source address. Yes - licence data only, never customer data
Support diagnostic bundle (operator-initiated) Run status, timings, readiness and drift history, anomaly counts, worker health, and an audit excerpt for one run or mapping Generated inside the deployment as a JSON file an operator downloads; nothing is sent automatically. It leaves only if the operator attaches it to a support email. Column names, evidence values, hashes, connection references, secrets, and SQL text are never included; object names are replaced with opaque tokens unless the operator opts in to keep exactly the mapping name, the source/target schema.table names, and the tenant name. Requires the diagnostics.export permission. Only if the operator sends it

There is no Paridity-operated database, control plane, or evidence store in this model. The only data Paridity holds is licence data in its licence service (hosted on Railway): the licence and its customer record, the activated machine id, the platform and architecture reported at activation, and the source addresses of licence requests - plus any support email, and any diagnostic bundle attached to it, that a customer chooses to send to EGMW's support mailbox (hosted on Microsoft 365). Paridity holds no customer data.


3. Data flow (customer VPC)

   Customer source systems ─┐
   (SAP/Oracle/DB2/…)       │  (read, in-VPC)
                            ▼
              ┌─────────────────────────────┐   Everything inside this box runs in the
              │   Customer cloud account /  │   customer's own account. No customer data
              │   VPC (customer-controlled) │   crosses out to Paridity-operated
              │                             │   infrastructure.
              │  Workers → hash/diff →      │
              │  PostgreSQL (cfg/run/ev)    │
              │  API/UI ← operators (SSO)   │
              │                             │  HTTPS 443, licence data only
              │  API pods + scheduler ──────┼─────────────► EGMW licence service
              │  worker (licence calls)     │  (license.evilgenius.io by default)
              └─────────────────────────────┘
                            ▲
   Customer target systems ─┘
   (Snowflake/Databricks/…)   (read, in-VPC)

Credential resolution (SecretsClient → the customer's own AWS SM / Azure KV / GCP SM / Infisical) and identity (the customer's own OIDC/SAML IdP - Okta or Microsoft Entra ID) are also entirely within the customer's control. Paridity is not in the path.

The licence path. The API pods and the scheduler worker call the licence service over HTTPS (port 443) at the address in the LICENCE_SERVER_URL setting (default https://license.evilgenius.io; an http:// address is refused outside development and test). The calls are: activation and deactivation, when an operator performs them; a refresh of the signed licence token once a day per tenant (at most once every 15 minutes); and a reachability probe, with an empty request, once at scheduler start. The daily refresh and the probe run only in releases that enforce the licence: every release after v1.4.0. v1.4.0 and earlier releases call the licence service only when an operator activates or deactivates.

  • Egress. The client honours the standard HTTPS_PROXY / NO_PROXY variables, and a custom CA certificate can be configured for a TLS-inspecting proxy. The licence service's name is stable but its IP address is not, so an egress-locked deployment (the Helm NetworkPolicy and the Terraform egress lockdown are both CIDR-based) routes the call through the customer's HTTPS egress proxy and permits that name on the proxy. Neither the chart nor the Terraform modules open this path by default.
  • If the licence service is unreachable. The signed token is valid for 7 days, so the installation keeps running normally for 7 days without it. After that, a paid licence is degraded until a refresh succeeds: runs are proof-only (counts and hash totals, no field-level diffs) and bringing new configuration into service (for example, creating or enabling a mapping or schedule) is refused. Runs are never stopped, and no data is touched.
  • Air-gapped installations. The product can read a staff-issued offline activation code, but the licence service cannot yet issue one for Paridity, so an installation with no outbound path cannot be activated today. In a release that enforces the licence it runs degraded, as above, until it is activated.

4. Sub-processor list

Core product (reconciliation, evidence, audit): no sub-processor. No customer data reaches Paridity-operated infrastructure or any EGMW vendor. The customer's own cloud provider (AWS / Azure / GCP) is the customer's own infrastructure, not a Paridity sub-processor.

EGMW's sub-processors (licence and support data; no customer data from the product):

Sub-processor What EGMW uses it for Data it receives Customer data?
Railway (cloud hosting) Hosts EGMW's licence service, which EGMW operates. license.evilgenius.io, the default licence address, is a DNS alias for the Railway-hosted service. The licence data in §2: licence key, machine id, activation id, product name, platform, architecture, and the request's source (IP) address. The licence service also holds the licence and its customer record (the customer the licence was issued to, the licence type, and its term). No - no source data, object/schema/column names, credentials, evidence values, or tenant or user identities
Microsoft 365 (Microsoft, Exchange Online) Hosts EGMW's support mailbox, [email protected]. Only what an operator chooses to email. The product's "Contact support" and "Report a Problem" links open the operator's own mail client with a prefilled draft naming the tenant, the Paridity version, the current page and, where relevant, a run id; the operator can edit it, and nothing is sent until the operator sends it. An attached diagnostic bundle carries what §2 lists and nothing else: no column names, evidence values, hashes, connection references, secrets, or SQL text, and object names replaced by opaque tokens unless the operator opts in. The email also carries the sender's address and whatever the operator writes. Not from the product - only what an operator chooses to write or attach

The hosting region of the licence service is not stated in this document.

Product notifications do not pass through EGMW. Paridity has no EGMW email or messaging service. Notifications go only through the providers the customer configures on each notification channel, with the customer's own endpoints and credentials (the credential is a pointer into the customer's secrets backend): email through the customer's own SMTP server or Microsoft 365 mailbox (Microsoft Graph), Slack, Microsoft Teams, PagerDuty, Jira, ServiceNow, or a webhook. Those providers are the customer's vendors, not Paridity sub-processors.

Every notification send passes the platform's outbound policy before the channel credential leaves the process: https only for HTTP channels, no redirects, no private, loopback, link-local, CGNAT, multicast or unspecified destination address, and the connected address re-checked before a response is read (SMTP: before the password is sent). A destination on the customer's own network (an internal SMTP relay, Jira Data Center, an on-premises ServiceNow, an internal webhook) is sent to only when a platform admin has added its exact host and port to the tenant's private destination allowlist. An entry permits private addresses (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 100.64.0.0/10, fc00::/7) for that one host and port and changes nothing else; loopback, link-local and cloud metadata addresses (169.254.0.0/16, fd00:ec2::254, 100.100.100.200) can never be allowlisted. The allowlist is stored in the customer's own deployment database and every change is audited.

Optional AI features (bring-your-own-key; only if the customer enables them):

External party (customer-contracted, not a Paridity sub-processor) Purpose Data exposed Controls
The AI model provider the customer configures with their own key (e.g. their Anthropic account) - for AI Triage / AI Insights Failed-run narration, risk/cost explanation, knowledge copilot Prompt context for the requested explanation - object/schema/column names masked (mask_object_names; on this path the masking is absolute - see the note below), prompt context scrubbed, data-residency provider restriction applied; raw evidence values are not sent Off by default; the provider key lives in the customer's secrets backend and the customer contracts with the provider directly; per-tenant AI settings + quota; deterministic fallback when AI is unavailable

Important: AI is an optional capability, not part of the correctness path - with it disabled there is no external AI traffic at all, and even with it enabled the provider relationship belongs to the customer (there is no Paridity-provided default provider). AI never determines correctness - it explains and triages only.

4.1 The /mcp agent surface is a customer egress path, not a Paridity sub-processor

If a customer enables the MCP agent surface, their own MCP client - Claude Desktop, Cursor, an internal pipeline agent - authenticates to /mcp with their own credentials and forwards what it reads into a model the customer chose and contracts with directly. Paridity routes nothing to that model and is not a sub-processor for it, but the flow deserves naming because the practical exposure differs from the UI even though the authorization is identical:

  • What /mcp returns. It is at parity with the REST API: object, schema and column names, run/readiness/drift metadata, and evidence aggregates and masked samples - under the same per-tool RBAC and the same query-time evidence masking (store_raw_diff_values, mask_in_ui) that REST enforces. It is a read-only catalog; arbitrary SQL, shell, secret retrieval, file access and direct database modification are permanently excluded from it.
  • What mask_object_names does here. It is best-effort on this surface, not absolute. A defined list of object-name fields is tokenized; a field carrying a name that is not on that list is returned in clear text. That is a deliberate design decision, not a defect - deny-by-default masking would make an agent unable to say which column drifted. See §4.2.
  • Who the disclosing party is. The customer's agent and the customer's model - your agent, your credentials, your disclosure. The customer's own model-provider agreement governs that hop, not Paridity's. One tenant toggle deliberately governs masking on both surfaces; a separate MCP masking control was considered and rejected (plan_mcp_enhancement_v1.md §3, decision record 6).

4.2 Residual risk we state rather than leave to be discovered

Same authorization is not the same disclosure surface. A person reading a table name on a Paridity screen does not thereby put that name into an LLM context window, a vendor's request logs, or a retention window. An MCP client does - automatically, on every call, for every field in the response. The permission check is identical to REST; the practical egress is not. "The customer controls the client and its model" is true, and it assumes the customer has considered which model that is - which is exactly the case a compliance control exists to cover when they have not.

We do not resolve this by tightening the masking (that would break the surface's purpose). We state it, and we rely on the controls that bound it:

Control How it bounds the risk
Off by default, three tiers CFG_AI_ENABLED defaults to false, so /mcp returns 403 MCP_DISABLED on a stock deployment; and in APP_ENV=production the API refuses to start with AI enabled unless CFG_AI_REQUIRE_TENANT_OPT_IN=true (startup guard in src/api/middleware/auth.py), which makes cfg.tenant_defaults.mcp_enabled a required second opt-in; per-tool RBAC grants apply on top.
Per-tool RBAC, identical to REST Every tool declares the same permission string the equivalent REST route requires (run.read, mapping.read, evidence.read_masked, discovery.execute), evaluated per call. An agent cannot read anything its principal could not read over REST.
Tenant scope from the principal Tenant identity always comes from the authenticated principal, never from a tool argument; a cross-tenant lookup returns 404.
Read-only, with permanent exclusions The shipped tool catalog is read-only. No arbitrary SQL, shell, secret retrieval, file access, or direct database modification tool exists, and none is permitted.
Audited and rate-limited Every dispatched tool call writes an attributed row to cfg.audit_log - successes and failures alike, from a finally block, with the agent surface as actor_type and the human principal behind it attributed. A per-principal, per-tool ceiling sheds abuse before dispatch; a shed call is refused rather than audited, deliberately, so a rate-limited client cannot flood the audit log.
Data-plane classification /mcp is classified data-plane and runs entirely inside the customer's own environment, like the rest of the platform - nothing on Paridity-operated infrastructure.

The customer-side control that actually closes it is the one only the customer can apply: govern which MCP clients and which models are permitted to hold Paridity credentials, exactly as you would govern REST API keys.


5. Hosted deployment option (internal roadmap)

Paridity is customer-hosted today; a hosted deployment option is on our internal roadmap with no committed date. It is being designed under a hard invariant: credentials, object/schema/column names, and evidence field values never leave the customer VPC and never reach Paridity-operated infrastructure - even under a hosted model. If it ships, this disclosure will be updated with the exact control-plane data classes (opaque IDs, counts, status, timings only) and any new sub-processors before a customer contracts against it. Until then, contract against the disclosure above.


Classification: Customer Confidential - reflects the shipping customer-hosted architecture as of the date above; verify against the current Security Whitepaper before relying on it in a signed agreement.