Skip to content

feat(federation): env-gated unsigned DuckDB extensions (+ Firebird proposal)#67

Merged
tobias-gp merged 9 commits into
mainfrom
feat/duckdb-unsigned-extensions
Jun 5, 2026
Merged

feat(federation): env-gated unsigned DuckDB extensions (+ Firebird proposal)#67
tobias-gp merged 9 commits into
mainfrom
feat/duckdb-unsigned-extensions

Conversation

@tobias-gp
Copy link
Copy Markdown
Contributor

@tobias-gp tobias-gp commented Jun 5, 2026

Summary

  • Implemented: Adds an opt-in DUCKDB_ALLOW_UNSIGNED_EXTENSIONS env var (default off). When enabled, project DuckDB instances are created with allow_unsigned_extensions, and the federation console accepts INSTALL <extension> FROM '<source>' for custom repositories/paths. When unset, behavior is identical to today (signed core + FROM community only).
  • Proposal only (no code): Includes the OpenSpec change add-firebird-connection-type, which proposes an env-gated firebird connection type built on top of the unsigned-extensions gate. This is design/spec docs for review, not implemented in this PR.

What changed (implemented)

  • packages/core/src/config/env.tsDUCKDB_ALLOW_UNSIGNED_EXTENSIONS + allowUnsignedExtensions() helper.
  • packages/core/src/services/duckdb.tscreateDuckDBInstance() applies allow_unsigned_extensions at instance creation (the option can only be set at startup); all three creation sites route through it. fromSource threaded through ensureProjectExtensionLoadedinstallAndLoadExtension, emitting INSTALL <ext> FROM '<source>' (single-quote-escaped).
  • packages/core/src/services/duckdb-console.tsparseExtensionSql(sql, allowUnsigned) matches the env-gated FROM '<source>' shape; rejected with the existing 400 message when the flag is off.
  • Docs: .env.example and a new "Data Federation (DuckDB)" table in apps/docs/.../reference/docker.mdx, both with a security caution that unsigned extensions run arbitrary native code.
  • OpenSpec: add-unsigned-duckdb-extensions (proposal/design/tasks/specs, all tasks checked off).

Test plan

  • npx vitest run packages/core/src/services/duckdb-console.test.ts — 16/16 pass (5 new cases for the gated parser)
  • pnpm typecheck exits 0
  • pnpm lint exits 0
  • pnpm --filter @archmax/api build exits 0 (runs as part of typecheck)
  • Manual: with DUCKDB_ALLOW_UNSIGNED_EXTENSIONS=true, INSTALL <ext> FROM '<repo>' loads from the console; with it unset, the same statement is rejected with 400

Notes

  • The add-firebird-connection-type proposal has an open question (exact ATTACH DSN for the custom duckdb_firebird extension) to resolve during its own implementation PR. It is intentionally not implemented here.

Made with Cursor


Note

High Risk
Enabling the flag loads unsigned native code and turns on allow_unsigned_extensions for all DuckDB instances; misconfiguration or a compromised extension repo is a serious security concern despite API/console restrictions.

Overview
Adds an opt-in, default-off firebird data source backed by a custom unsigned DuckDB extension, gated by DUCKDB_ENABLE_CUSTOM_FIREBIRD (not a general unsigned-extensions flag).

When enabled, project DuckDB instances are created with allow_unsigned_extensions, the Firebird extension is auto-installed from a fixed archmax-hosted repo (SET custom_extension_repository + INSTALL/LOAD firebird), and connections attach via TYPE FIREBIRD with structured fields passed as ATTACH options (empty path + HOST/PORT/DATABASE/etc.) to avoid Windows-path / URI parsing bugs. When disabled, create/update/test for Firebird return 400, active Firebird connections are skipped during federation (without breaking other sources), and the federation console still rejects INSTALL … FROM '<arbitrary source>'.

API & UI: firebird on the connection model, optional charset in config, /api/config exposes firebirdEnabled, and the connections form shows Firebird only when the flag is on (defaults port 3050, charset UTF8).

Docs & specs: .env.example, Docker reference, data-federation guide, OpenSpec change add-firebird-connection-type, plus integration/unit tests for gates and attach building.

Reviewed by Cursor Bugbot for commit dc5a205. Bugbot is set up for automated code reviews on this repo. Configure here.

Tobias Grosse-Puppendahl and others added 2 commits June 5, 2026 09:42
Add an opt-in DUCKDB_ALLOW_UNSIGNED_EXTENSIONS env var. When enabled,
project DuckDB instances are created with allow_unsigned_extensions and
the federation console accepts `INSTALL <ext> FROM '<source>'` for custom
repositories or paths. Default-off preserves current behavior (signed core
and community extensions only).

Includes the OpenSpec change `add-unsigned-duckdb-extensions`.

Co-authored-by: Cursor <cursoragent@cursor.com>
Proposal-only (no implementation). Adds an env-gated `firebird` connection
type backed by the unsigned duckdb_firebird extension, activated by
DUCKDB_ENABLE_FIREBIRD (effective only when DUCKDB_ALLOW_UNSIGNED_EXTENSIONS
is also set) with an optional DUCKDB_FIREBIRD_EXTENSION_REPOSITORY override.

Co-authored-by: Cursor <cursoragent@cursor.com>
@railway-app railway-app Bot temporarily deployed to archmax SemLayer / archmax-pr-67 June 5, 2026 07:43 Destroyed
@railway-app
Copy link
Copy Markdown

railway-app Bot commented Jun 5, 2026

🚅 Deployed to the archmax-pr-67 environment in archmax SemLayer

Service Status Web Updated (UTC)
archmax_standalone_with_volume ✅ Success (View Logs) Jun 5, 2026 at 9:13 am
archmax_standalone ✅ Success (View Logs) Jun 5, 2026 at 9:13 am
archmax_external_dbs ✅ Success (View Logs) Jun 5, 2026 at 9:13 am

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security review complete: I did not find concrete issues in the changed files.

Checked threat surfaces:

  • MCP endpoint auth: apps/api/src/mcp/archmax-route.ts authenticates the bearer token and resolves the project before registering/running tools, re-authenticates resumed sessions against the original token/project/slug, and returns JSON-RPC auth errors for invalid credentials. Token scopes flow into registerArchmaxTools and are enforced by the MCP tool handlers.
  • Query execution sandboxing: packages/core/src/services/mcp-tools.ts validates SQL with validateSqlAst before acquiring the project DuckDB instance, materializes scoped model VIEWs, hardens the connection/search path, enforces withQueryTimeout, and truncates results at MAX_ROWS. The new unsigned-extension flag in packages/core/src/services/duckdb.ts does not relax the MCP validator, and packages/core/src/services/duckdb-console.ts gates custom-source INSTALL parsing behind allowUnsignedExtensions().
  • Admin auth: packages/core/src/config/env.ts still requires BETTER_AUTH_SECRET length >= 32; the API app applies CSRF middleware before session-authenticated routes; Better Auth production cookies remain Secure, HttpOnly, and SameSite=Lax.
  • API input validation: changed console POST bodies continue to use Zod validation, extension names remain allowlisted, and the new custom source is single-quote escaped before interpolation into DuckDB SQL.
  • Environment secrets: no .env.local or hardcoded secrets were added; docs/examples use placeholders and warn about unsigned native-code risk. Existing DuckDB error paths redact connection secrets before returning/logging.
  • Dependency exposure: no dependency manifest changes were included in this PR, so no new dependency exposure was introduced.
Open in Web View Automation 

Sent by Cursor Automation: archmax Security Review

Comment thread packages/core/src/services/duckdb.ts
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 5, 2026

Docker image ready

docker pull ghcr.io/archmaxai/archmax:pr-67

The federation console PR is merged, so sync specs/ with reality: land the
duckdb-console capability and the documentation-site/frontend-shell updates,
and move the change into changes/archive/. This lets the stacked
add-unsigned-duckdb-extensions delta build on the now-published spec.

Co-authored-by: Cursor <cursoragent@cursor.com>
@railway-app railway-app Bot temporarily deployed to archmax SemLayer / archmax-pr-67 June 5, 2026 08:00 Destroyed
Add an optional, unsigned Firebird connection type gated behind
DUCKDB_ENABLE_CUSTOM_FIREBIRD. Enabling it activates the `firebird`
connection type, starts DuckDB instances with allow_unsigned_extensions,
and installs the custom Firebird extension from a configurable repository.
API rejects firebird connections when the flag is off, and the UI exposes
availability via /api/config. Update the duckdb test mock with the new
config/env exports.

Co-authored-by: Cursor <cursoragent@cursor.com>
@railway-app railway-app Bot temporarily deployed to archmax SemLayer / archmax-pr-67 June 5, 2026 08:02 Destroyed
Comment thread packages/core/src/services/duckdb.ts
Comment thread apps/api/src/routes/connections.ts
Adds the remaining Firebird coverage to complete the feature:
- env helper tests for allowUnsignedExtensions/customFirebirdEnabled/
  firebirdExtensionRepository
- buildAttachString firebird DSN tests (default port/charset, uri pass-through)
- API integration test for the firebird create/update 400 gate and the
  accepted-when-enabled path including charset
- data-federation guide Firebird section and checked-off tasks

Co-authored-by: Cursor <cursoragent@cursor.com>
@railway-app railway-app Bot temporarily deployed to archmax SemLayer / archmax-pr-67 June 5, 2026 08:12 Destroyed
Comment thread packages/core/src/services/duckdb.ts
…rces

Address Bugbot findings on the env-gated Firebird / unsigned-extension work:

- Skip active firebird connections during federation when
  DUCKDB_ENABLE_CUSTOM_FIREBIRD is off, so a leftover source no longer
  aborts getProjectInstance and breaks DuckDB for the whole project.
- Gate partial PUT updates on an existing firebird connection by the
  stored type, not just the (optional) incoming type.
- Honor an explicit console INSTALL <ext> FROM '<source>' for firebird
  instead of always installing from the default repository.
- Re-run the install in ensureProjectExtensionLoaded when a custom
  fromSource is requested, even if the extension is already loaded.

Co-authored-by: Cursor <cursoragent@cursor.com>
@railway-app railway-app Bot temporarily deployed to archmax SemLayer / archmax-pr-67 June 5, 2026 08:22 Destroyed
Comment thread packages/core/src/services/duckdb.ts
Move the completed change into changes/archive/ and apply the
reconciled "Admin User Seeding" requirement to the auth spec.

Co-authored-by: Cursor <cursoragent@cursor.com>
@railway-app railway-app Bot temporarily deployed to archmax SemLayer / archmax-pr-67 June 5, 2026 08:36 Destroyed
Comment thread packages/core/src/services/duckdb.ts
…rd only

Build Firebird connections from explicit ATTACH options
(HOST/PORT/DATABASE/USER/PASSWORD/CHARSET) with an empty ATTACH path instead
of a key=value/URI DSN. The custom extension parses the ATTACH path as a DSN,
so a Windows drive-letter colon (e.g. C:\db.fdb) was fed to std::stoi and
threw "Invalid Error: stoi", failing every attach. Options also safely carry
passwords/paths containing URI metacharacters the DSN parser cannot decode; a
raw `uri` is still passed through as the ATTACH path.

Consolidate unsigned-extension support behind the single
DUCKDB_ENABLE_CUSTOM_FIREBIRD switch: drop DUCKDB_ALLOW_UNSIGNED_EXTENSIONS and
DUCKDB_FIREBIRD_EXTENSION_REPOSITORY and the console's custom-source install
path, and fold the add-unsigned-duckdb-extensions proposal into
add-firebird-connection-type.

Co-authored-by: Cursor <cursoragent@cursor.com>
@railway-app railway-app Bot temporarily deployed to archmax SemLayer / archmax-pr-67 June 5, 2026 09:10 Destroyed
POST /connections/:id/test ran testSingleConnection without checking
customFirebirdEnabled(), so a stored firebird connection could install
and load the unsigned extension even when DUCKDB_ENABLE_CUSTOM_FIREBIRD
is off. Gate the test route (400) and add a defensive guard in
testSingleConnection itself.

Co-authored-by: Cursor <cursoragent@cursor.com>
@railway-app railway-app Bot temporarily deployed to archmax SemLayer / archmax-pr-67 June 5, 2026 09:12 Destroyed
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit dc5a205. Configure here.

await db.run(`SET custom_extension_repository = '${repo}'`);
await db.run("INSTALL firebird");
await db.run("LOAD firebird");
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Custom repo breaks core installs

High Severity

The Firebird install path sets custom_extension_repository on the shared project DuckDB instance and never restores the default. Later INSTALL calls for core extensions (e.g. postgres, iceberg) without an explicit FROM community clause can resolve against the Firebird repo instead of DuckDB’s core catalog, so multi-source projects may fail attach or extension load depending on connection order.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit dc5a205. Configure here.

case "sqlite":
return "sqlite";
case "firebird":
return "firebird";
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setup SQL omits Firebird options

Medium Severity

buildRedactedAttachSql still builds Firebird setup commands from buildAttachString only, which returns an empty ATTACH path for structured Firebird configs. The runtime attach path appends buildFirebirdAttachOptions (HOST, PORT, DATABASE, etc.), so console setup copy-paste SQL does not match what the server actually runs and will not attach structured Firebird sources.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit dc5a205. Configure here.

@tobias-gp tobias-gp merged commit b5334c5 into main Jun 5, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant