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:
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=offon a PostgreSQL source letfilter_sourcevalues and view parameters break out of their strings the same way. TDB now pins it on for its own sessions. On MySQL it now removesNO_BACKSLASH_ESCAPESfor the same reason.PUT /v1/auth/keys/{id}/toolswith 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=redactedreplaces values with?. It is opt-in. New audit files are also created0600. - 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
readkey. - 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:
- Upgrade to 0.21.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:
- Upgrade to 0.20.3.
- Rotate API keys (
POST /v1/auth/keys/{id}/rotate, or a new value inTDB_API_KEYS) unless everyone with access to TDB's application logs, including any log store they are forwarded to, is trusted with every key. - 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
tdbCLI, 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_ROWSnow bounds the MCP tools and view runs (0.16.0).preview_source,filter_source,aggregate_source,run_viewandPOST /v1/views/{name}/runcappedlimitat a fixed 1,000, whateverTDB_MAX_ROWSsaid. 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. Alimitabove the ceiling is now refused and auditedlimit_exceeds_max, as onPOST /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_toolsnow applies on REST. A key scoped to MCP tools could still use the equivalent REST routes — notablyPOST /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 Serversysadmin/bulkadminlogin 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, orTDB_ALLOW_PRIVILEGED_DB_ROLE=trueis set. See use a least-privileged role. - PostgreSQL passwords are never sent in cleartext to an unverified
server. TDB now sets
require_auth=!passwordunless the source usessslmode=verify-full, and acceptssslmode/sslrootcertkeys. 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.