Research for BE-1597 — how to expose the DuckDB UI of the running mando-bess service (and, the ticket’s second half, how to remotely connect a client to its in-memory DuckDB). Explains why the two prior attempts failed, the origin-check that dooms any reverse-proxy/ALB approach, the SSM port-forward that actually works, the DuckDB-UI extension constraints, the proprietary-frontend licensing blocker for production, the Quack remote protocol (DuckDB 1.5.3+), and a full 1.4.2 → 1.5.4 upgrade assessment. Every fact below is verified — nothing speculative.

For Agents — bottom line

  • Dev/dev-container: use SSM AWS-StartPortForwardingSessionToRemoteHost to forward local port 4213 through the bastion, then browse http://localhost:4213 (literally localhost, NOT 127.0.0.1). No ALB, no reverse proxy, no custom hostname — those can never satisfy the UI’s hardcoded origin check.
  • Production (int_prod trading service): do NOT run the DuckDB UI. The frontend assets are proprietary MotherDuck code served live from ui.duckdb.org with no auth and, per DuckDB’s own docs, “can access the data you load into DuckDB”. Acceptable on a dev laptop / dev container only.
  • Remote client → in-memory DuckDB: the only mechanism is the Quack remote protocol (DuckDB 1.5.3+, 2026-05-20). 1.4.2 has no wire protocol at all. Quack is beta until DuckDB 2.0 (Sept 2026).
  • A 1.4.2 → 1.5.4 upgrade is low-risk for mando (in-memory-only usage sidesteps storage-format churn); details in the upgrade section.

Why the two previous attempts failed (root cause)

Two people (Andras Lederer + Gergely Vászon) took two runs at this on Mar 3–4 2026. Nothing merged. Stale branches left behind:

optimization-universe-iac (IaC):

  • feature/BE-1597
  • feature/BE-1597-enable-duckdb-ui
  • feature/BE-1597-mando-feature-deploy
  • feature/expose-duckdb-ui

mando:

  • feature/BE-1597-add-tunnel-for-duckDB-UI — commit c700fee9
  • feature/BE-1597-proxy-duckdbUI-thorugh-mando — commit 0e06c93e

Attempt A — socat + ALB target group + listener rule

socat bridging port 4214 → an ALB target group + a listener rule with path_pattern /duckdb*. Died on health checks: the target group health-checked port 8080 /health, so the DuckDB target was never healthy. The branch history literally contains commits named temp: disable healthckeck for duckdb (sic).

Attempt B — reqwest reverse proxy inside mando-bess

A reqwest reverse proxy at mando-bess/src/route/duckdb_proxy.rs, rewriting the <base href> and string-patching the DuckDB JS bundle at runtime to try to make it work behind a path prefix.

Root cause of BOTH attempts

The origin check that dooms path-prefix / custom-hostname / reverse-proxy approaches

The DuckDB UI JS bundle hardcodes:

"localhost:4213" === window.location.host ? "duckdb_ui" : "web"

Any origin other than the literal string localhost:4213 silently degrades the UI into “web” mode. A path prefix (/duckdb*), a custom hostname, or an ALB origin can never satisfy this. Both attempts were fighting an un-winnable fight at the wrong layer.

Key insight — why SSM port-forwarding is the answer

SSM lets you choose the LOCAL port → the origin check passes for free

AWS-StartPortForwardingSessionToRemoteHost lets you pick the local port. Forward local 4213 to the remote DuckDB UI port, then browse http://localhost:4213. Because the browser’s window.location.host is now literally localhost:4213, the hardcoded origin check passes. No ALB involvement at all — and the mando ALB is already internal = true anyway.

Browse localhost, not 127.0.0.1 — the check is a literal string comparison on the hostname, and 127.0.0.1:4213 would fail it.

Why a bastion SSM hop is genuinely required

ECS Exec (enable_execute_command) supports only interactive commands, NOT port forwarding. So you cannot port-forward directly into the Fargate task via ECS Exec — a bastion SSM hop (...ToRemoteHost, forwarding from the bastion onward to the task) is genuinely required, not incidental.

DuckDB UI extension constraints (verified against docs)

  • Settings (the complete set): ui_local_port (default 4213), ui_remote_url (default https://ui.duckdb.org), ui_polling_interval (284 ms). There is NO bind-address setting — the UI server binds the IPv6 loopback ::1, which is exactly why socat is required to reach it from anywhere else.
  • No authentication of any kind on the local UI server.
  • The extension proxies the frontend assets from ui.duckdb.org at runtime, per sessioninternet access is required to use the UI.
  • ui_remote_url is only respected when allow_unsigned_extensions is enabled → you cannot mirror / self-host the assets without globally disabling extension signature verification.
  • The ui extension is NOT statically linked in the vendored libduckdb. Verified:
    nm -D lib/libduckdb/1.4.2/linux-amd64/libduckdb.so
    
    → only IcuExtension and JsonExtension appear as DuckDB::LoadStaticExtension<...>. So CALL start_ui_server() auto-downloads the ui extension from extensions.duckdb.org at runtime.

License finding (the important one)

The DuckDB UI frontend is PROPRIETARY MotherDuck code — not acceptable in production

What is permissively licensed (MIT/permissive, unchanged 2025–2026, IP held by Stichting DuckDB Foundation — safe to vendor closed-source with attribution/NOTICE):

  • DuckDB core, the prebuilt libduckdb binaries, duckdb-rs, libduckdb-sys
  • the ui extension source (github.com/duckdb/duckdb-ui)
  • quack, ICU (Unicode-3.0), yyjson

What is NOT open and blocks production use:

  • The DuckDB UI frontend assets served from ui.duckdb.org are proprietary MotherDuck code. The source is not published.
  • DuckDB’s own launch post (https://duckdb.org/2025/03/12/duckdb-ui) states: “The repository does not contain the source code for the frontend, which is currently not available as open-source.”
  • MotherDuck said in Mar 2025 they were “reviewing licensing options”. As of July 2026 there is STILL no published license or terms of use.

Consequence: running the DuckDB UI inside a production container means a third party serves unversioned, unreviewable, closed-source JS that — per DuckDB’s own docs warning — “can access the data you load into DuckDB.” Fine on a dev laptop / dev container; not acceptable for the int_prod trading service.

DuckDB Quack remote protocol (answers the ticket’s second half)

The ticket’s second half — remotely connecting a client to the running in-memory DuckDB — is answered by Quack.

  • Announced 2026-05-12 (https://duckdb.org/2026/05/12/quack-remote-protocol), first shipped in DuckDB 1.5.3 (2026-05-20).
  • CORE, DuckDB-signed extension (repo duckdb/duckdb-quack, MIT). Does NOT need allow_unsigned_extensions. Autoinstall + autoload; can be pre-installed at Docker build time into an extension_directory for airgapped use.
  • Server:
    CALL quack_serve('quack:0.0.0.0:9494', allow_other_hostname => true);
    returns an auth_token.
  • Client:
    ATTACH 'quack:host:9494' AS remote (TOKEN '...');
    -- or
    FROM quack_query(...);
  • Works against an in-memory instance. Full DuckDB feature set over HTTP; concurrent readers/writers.
  • Beta as of 1.5.3 — “protocol, function names, settings and defaults subject to change”. Stable in DuckDB 2.0 (September 2026).
  • This is the ONLY way to remotely connect a client to a running in-memory DuckDB. DuckDB 1.4.2 has no wire protocol at all — so answering the ticket’s second half requires the upgrade below.

Upgrade assessment: 1.4.2 → 1.5.4

Verdict: low-risk for mando

mando’s in-memory-only DuckDB usage sidesteps the storage-format churn that dominates the 1.5.x changelog, and the exact =1.4.2 pin means a bump is a deliberate, controlled change.

Release timeline:

  • 1.4.3 — 2025-12-09 (LTS)
  • 1.4.4 — 2026-01-26 (LTS)
  • 1.5.0 “Variegata” — 2026-03-09
  • 1.5.1, 1.5.2
  • 1.5.3 — 2026-05-20 (Quack)
  • 1.5.4 — 2026-06-17 (latest)
  • 1.4.x LTS EOL: September 2026. DuckDB 2.0 due September 2026.

Crate / toolchain:

  • duckdb-rs version scheme CHANGED at 1.5.0 → format 1.MAJOR_MINOR_PATCH.x. DuckDB 1.5.4 → crate 1.10504.0. A caret range "1.4.2" would silently resolve to 1.10504.0 — but mando pins =1.4.2 exactly (Cargo.toml:60) so it is currently safe.
  • The 1.5.x crate needs rustc >= 1.85.1 / edition 2024. mando’s rust-toolchain.toml pins 1.89.0 — fine. (Consistent with the toolchain bump recorded in remotefs-smb to smb migration.)
  • C API is additive only across 1.4.3..1.5.4 — no removed/renamed symbols. Regenerate bindings when bumping the vendored .so.

Semantics mando relies on — unchanged:

  • In-memory semantics: DuckdbConnectionManager::memory() holds one Connection::open_in_memory() and connect() returns try_clone(), so all r2d2 pooled connections share the same in-memory DB. Unchanged — this is what mando relies on.
  • Arrow appender unchanged (appender-arrow feature intact).
  • Features r2d2 / chrono / serde_json / uuid all still exist.
  • ICU + JSON remain statically linked in official distributions.
  • Storage-format churn is irrelevant — mando is in-memory only (mando-bess/src/lib.rs:177,246).

1.5.0 SQL breaking changes, checked against mando — both clear:

  • date_trunc(DATE) now returns TIMESTAMP — mando’s only date_trunc uses are in Postgres migrations with current_timestamp args → unaffected.
  • Deprecated -> lambda syntax — mando uses no list_transform / list_filter / list_reduce → clear.

Prebuilt bundles exist for all three vendored platforms: libduckdb-linux-amd64.zip, libduckdb-osx-universal.zip, libduckdb-windows-amd64.zip.

Relevant infra facts

Context from optimization-universe-iac (see also be-1595-arrow-flight-dev-deploy-runbook-2026-06-24 for the IaC worktree layout, and be-1595-enabling-arrow-flight-consumers-2026-06-16 for the ECS/SG topology):

  • mando ALB aws_lb.bess_os_service is internal = true (bess_os_load_balancer.tf), 443 only, single target group on port 8080.
  • Bastion (bastion.tf) = t3.medium EC2 in a private subnet, no public IP, SSM Session Manager only; its SG has egress-all and ZERO ingress.
  • mando runs on ECS Fargate, desired_count = 1, private subnets; enable_execute_command = var.environment == "dev" (bess_os_ecs.tf:499) → ECS Exec is dev-only (and even where enabled, only interactive, not port-forwarding — see above).
  • The bess_os_ecs SG (security_group.tf:4-23) uses INLINE ingress blocks. Adding a standalone aws_vpc_security_group_ingress_rule would be silently deleted on the next apply — you must use a dynamic "ingress" block inside the existing resource.
  • No NAT gateway or VPC endpoints are defined in optimization-universe-iac; the VPC is a data lookup on tag base-vpc, owned by another team. Whether the Fargate task can egress to duckdb.org is UNVERIFIED — this matters because the UI proxies assets from ui.duckdb.org and CALL start_ui_server() pulls the ui extension from extensions.duckdb.org at runtime.
  • int and prod both map to account group int_prod (main.tf:5-6) — the misleadingly-named int_prod is INT (integration), reinforcing that the “not acceptable in production” bar applies to this environment group.