Skip to content

Zero-data control plane

Production-realism without production risk

Your data never touches our servers. The Subveil CLI runs in your environment, on your laptop or your CI runner. Database rows, PII values, credentials and the masking seed never go to Subveil; Cloud is a zero-data policy and compliance control plane for metadata only.

The cloud stores account and control-plane metadata, never database row values.

Two boundaries · one local data path

Data plane and control plane stay separate

The CLI reads and masks rows where you run it. Cloud sends policies to the CLI and receives schema and execution metadata over HTTPS.

Data plane · your network

Laptop · VPC · GitHub Actions runner

Source DB

Postgres · MySQL · MariaDB

Subveil CLI

FK graph · HMAC masking before load

Target DB

Local · ephemeral PR DB

Raw rows, PII values, database credentials and SUBVEIL_SEED stay in this boundary.

HTTPS

SUBVEIL_API_KEY
policy down · metadata up

Subveil Cloud · control plane

  • Published masking policy

    Policy document + policy_hash

  • Schema catalog

    Table and column names + PII coverage status

  • Run and branch metadata

    Signed audit receipts + PR lifecycle status

Policies travel to the CLI; schema and run metadata return to Cloud. Database rows and database credentials do not.

From schema to safe test data

The four-step workflow

Review the generated policy, choose the slice you need, and run it locally or in CI. Cloud features are opt-in and work on metadata only.

  1. 01 / 04

    Discover likely PII and schema drift

    subveil init inspects column names and catalog types, then samples up to 50 values from eligible text and JSON columns by default. Type and name rules help classify fields; sampling can spot patterns their names miss. The sample runs on the CLI machine: values aren't printed, written to subveil.yaml, or sent to Cloud. Treat detections as suggestions and review the generated file.

    local discovery + schema report
    $ subveil login$ subveil init --project prj_abc123def456 --report-drift --sample-rows 50
    example generated masking suggestions
    tables:  public.customers:    columns:      email:     { transformer: email }      full_name: { transformer: name }      phone:     { transformer: phone }      ssn:       { transformer: token }

    Column names

    Suggest email, name, phone and identifier fields.

    SQL types

    Help choose compatible rules and rule out impossible values.

    Local sampling

    Look for PII patterns in up to 50 eligible values per column.

    When the schema is reported, Cloud checks catalog names and types against the published policy. It can flag likely unmasked PII such as public.customers.ssn; this is a drift signal, not a guarantee that every sensitive field was found.

  2. 02 / 04

    Subset the graph and mask before loading

    Root filters select starting rows. Subveil follows the foreign-key graph to include referenced and referencing rows needed by the slice, then masks each row before it is written to the target. The same SUBVEIL_SEED keeps deterministic mappings stable across runs; unique columns get collision-safe variants when needed.

    subveil.yaml · masking + subset excerpt
    version: 1engine: postgresmasking:  seed_env: SUBVEIL_SEEDsubset:  roots:    - table: public.accounts      scale: 0.05  default_scale: 0.05
  3. 03 / 04

    Test migrations on every pull request

    The generated .github/workflows/subveil-pr.yml starts a service database on the runner (PostgreSQL 18 by default, or MySQL 8.4), loads a masked copy, then runs your migrations and tests against it. Constraint failures can surface before merge. Subveil reports the PR database lifecycle to the dashboard; only status metadata is sent.

    Representative steps · configure secrets in GitHub
    .github/workflows/subveil-pr.yml
    # .github/workflows/subveil-pr.yml (representative steps)jobs:  database:    runs-on: ubuntu-latest    services:      postgres:        image: postgres:18        env: { POSTGRES_PASSWORD: subveil }        ports: ["5432:5432"]    env:      SUBVEIL_API_KEY: ${{ secrets.SUBVEIL_API_KEY }}      SUBVEIL_SOURCE_DSN: ${{ secrets.SUBVEIL_SOURCE_DSN }}      SUBVEIL_SEED: ${{ secrets.SUBVEIL_SEED }}      SUBVEIL_PROJECT_ID: prj_abc123def456      SUBVEIL_TARGET_DSN: postgres://postgres:subveil@localhost:5432/postgres?sslmode=disable    steps:      - run: subveil pull --config subveil.yaml --project "$SUBVEIL_PROJECT_ID" --create-schema --require-audit      - run: npx prisma migrate deploy        env: { DATABASE_URL: ${{ env.SUBVEIL_TARGET_DSN }} }      - run: npm test        env: { DATABASE_URL: ${{ env.SUBVEIL_TARGET_DSN }} }      - if: always()        run: subveil ci report --status "${{ job.status }}"

    Example migration and test commands are for a Prisma/Node project; replace them with your project's migration and test commands. Fork pull requests are skipped because GitHub does not provide repository secrets to them. The full workflow is generated on the Branches tab of your dashboard.

    On a GitHub-hosted runner, source rows pass through that runner while the CLI copies them. If rows must stay inside your own network, use a self-hosted runner.

    Before using --require-audit, publish a project policy; the CLI stops before loading the target if the required policy cannot be fetched.

  4. 04 / 04

    Keep a verifiable run receipt

    A cloud-mode pull submits a receipt with a SHA-256 hash of its canonical contents and an HMAC signature tied to the API token. The append-only ledger verifies the receipt hash and shows the policy hash, hashed source and target identifiers, row counts, and masked column names.

    Receipts give your team verifiable records to support SOC 2, ISO 27001, HIPAA, and GDPR evidence gathering. They do not certify that an entire environment is free of raw PII or guarantee auditor acceptance.

    The displayed sample is illustrative; real receipts contain your run metadata and hashes, never row values.
    verified receipt · shortened example
    {  "run_id": "9d1c…e2f4",  "status": "success",  "source_host_hash": "4be1…9c07",  "policy_hash": "9f2c…b71e",  "total_rows_copied": 50000,  "tables_summary": [    { "table": "public.customers", "rows_copied": 50000, "masked_columns": ["email", "full_name", "ssn"] }  ],  "receipt_sha256": "8a72…f310",  "signature": "c41e…07ab"}

Technical guarantees

Built for real database shapes

Compatibility floors and the versions exercised in CI are shown separately, so you can see what is tested and what is only expected to work.

Supported database engines and the versions exercised in CI
EngineSupported floorExercised in CI
PostgreSQL14+16
MySQL8.0+8.0 · 8.4
MariaDB10.6+10.6

Versions above the floor are expected to work but are not in the CI matrix. Managed-provider configurations (RDS, Cloud SQL and similar) are not covered either.

Built-in transformers

Mask common sensitive fields

Transformer IDs match the CLI. custom_sql runs only from your local config; Cloud policies do not accept it. Quote nullin YAML so it is not parsed as an empty value.

  • email
  • name
  • phone
  • address
  • uuid
  • token
  • ip
  • number
  • date_shift
  • redact
  • "null"
  • custom_sql
  • jsonb

Security FAQ

Questions security teams ask

Do you store database connection strings or credentials?

No. Source and target DSNs, database passwords, and SUBVEIL_SEED stay in your environment or CI secret store. Subveil Cloud receives an API token over HTTPS for its own authentication, but it does not receive your database credentials or masking seed.

How does Subveil preserve foreign-key integrity?

You choose root rows with a filter or sample scale. The CLI follows the foreign-key graph to include the related rows the slice needs, plans a valid load order, and handles cycles and self-references with engine-specific strategies. Run subveil plan to inspect the graph before loading.

What happens when a migration adds a PII column?

When you report a schema, the CLI sends catalog metadata such as table and column names and types, not values. The dashboard compares likely PII columns with the published project policy and can flag candidates with no rule. This is a useful drift signal, not proof that every possible PII field has been found.

Can we run the CLI offline?

Yes. Local init, plan, and pull can use a local subveil.yaml without contacting Subveil Cloud. Your source and target databases still need to be reachable from the machine running the CLI. Central policy sync, schema reporting, branch status, and cloud audit receipts need network access to Subveil Cloud.

Start with a local copy

Keep production rows where they belong.

Install the free CLI, review the generated policy, and build a masked database in your own environment.