GHSA-CC8F-FCX3-GPJR: SurrealDB adds file allowlist checks for analyzer mapper file reads
Summary
The patch introduces centralized path validation for filesystem-backed mapper access and wires it into analyzer and tree-store mapper call sites. It mitigates arbitrary file disclosure by allowing operators to constrain readable paths via SURREAL_FILE_ALLOWLIST, but the default behavior remains permissive when the allowlist is unset, so the fix is configuration-dependent rather than a hard security boundary by default.
Analysis
Vulnerability
GHSA-CC8F-FCX3-GPJR describes an arbitrary file disclosure issue in SurrealDB where authenticated users with EDITOR or OWNER privileges can register a DEFINE ANALYZER statement whose mapper() filter references attacker-controlled filesystem paths. The vulnerable flow shown in the patch summary passed the supplied path directly into file iteration logic without any authorization or path-scope validation.
The relevant vulnerable call site in the analyzer mapper was:
Self::iterate_file(&mut terms, path).await?;Given the advisory scope and the patched files, the core weakness is unrestricted filesystem access from a database feature that accepts a path parameter. This is a host-boundary violation: database-level privileges were sufficient to trigger reads from arbitrary host files rather than only from explicitly sanctioned data directories. Source references: pull request #5600, GitHub Security Advisory, CVE Reports summary.
Patch
The patch adds a centralized file access control helper in crates/core/src/iam/file.rs and introduces a process-level allowlist sourced from the SURREAL_FILE_ALLOWLIST environment variable in crates/core/src/cnf/mod.rs. The new logic canonicalizes the requested path and, when an allowlist is configured, permits access only if the canonical target is under one of the canonicalized allowed directories.
pub static FILE_ALLOWLIST: LazyLock<Vec<PathBuf>> = LazyLock::new(|| {
std::env::var("SURREAL_FILE_ALLOWLIST")
.map(|input| extract_allowed_paths(&input))
.unwrap_or_default()
});
pub(crate) fn is_path_allowed(path: &Path) -> Result<PathBuf, Error> {
check_is_path_allowed(path, &FILE_ALLOWLIST)
}
fn check_is_path_allowed(path: &Path, allowed_path: &[PathBuf]) -> Result<PathBuf, Error> {
let canonical_path = fs::canonicalize(path)?;
if allowed_path.is_empty() {
return Ok(canonical_path);
}
if allowed_path.iter().any(|allowed| canonical_path.starts_with(allowed)) {
Ok(canonical_path)
} else {
Err(Error::FileAccessDenied(path.to_string_lossy().to_string()))
}
}The analyzer mapper now validates the path before reading:
let path = is_path_allowed(path)?;
Self::iterate_file(&mut terms, &path).await?;A second call site in crates/core/src/idx/trees/store/mapper.rs also now invokes is_path_allowed(p)?, which is a positive sign that the maintainers looked beyond the single reported sink. The patch also adds a dedicated FileAccessDenied error variant and unit tests covering empty allowlist behavior and allow/deny decisions for files inside and outside configured directories. Primary source: https://github.com/surrealdb/surrealdb/pull/5600.
Review
Pros
The patch addresses the immediate root cause at the sink level by inserting a mandatory validation step before filesystem reads in the analyzer mapper. Centralizing the logic in iam/file.rs is maintainable and reduces the chance of inconsistent checks across features. Canonicalization before prefix comparison is the correct baseline defense against simple path traversal and symlink-based bypasses, because policy is evaluated against the resolved path rather than the raw user input.
The addition of FILE_ALLOWLIST gives operators a practical containment mechanism without changing query syntax or storage formats. The same helper being reused in another mapper-related module suggests some effort toward broader coverage. The dedicated error type improves observability and makes policy denials distinguishable from generic I/O failures. The included tests validate the intended semantics of the allowlist implementation.
Cons
The most important limitation is explicit in the code: when allowed_path.is_empty(), the helper returns success and imposes no restriction. That means the product remains permissive by default unless operators set SURREAL_FILE_ALLOWLIST. For a vulnerability framed as arbitrary host file disclosure, a security fix that depends on post-upgrade configuration is not a complete default-safe remediation.
The patch is also scoped to specific call sites visible in the diff summary. While crates/core/src/idx/ft/analyzer/mapper.rs and crates/core/src/idx/trees/store/mapper.rs are covered, the review cannot confirm from the provided sources that every filesystem-backed feature now routes through the same guard. If any other path-consuming code path remains outside is_path_allowed, similar disclosure risk could persist elsewhere.
There are also policy-model concerns. The allowlist is process-global, not role-aware, namespace-aware, or database-aware. Any user who can reach a guarded feature can read any file under the configured allowed directories, which may still be broader than least privilege. Finally, canonicalization requires the target to exist and does not itself address time-of-check/time-of-use races if files or symlinks are swapped after validation, though that is a narrower concern than the original unrestricted read issue.
Verdict
Partial fix.
The patch materially improves security by adding centralized path validation and by constraining reads to operator-approved directories when configured. However, it does not fully eliminate the vulnerability class by default because an empty SURREAL_FILE_ALLOWLIST preserves unrestricted access semantics. In practice, this is a strong mitigation and likely sufficient for hardened deployments, but it is not a complete root-cause eradication unless the deployment also sets a restrictive allowlist. Engineers adopting this patch should treat allowlist configuration as mandatory and audit all remaining filesystem read paths for consistent use of is_path_allowed. References: patch PR, advisory, report summary.
Recommended Labs
Try this vulnerability pattern yourself with hands-on labs.
- Path Traversal.go
This is the closest defensive hands-on match to the SurrealDB issue. The advisory describes arbitrary file disclosure caused by attacker-controlled paths reaching filesystem access. That aligns strongly with path traversal and arbitrary file read weaknesses such as CWE-22 and CWE-73. A Go lab is also relevant because SurrealDB is implemented in a systems language and the core lesson is validating and constraining file paths before use.
- BadZip.go
Although focused on Zip Slip rather than direct analyzer path injection, this lab teaches the same defensive pattern: never trust attacker-influenced file paths, normalize paths, prevent escape from approved directories, and enforce safe extraction or read boundaries. It is useful as a second lab because the SurrealDB bug also stems from insufficient restriction of filesystem path handling.
- Electron LFI.js
This lab is valuable for learning the arbitrary file disclosure side of the issue. The SurrealDB vulnerability lets an authenticated user read host files through a malicious mapper path; local file inclusion and file disclosure labs reinforce how dangerous file read primitives become and how to harden code by restricting file origins, allowlisting safe resources, and separating untrusted input from filesystem APIs.