Skip to content

Dev-mode UDFs bypass server sandbox denylist enabling unauthenticated filesystem oracle (conditional RCE)

Low
julien040 published GHSA-4rr9-66j6-r74m Aug 5, 2026

Package

gomod github.com/julien040/anyquery (Go)

Affected versions

>= 0.4.5, <= 0.4.6

Patched versions

0.5.0

Description

Summary

anyquery's server-mode sandbox (added to fix CVE-2026-50006/CVE-2026-47253) gates dangerous SQL functions using a fail-open denylist on the SQLite SQLITE_FUNCTION authorizer branch. When the server is started with --dev, three developer UDFs (load_dev_plugin, reload_dev_plugin, unload_dev_plugin) are registered on every connection but are absent from that denylist, and --dev does not disable the sandbox. An unauthenticated MySQL client (default auth is NewAuthServerNone) can therefore call load_dev_plugin('x', '<any path>'), whose implementation performs os.ReadFile on the raw path with no AllowedDirs/CheckFileRead guard — bypassing the file-confinement the sandbox exists to provide.

Root Cause

The SQLITE_FUNCTION authorizer branch (namespace/namespace.go:466-474) denies only if deniedSandboxFunctions[lower(arg2)] is set, otherwise returns SQLITE_OK (fail-open). deniedSandboxFunctions (namespace.go:32-38) = {load_file, load_file_bytes, clear_plugin_cache, clear_file_cache, load_extension}. The dev UDFs are registered when n.devMode is true (namespace.go:366-379), independently of the sandbox restrictions, and are not in the denylist. LoadDevPlugin does os.ReadFile(pluginManifestPath) (developer.go:67) on the raw attacker argument with no path restriction (unlike read_* modules and load_file).

Impact

GUARANTEED (no staging): an unauthenticated network client obtains a whole-filesystem existence/readability oracle via differential error returns (developer.go:69/75/79: "error reading manifest" vs "error reading json manifest" vs "error validating manifest"), plus partial file-content disclosure — hjson parse errors echo a ~20-char content window of the target file. CONDITIONAL escalation (requires attacker-staged schema-valid manifest on disk): exec.Command(build_command).CombinedOutput() (developer.go:98) → RCE, and os.OpenFile(log_file, O_CREATE|O_WRONLY|O_APPEND) (developer.go:107) → arbitrary file create/append outside AllowedDirs.

Proof of Concept

# victim (developer server exposed to network; sandbox still ON):
anyquery server --dev --host 0.0.0.0 --port 8070
# attacker (no credentials — NewAuthServerNone default):
mysql -h VICTIM -P 8070 main
SELECT load_dev_plugin('p','/etc/shadow');       -- "error validating manifest" => exists + readable
SELECT load_dev_plugin('p','/root/.ssh/id_rsa'); -- "permission denied" => exists, unreadable
SELECT load_dev_plugin('p','/nonexistent');      -- "no such file or directory" => absent
# differential responses = unauthenticated filesystem existence/readability oracle (C:L)

Attack Chain

  1. Entry: MySQL protocol, unauthenticated. Guard: MySQL authentication. Bypass proof: namespace/mysql.go:123-126if s.AuthFile=="" && s.Users==nil { authServer = mysql.NewAuthServerNone() }; default anyquery server sets neither → no auth (same premise as CVE-2026-50006/CVE-2026-47253).
  2. Rewriter: SELECT load_dev_plugin('probe','/path'). Guard: MySQL query rewriter could strip/neutralize the unknown function. Bypass proof: namespace/mysql_rewriter.go:592-624load_dev_plugin is absent from both remapper maps; default is "we don't know the function, we don't rewrite it and we let SQLite handle it." Call reaches SQLite intact.
  3. Authorizer: SQLite invokes the sandbox authorizer for the function. Guard: SQLITE_FUNCTION authorizer + per-function file check. Bypass proof: namespace/namespace.go:466-474 — denies only if deniedSandboxFunctions[lower(arg2)], otherwise return SQLITE_OK. load_dev_plugin is absent from the 5-entry denylist → SQLITE_OK (fail-open). The dev UDF is still registered because registration (namespace.go:366-379) is gated on n.devMode and the authorizer registration (namespace.go:458) on n.restrictions != nil — independent, both true under server --dev.
  4. Sink: LoadDevPlugin executes. Guard: AllowedDirs/CheckFileRead/symlink-resolution confinement (applied by read_* modules and load_file). Bypass proof: namespace/developer.go:67os.ReadFile(pluginManifestPath) on the raw attacker arg with no restrictions reference.
  5. Impact (guaranteed): unauthenticated filesystem oracle + partial file-content disclosure.
  6. Impact (conditional, staged manifest): developer.go:98 exec.Command(...).CombinedOutput() → RCE; developer.go:107 os.OpenFile(..., O_CREATE|O_WRONLY|O_APPEND) → arbitrary file create/append.

Bypass Evidence

  • Denylist = {load_file, load_file_bytes, clear_plugin_cache, clear_file_cache, load_extension}; dev UDFs not among them → fail-open SQLITE_OK (verified on 0.4.6 and HEAD).
  • load_dev_plugin absent from both rewriter remapper maps (grep exit 1) → passes through to SQLite.
  • os.ReadFile in developer.go has no CheckFileRead/AllowedDirs (grep of developer.go shows only os.ReadFile).
  • Empirically verified differential errors; hjson v4.5.0 echoes a ~20-char content window on trailing-character parse errors.
  • NOT the "operator-disabled-control" FP: --dev help text ("Run the program in developer mode") carries no security warning and does NOT disable the sandbox (contrast --no-sandbox, documented UNSAFE); docs recommend anyquery server --dev. The sandbox stays ON and is logged as enabled, so this is a genuine trust-boundary crossing.

Affected Versions

>= 0.4.5, <= 0.4.6 — the sandbox (the control being bypassed) was introduced in 0.4.5 via commit 27f84fc; the dev-UDF denylist omission is present on 0.4.6 and unfixed on HEAD/main.

Suggested Fix

Register the dev UDFs only when n.restrictions == nil, OR add load_dev_plugin/reload_dev_plugin/unload_dev_plugin to deniedSandboxFunctions, OR (most robust) invert the SQLITE_FUNCTION branch to a fail-closed allowlist mirroring the PRAGMA gate.

Dedup

Not covered by any existing advisory — none of the 7 published anyquery advisories (file-URI sandbox bypass, clear_plugin_cache traversal, AppleScript injection, LFR/SSRF/AFW via read modules in server mode, unauth HTTP API v0.4.3) mention --dev, developer mode, or load_dev_plugin. The sandbox fix commit (27f84fc) itself introduced the fail-open gap. This is framed as a distinct denylist oversight, not a "CVE fix bypass".


Reported by zx (Jace) — GitHub: @manus-use

Severity

Low

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
High
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N

CVE ID

No known CVE

Weaknesses

Observable Discrepancy

The product behaves differently or sends different responses under different circumstances in a way that is observable to an unauthorized actor, which exposes security-relevant information about the state of the product, such as whether a particular operation was successful or not. Learn more on MITRE.

Incorrect Authorization

The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. Learn more on MITRE.

Credits