Skip to content

Security & Updates

How TDB Enterprise handles security issues, how you receive fixes, and how to report a vulnerability.


Reporting a vulnerability

Please report security issues privately — do not open a public issue.

  • Email security@tdb.asvanlabs.com, or
  • Open a private security advisory on the relevant repository.

Include the version you're running (see Checking your version) and steps to reproduce. We follow coordinated disclosure: advisory details are published after a fix is available and customers have had time to update.

Response targets

Stage Target
Acknowledgement within 48 hours
Assessment & severity rating within 5 business days
Fix released — critical / high within 14 days
Fix released — medium / low within 60 days

Supported versions

Security fixes ship in the latest release. There are no backports to older versions — to stay supported, run the current version and update when a new one is published. Updating is a docker pull + restart (see below).

How you receive a fix

TDB is self-hosted — it never calls home and nothing auto-updates, so you stay in control (including in air-gapped environments). When we publish a fix, you update to the patched image. The steps depend on how you receive TDB:

We deliver the patched version as a versioned image tarball. The image is generic and your license is supplied at runtime, so updating never touches your license:

docker load < tdb-enterprise-vX.Y.Z.tar.gz
# restart your container — your existing -e TDB_LICENSE is unchanged

This works the same way for air-gapped deployments.

As we grow we'll offer registry-based updates, so you can pull a new version directly:

docker pull <registry>/tdb-enterprise:vX.Y.Z
# restart your container — your token is unchanged

We re-issue your trial image on the patched version; load it and restart.

We notify the contact on your account when an update affecting you is available, with the exact version and steps.

Checking your version

Confirm what you're running at any time:

# Authenticated — includes the build commit:
curl -s -H "Authorization: Bearer <your-api-key>" http://<host>:8000/v1/version
# → {"version": "X.Y.Z", "build_sha": "abc1234"}

# Unauthenticated liveness banner also carries the version:
curl -s http://<host>:8000/

The running version is also printed in the container logs at startup (tdb_startup version=… build_sha=…).

Where advisories are published

Once a fix is released and customers have had time to update, advisories are published as GitHub Security Advisories and summarized here. For the Community Edition's security policy and known design constraints, see the SECURITY.md in the open-source repository.

2026-11-10 — MCP tools named tables and columns outside a key's policy

Enterprise 0.22.0 to 0.36.0, with a policy file; fixed in 0.36.1. Community is not affected: it has neither policies nor these tools.

When preview_source, filter_source or aggregate_source was given a table or column it could not find, its error listed the whole database: every table, including those outside the caller's rule, and every column of the table, including those the rule withholds. A key under a policy could learn those names (never values) by asking for a name that does not exist; schema_source and the REST schema already hid them. Found by running policies against a real Snowflake account, where the tools also refused a table or column name not written in Snowflake's upper case.

From 0.36.1 those tools check names against what the caller's rule shows, in any case, and an error lists nothing else. A table outside the rule is refused (policy_table_not_permitted) whether or not it exists, as SQL is.

Action: upgrade if a policy file hides tables or columns whose names should not be known to the keys it restricts. To look back, search the audit log for invalid_arguments entries from those keys on mcp_query.

2026-11-07 — A SQL Server driver could name a library to load

Enterprise 0.34.0 and earlier; fixed in 0.35.0. Community is not affected: it has no SQL Server connector.

A SQL Server source's driver went into the ODBC connection string as written, and the ODBC driver manager loads a library from a path given there. So a key allowed to register sources (readwrite or admin) could make TDB load any shared library on its filesystem, which is code execution where that caller can also put a file on the host, such as a tenant whose data directory it can write. Reproduced on the 0.34.0 image. The other connection values were not escaped either, so a ; in a password broke the login and could add connection attributes.

From 0.35.0 driver must be an installed driver's name, such as ODBC Driver 18 for SQL Server: anything else is refused at registration (400, audited invalid_connection) and a stored source holding one fails before anything is loaded. Every other value is escaped.

Found in the same review, and fixed in 0.35.0: a PostgreSQL query opening with a comment read its whole result into TDB's memory before the row cap applied.

Action: upgrade. Then list your sources and check each SQL Server driver is a driver name; re-register any that is not. To look back, search the audit log for register entries by keys you do not fully trust.

2026-11-06 — One reading of the Authorization header

Enterprise 0.33.0. A hardening change, not a vulnerability report: no data was exposed.

Two parts of TDB read the Authorization header in different ways. Authentication took the first header and the bearer scheme in any case. The audit log's identity took the last header, and MCP and the policy caller lookup read only Bearer. So a request with two headers could be authenticated as one credential and recorded as another, and a lowercase bearer was refused on MCP. From 0.33.0 every reader uses authentication's reading, and a request with two Authorization headers is refused with 400. The same review closed two holes in the unreleased tenant code before 0.33.0 shipped.

Action: none, unless a client sends the header twice; it now gets 400.

2026-11-05 — SQL injection through filter_source

Enterprise 0.30.0 and earlier; fixed in 0.31.0. Community is not affected: it has no filter_source.

filter_source escaped its value by doubling ' only. On MySQL and Snowflake a backslash is also an escape, so a value holding \' ended the string and the rest ran as SQL. A key limited to filter_source could read any table the source's database user could. A policy file's rewrite still applied to the injected SQL. On MySQL it ran inside TDB's read-only session, so it could not write. Snowflake has no such session, and this SQL did not pass through TDB's read-only check, so there only the role you registered limits it: check that it holds nothing beyond SELECT. Reproduced on a real MySQL. Snowflake came from reading its rules; on 2026-11-10 the fixed filter_source was run against a real Snowflake account with both quoting attacks, and each returned no rows. PostgreSQL, SQL Server, CSV, Parquet and DuckDB treat a backslash as an ordinary character by default and were not exposed this way.

Found in the same pass, and fixed in 0.31.0:

  • standard_conforming_strings=off on a PostgreSQL source let filter_source values and view parameters break out of their strings the same way. TDB now pins it on for its own sessions. On MySQL it now removes NO_BACKSLASH_ESCAPES for the same reason.
  • PUT /v1/auth/keys/{id}/tools with a missing or misspelt field answered 200 and removed the key's tool limits. It is now a 422. So is .../rate-limit.

Action: upgrade if you have MySQL or Snowflake sources, or a PostgreSQL role or database with standard_conforming_strings off, and keys you do not fully trust. To look back, search the audit log's filter_source entries for a sql holding a backslash. Then list your keys and check that each allowed_tools is what you set.

2026-11-04 — Audit and policy hardening

Enterprise 0.27.0 to 0.30.0, community 0.16.0 to 0.17.1. Found by two independent reviews and the testing that followed. None has an advisory: each closes a gap in evidence or a default, not a way in.

  • Values in SQL can be kept out of the logs (0.27.0 / 0.16.0). A statement is stored as written, so a value in it sits in the log. TDB_AUDIT_SQL=redacted replaces values with ?. It is opt-in. New audit files are also created 0600.
  • A query that fails is now audited (0.28.0 / 0.17.0). One the engine rejected, or that hit a missing file or an unexpected error, reached only the application log. In enterprise, MCP tools and view runs had also left timeouts and busy sources unrecorded. A failure under a column or row policy names the rule from 0.29.0.
  • A policy file refuses sources it does not name (0.30.0). They were open to every key, as documented, so a typo or a new source silently removed every rule.

Action: upgrade. If you use TDB_POLICIES_FILE, list every source first, or set unlisted_sources: allow. Before 0.30.0, check that every source you meant to restrict is named in the file, with exactly its registered name. Before 0.28.0, the audit log has no entry for a failed query, so look in the application log for query_error and query_execution_error.

2026-11-04 — SQL could read other files in the data directory

Fixed in community 0.15.0 and enterprise 0.26.0. Found by an independent review. Advisory: GHSA-c2ww-8m4q-8q58.

SQL on a CSV or Parquet source could read any other file in the source's data directory. read_csv and read_text returned contents, and glob listed names, on REST and MCP. Nothing outside the directory was reachable.

Who was affected: any deployment with other files in the data directory, on community 0.7.0 to 0.14.0 and enterprise 0.11.0 to 0.25.0. Before those, the wider issue in the 2026-09-28 entry applied.

  • Enterprise: a key for one source could read another source's file, whatever its role, including a read key.
  • Community: the one key could already register another file in that directory instead. This also read files that are not CSVs, and got around the one-source limit.

How to check: search the audit log for read_csv, read_text, read_blob or glob in the sql field of successful queries on versions before the fix.

Action: upgrade. If a key that should not see a file in that directory could have read it, review the audit log, and treat secrets in those files as exposed.

On upgrade: SQL that read another file now returns 403. Each file has its own engine, limited to 4 threads in enterprise, so a single large query can be slower: Performance.

2026-11-04 — A built-in API key worked when none was set

Fixed in community 0.14.0 and enterprise 0.25.0. Found by an independent review. Advisory: GHSA-8mfc-29h8-v8rm.

With TDB_API_KEYS unset, TDB fell back to dev-insecure-key-change-me, which is published in its own source and in these docs. Our docs also told Docker Desktop users to use it.

Who was affected: any deployment started without TDB_API_KEYS, on every earlier release. The community Compose file used the same default and published the port on every interface.

  • Enterprise: static keys are always admin, so anyone who could reach the port had admin access: all sources, keys and audit. It printed no warning.
  • Community: anyone who could reach the port could query and replace the source. It printed a warning at startup.

How to check: curl -H "Authorization: Bearer dev-insecure-key-change-me" http://<host>:8000/v1/sources. A 200 means you are exposed. The audit log's key_hint for that key starts with dev-in.

Action: upgrade, and set a generated TDB_API_KEYS. TDB now refuses to start with an example value, or when unset unless another login exists (enterprise). If the port was reachable by anyone you do not trust, review the audit log, and rotate your API keys and the credentials of your registered sources.

2026-11-04 — filelock advisories closed

Enterprise 0.23.1 moves filelock to 4.2.7, which closes the advisories image scans reported against it (GHSA-8mx5-7m5m-r77g, GHSA-cwqr-hh33-gh44, GHSA-rfpc-qcw5-3pc4, GHSA-69hj-f68j-2879, GHSA-j8f7-rjxc-mr56, GHSA-fq56-3cx3-fjxp and earlier ones).

TDB was not known to be exposed. Only the Snowflake connector uses filelock, for caches in a private directory, in a container that runs as one user.

Action: upgrade, so your image scans stop reporting them. If you use Snowflake, try it on the new version first: the connector's own limit on filelock is overridden. (That pairing has since run against a real Snowflake account, on 0.35.0, on 2026-11-10.)

2026-10-29 — A rotated API key could call every MCP tool

Fixed in enterprise 0.21.2. Community is not affected (it has no key management). Rotating a DB-managed key (POST /v1/auth/keys/{id}/rotate) issued a replacement with the same name, expiry, rate limit and role, but without its allowed_tools list or its Lean Mode setting. A key with no allowed_tools may call every tool, so a key limited to, for example, query_source came back from rotation able to call all seven MCP tools. Every enterprise release from per-key tool scoping up to 0.21.1 is affected.

This matters most if you followed the 2026-10-27 advice below and rotated keys after upgrading to 0.20.3.

Action:

  1. Upgrade to 0.21.2.
  2. List your keys (GET /v1/auth/keys). For any key that should be limited and shows "allowed_tools": null, set its tools again (PUT /v1/auth/keys/{id}/tools), and its Lean Mode default if it had one (PUT /v1/auth/keys/{id}/lean-mode).

Roles were always carried over; no key gained a role it did not have.

2026-10-27 — Enterprise wrote API keys to the application log

Fixed in enterprise 0.20.3. Community is not affected. TDB Enterprise wrote the caller's full API key into its plain-text application log (the container's standard output) as by=… on every successful query, source registration and deletion, refused, failed or timed-out query, and view run, and on a prompt-injection refusal over MCP. Anyone able to read those logs, or a log store they are shipped to, could use the keys. Every enterprise release before 0.20.3 is affected.

Log lines now carry only the first six characters, as the audit log always has. The signed audit log never held a full key.

Action:

  1. Upgrade to 0.20.3.
  2. Rotate API keys (POST /v1/auth/keys/{id}/rotate, or a new value in TDB_API_KEYS) unless everyone with access to TDB's application logs, including any log store they are forwarded to, is trusted with every key.
  3. Purge retained application logs from before the upgrade, or restrict them as you would a secrets store.

JWTs and OAuth tokens were not written: for those, the log carried the token's subject.

2026-10-27 — Log lines could be forged through caller input

Fixed in enterprise 0.20.2 and community 0.12.3. TDB writes source names, SQL fragments and database error messages into its plain-text application log (the container's standard output). A value containing a line break was written as is, so a caller with query access could make text appear as a separate log line, for example one that looks like an unrelated event. Database errors also spanned several lines on their own.

Line breaks in everything TDB logs are now escaped (\n, \r), so each log call is one line. Ordinary values log exactly as before, and tracebacks are unchanged.

  • The signed audit log was never affected. It is JSON, one entry per line, and hash-chained; this concerns only the application log.
  • Medium severity: it needs a valid API key, and it misleads a reader of the log rather than exposing data.

Action: upgrade. If you alert on, or parse, the application log, a log line can no longer be split by caller input; nothing needs changing on your side.

2026-10-26 — certifi trusted CAs with compromised keys

Fixed in enterprise 0.19.1 and community 0.12.1. certifi, the CA bundle Python's HTTPS clients verify against, trusted emSign root CAs whose intermediate CA keys were compromised (GHSA-6q48-6c7w-9gw2, critical). A certificate issued from those keys passed verification for any hostname.

  • Enterprise was exposed on the three outbound connections that use certifi's bundle: the Splunk exporter, the S3 exporter and the Snowflake connector. An attacker already on the network path, holding such a certificate, could have read or altered that traffic, including exported audit entries and Snowflake credentials and results. The CIMD fetch and the PostgreSQL wire gateway use the system trust store and were not affected.
  • Community's server makes no outbound TLS connection. Only the tdb CLI, pointed at a TDB server over HTTPS, used the bundle.

Enterprise 0.19.1 also reports three filelock advisories in image scans (GHSA-v4x8-3wr5-3wq8, GHSA-9vhv-jxww-xfm5, GHSA-567q-5qvw-pf92). TDB is not exposed: they cover locks TDB does not use and Windows-only behaviour, and the fixed filelock 4.x cannot be installed alongside the Snowflake connector yet. Enterprise 0.23.1 moves to filelock 4.2.7, which closes these and the later filelock advisories.

Action: upgrade. If you export to Splunk or S3, or query Snowflake, over a network you do not control, treat the credentials those connections carry as worth rotating.

2026-10-10 — Enterprise 0.16.0 and 0.17.0: two hardening fixes

Fixed in enterprise 0.16.0 and 0.17.0. Community is not affected.

  • TDB_MAX_ROWS now bounds the MCP tools and view runs (0.16.0). preview_source, filter_source, aggregate_source, run_view and POST /v1/views/{name}/run capped limit at a fixed 1,000, whatever TDB_MAX_ROWS said. An operator who lowered the ceiling to limit how much data one call can return still got up to 1,000 rows from these paths. A limit above the ceiling is now refused and audited limit_exceeds_max, as on POST /v1/query.
  • The prompt-injection output filter fails closed (0.17.0). If the filter itself raised an error, the tool result was returned unfiltered. It is now a tool error, and the rows are withheld. No input that triggers such an error is known; this closes the gap rather than a demonstrated bypass.

Action: upgrade. If you lowered TDB_MAX_ROWS, check the audit log for MCP query entries whose rows_returned exceeded it before the upgrade.

2026-10-09 — pip removed from the images

Community 0.9.1 and enterprise 0.14.1. The python:3.12-slim base image ships pip 25.0.1, which image scanners report for six CVEs, among them CVE-2026-13346 (GHSA-qwm4-qh6w-59xr). TDB was not exposed: it never runs pip, because its dependencies are installed with uv at build time. Rather than upgrade a tool nothing uses, the images no longer contain pip, including ensurepip's bundled copy. A Trivy scan of the rebuilt images reports no Python-package findings.

Action: upgrade if your image scanner reports pip in a TDB image. Nothing else changes.

2026-10-09 — The read-only check could be shown one statement while the engine ran several

Fixed in community 0.7.2 and enterprise 0.12.0. Affects every earlier release. Advisory GHSA-qmj5-8fv9-h74r. The SQL validator decided where string literals and comments end the ANSI way only, while the engines TDB runs on also read dollar-quoted strings, backslash escapes and nested or engine-specific comments. Quoted carefully, SQL could therefore carry a second statement past the one-statement rule introduced in 0.7.0 / 0.11.0, and the CSV engine executed it. The engine lock from 0.7.0 / 0.11.0 still confined file access to the data directory, but within that directory a second statement could write files — including the registered CSV. Exploiting it requires a valid API key or token.

The fixed releases check the SQL under each supported engine's reading of it, and the CSV connector runs a query only if DuckDB's own parser reads exactly one SELECT. Refusals are 400, audited as sql_validation_failed.

Action: upgrade. If you ran an earlier release with keys held by people or agents you would not give write access to the data directory, check that the CSV files there are the ones you expect.

2026-10-09 — Enterprise 0.12.0: four further hardening fixes

Fixed in enterprise 0.12.0. Found in the same review:

  • allowed_tools now applies on REST. A key scoped to MCP tools could still use the equivalent REST routes — notably POST /v1/query — so a key meant only to run views could send its own SQL. See RBAC → Restricting tool access.
  • Privileged database roles are refused. A source registered as a PostgreSQL superuser, a MySQL user with FILE, or a SQL Server sysadmin/bulkadmin login let any key allowed to query it read files on the database host, because a read-only session does not stop those functions. Such sources now return 403 (privileged_db_role) until they use a least-privileged role, or TDB_ALLOW_PRIVILEGED_DB_ROLE=true is set. See use a least-privileged role.
  • PostgreSQL passwords are never sent in cleartext to an unverified server. TDB now sets require_auth=!password unless the source uses sslmode=verify-full, and accepts sslmode / sslrootcert keys. See SSL connections.
  • View string parameters are escaped for MySQL and Snowflake, whose literals honour backslash escapes. See views.

Action: upgrade; then re-register any database source that used a superuser, root or sa with a SELECT-only role, and review keys whose allowed_tools you relied on — check the audit log for POST /v1/query entries from them.

2026-09-28 — SQL on a CSV source could read files outside the data directory

Fixed in community 0.7.0 and enterprise 0.11.0. Affects every earlier release. DuckDB resolves file paths written inside a query — for example read_csv('/etc/passwd') — and TDB_ALLOWED_DATA_DIR only ever checked the path a source was registered with. A caller able to query a CSV source (in enterprise, any role, read included) could therefore read any file the server process could read. Exploiting it requires a valid API key or token.

The fixed releases confine SQL to the source's data directory, disable extension loading and lock the engine's settings. A refused read returns 403 and is audited as sql_file_access. They also refuse a query containing more than one statement, and in enterprise a key created without a role is now read rather than admin.

Action: upgrade. If you ran an earlier release with keys held by people or agents you would not give shell-level file read to, review the audit log for read_csv, read_text or other file paths in sql, and rotate any secret stored in a file the server process could read.