# Security Review: What Schemity Reads, Stores and Sends

> Schemity reads your database's structure, never its rows, and never writes a password to the diagram file. The full list of what it reads, stores and sends.

Source: https://schemity.com/doc/security-review/

Schemity reads your database's structure, never its rows. Every query it runs against a connected database reads the system catalog or returns a single number, the diagram file holds the schema and the connection details but never a password, and the only things that leave your machine are listed in the [privacy policy](https://schemity.com/privacy/). This page is the version of that answer an IT or security reviewer can check line by line.

## What does Schemity read from a connected database?

Only the catalog: `information_schema`, `pg_catalog`, `sys.*` on SQL Server, and `sqlite_master` with the `pragma_*` functions on SQLite. From there it reads tables, columns, types, keys, constraints, indexes, enums, views, triggers, functions, comments, and the objects that depend on each other.

Two features count rows, and both return a number, never the rows themselves:

- **Row estimates** come from the statistics the database already keeps: `pg_class.reltuples` on PostgreSQL, `information_schema.tables.table_rows` on MySQL, `sys.dm_db_partition_stats` on SQL Server, and the first figure of `sqlite_stat1` on SQLite. PostgreSQL's `pg_stats` and SQLite's `sqlite_stat4`, which hold sample values from your tables, are never read.
- **Count exactly** in [impact analysis](https://schemity.com/doc/impact-analysis/) runs only when you click it: a `COUNT(*)`, a count of `NULL` values, a count of duplicates, or a count of rows with no parent. It runs in a read-only transaction with a 60-second limit; SQL Server has no read-only transaction, so there it gets the time limit only.

There is no data preview, no sampling, and no query that selects the values in your columns. A `users` table full of emails and password hashes is read as column names and types.

Snowflake is never connected at all: Schemity reads a Snowflake schema from a `GET_DDL` script you export ([Snowflake schema design](https://schemity.com/doc/snowflake/)).

## Does Schemity ever write to the database?

Only when you apply a migration. The migration dialog shows the exact SQL first, and a connection can require you to type the database name before **Apply** is enabled, which is on by default for connections labelled Production ([Connections Overview](https://schemity.com/doc/connections-overview/)).

If a migration fails, the database's error can quote row values, for example PostgreSQL's `Key (email)=(a@b.com) already exists`. That message is shown in the migration dialog and nowhere else: it is not written to the diagram file, not logged, and not sent in a crash report.

## What is in the diagram file?

The diagram is one [JSON file](https://schemity.com/doc/json-storage-format/) per connection. Next to the schema and the layout, it keeps the connection details, because that is how the diagram reconnects:

| Kept in the file | Never in the file |
|---|---|
| Database engine, host, port, database name, schema | Database password |
| Username | SSH password |
| SSH host, port and username | The contents of an SSH key or a TLS certificate |
| The path to the SSH key and TLS certificate files | Any row from your database |
| The password command, as text | The output of the password command |
| The environment label (Local, Staging, Production) | |

Check constraints, defaults and comments are stored the way the database reports them, so a literal written into one, such as `DEFAULT 'support@example.com'`, is in the file because it is part of the schema.

The **Schemity JSON** export writes the same document, connection details included. Importing it into Schemity Lite drops the connection.

## Is it safe to commit the diagram file to Git?

For the password, yes: it is never in the file. What a commit does share is where the database is and who connects to it: the host, the username and the SSH jump server. On most teams that is not a secret, and it is what lets a teammate open the diagram and connect with their own credentials. If your hostnames are confidential, keep the workspace in a repository with the same access as your infrastructure code ([Version Control in Git](https://schemity.com/doc/version-control-git/)).

The one field that could turn the file into a secret store is the password command, so do not put a password in it. Schemity warns in the connection form when the command looks like it contains one, such as `echo hunter2` or `PGPASSWORD=hunter2 psql`, and suggests a command that fetches it, such as `op read` or `vault kv get`.

## Where are database passwords kept?

In your operating system's keychain: Keychain on macOS, Credential Manager on Windows, and the Secret Service on Linux. Schemity asks the keychain for a password when it connects and does not write it anywhere else.

With **Credential: Command**, nothing is stored at all. Schemity runs the command on each connect, for example `aws rds generate-db-auth-token` for an RDS IAM token, and keeps its output in memory for five minutes. A command runs only after you approve it, and the approval is saved as a SHA-256 hash of the command, not the command's output.

## How is the connection to the database protected?

With TLS, set per connection: **Verify full** checks that the server's certificate was signed by a trusted authority and belongs to the host you typed, **Verify CA** checks the authority only, and **Require** encrypts without checking. A pasted connection string with `sslmode=prefer` or `allow` is filled as Require, because both quietly fall back to plain text ([Connections Overview](https://schemity.com/doc/connections-overview/)).

Through an SSH tunnel, Schemity pins the server's host key on the first connection, as a SHA-256 fingerprint in `~/schemity/.ssh_known_hosts`. From then on a server that presents a different key is refused, so a machine pretending to be your jump server cannot take the connection. The tunnel's local end listens on `127.0.0.1` only.

## What can an AI agent see through the MCP server?

The [MCP server](https://schemity.com/doc/ai-assisted-design/) listens on `127.0.0.1` only and checks a token on every request, so it is not reachable from the network. It gives an agent the schema, the context views, dependencies, lint findings and the impact of pending changes. No tool returns rows: `count_rows` returns a number. When a connection fails, the agent is told the kind of failure (the credentials were refused, the SSH tunnel did not open) rather than the error text, which can name the database user and host.

Whatever the agent receives goes to the model provider its host uses, under that provider's terms. Schemity sends nothing to us through the MCP server.

## What leaves the machine?

| What | When | Can it be turned off? |
|---|---|---|
| License and update checks: a device ID, the license key on activation, the app version, OS and processor | Always | No, this is how Schemity is licensed |
| Usage statistics: which features are used, with the database type and export format, no names | On by default | Yes, in Settings |
| Crash reports: the error, a stack trace, the app version and OS | On by default | Yes, in Settings |
| Feedback: the message you type and the details listed in the dialog | Only when you send it | It is never automatic |

The [privacy policy](https://schemity.com/privacy/) lists every field in each of these and what is removed before sending. Settings is the gear icon in the bottom right corner of the home screen, and turning crash reports off stops them from that moment, including the report a native crash would upload.

## Can Schemity run on an air-gapped machine?

Yes. Diagrams, connections to databases on the same network, impact analysis and lint all work without internet access. A machine that cannot reach the license server activates with a token from support, pasted into the license key field, and an activated license stays active however long the machine stays offline.

## How do I know an update is genuine?

Every update is signed, and Schemity checks the signature against a public key built into the app before installing anything. A package that is unsigned or signed with any other key is refused, so even someone who controlled the download server could not ship a modified build. Updates install only when you click **Install Update**.

The macOS app is also signed with an Apple Developer ID and notarized by Apple, so Gatekeeper checks it on first launch.

## How is Schemity itself checked?

Every week, and whenever a dependency changes, the same scan runs over the app and the license server: [Snyk](https://snyk.io/) and [cargo-audit](https://rustsec.org/) check every JavaScript and Rust dependency against published vulnerabilities, [Semgrep](https://semgrep.dev/) analyses the source code, and [gitleaks](https://github.com/gitleaks/gitleaks) searches the whole Git history for leaked secrets. gitleaks also blocks a secret before it can be committed. A finding fails the scan until it is fixed, or accepted with a written reason when no fix exists yet.

## How do I report a security issue?

Email [support@schemity.com](mailto:support@schemity.com) with "Security" in the subject, the app version and the steps to reproduce.

## Next

See how the file itself is laid out in [JSON Storage Format](https://schemity.com/doc/json-storage-format/), or how to review schema changes in pull requests in [Version Control in Git](https://schemity.com/doc/version-control-git/).
