---
summary: "Tenant-isolation hardening on the dataframe surface — gate brapi_dataframe_describe list-all under shared-trust HTTP, reject system-catalog reads in brapi_dataframe_query, and forward gate context on rejection."
breaking: false
---

# 0.5.2 — 2026-05-05

Closes two enumeration paths through the dataframe surface that would have let a caller without a known `df_<uuid>` name fish through other concurrent clients' workspaces under shared-trust HTTP. Possession of a dataframe name is the capability gate; these fixes make that gate stand on its own. Also forwards structured gate context (catalog, operators, statementType) on SQL rejection so structured-only consumers see which specific element matched without parsing the message text.

## Added

- **System-catalog SQL gate in `CanvasBridge.query()`.** Rejects queries that reference DuckDB's metadata views and introspection functions: `information_schema.*`, `pg_catalog.*`, `sqlite_master`, `sqlite_temp_master`, and 30+ `duckdb_*(…)` functions (`duckdb_tables`, `duckdb_columns`, `duckdb_views`, `duckdb_databases`, `duckdb_schemas`, `duckdb_functions`, etc.). Without this gate, `SELECT * FROM information_schema.tables` would leak the full `df_<uuid>` namespace on a shared canvas, bypassing `brapi_dataframe_describe`'s possession-required list-all gate. Throws `validationError` with `data.reason = 'system_catalog_access'` and a `catalog` label naming which family matched. Comments and string literals are stripped before matching so a column named `information_schema_id` or a literal value `'information_schema.tables'` doesn't false-positive. `SYSTEM_CATALOG_ACCESS_REASON` is exported from `@/services/canvas-bridge` so the dataframe-query tool's contract translator routes it through `sql_rejected` alongside the framework's `SQL_GATE_REASONS`.
- **Typed error contract on `brapi_dataframe_describe`: `list_all_disabled_on_shared_http`.** Calling the tool without a `dataframe` argument under `MCP_AUTH_MODE=none` on HTTP transport (where every caller resolves to `tenantId='default'` and shares the canvas) now throws `Forbidden` with structured recovery: pass `dataframe` with a name from a prior `find_*` spillover (`result.dataframe.tableName`) or one you registered via `brapi_dataframe_query` with `registerAs`. Stdio is unaffected (only the operator runs there); JWT/OAuth is unaffected (each caller carves their own tenant). Tool description now flags this gate inline so an LLM client doesn't burn a call discovering the policy.
- **`tests/config/server-config.test.ts`.** New unit suite covering `getServerConfig` env-var mapping, defaults, type coercion, and validation errors. Required exporting `ServerConfigSchema` from `src/config/server-config.ts` for testability — purely additive, no runtime change.

## Changed

- **`brapi_dataframe_query` forwards gate context on rejection.** When `bridge.query()` rethrows a gate `validationError`, the handler's `sql_rejected` translator now spreads the original error's structured fields (other than `reason` and `recovery`) into `data` — so structured-only consumers see `data.catalog` for system-catalog rejections, `data.operators` for plan-operator rejections, `data.statementType` for non-SELECT rejections, etc. — without parsing the human message text. New `extractGateContext` helper isolates the forwarding logic. Recovery hint and `gateReason` keys still come from the contract / framework.
- **`brapi_dataframe_describe` and `brapi_dataframe_query` descriptions emphasize the inline-name flow.** Both tools now state explicitly that the dataframe name appears on every `find_*` response that spilled (`result.dataframe.tableName`), so the typical flow is `find_*` → read the name → query / describe by name. Reduces wasted "list everything first" calls — and aligns with the new shared-trust HTTP policy where listing requires possession.
- **README rewritten around multi-agent collaboration as the headline value.** New top-of-file framing ("A collaborative BrAPI v2.1 workspace for multi-agent research via MCP"), new **Multi-agent workflows** section explaining the connection-state vs dataframe-state split (both tenant-scoped, dataframe `df_<uuid>` names act as in-tenant capability tokens, 24h TTL caps blast radius, provenance trail supports audit), and new **Deployment shapes** section replacing the older **Multi-user HTTP deployments** table with a clearer shared-trust vs per-user-credentials axis. Documents the two new gates inline.
- **Server descriptions refreshed across `package.json`, `server.json`, README h1, and the GitHub repo metadata** to lead with multi-agent collaboration. `server.json` carries a condensed variant to fit the MCP Registry's 100-character cap.

## Dependencies

- **`@cyanheads/mcp-ts-core` ^0.8.15 → ^0.8.16.** Picks up the upstream gate context (catalog / operators / statementType structured fields) that the dataframe-query handler now forwards.
