Skip to content

sendFile(directory="/abs/path") cannot serve files outside the web root on Adobe CF, and hijacks any path containing "/wheels" on all engines #3077

Description

@bpamiri

Summary

sendFile() documents (docblock + file-uploads guide line 198): "Directory outside of the web root where the file exists. Must be a full path." The path-resolution block in vendor/wheels/controller/miscellaneous.cfc (~lines 254-290) breaks that contract two ways, same root cause: it post-processes an already-absolute directory argument instead of using it verbatim.

Repro (guide-behavioral-audit harness, develop post-4.0.3)

Defect 1 — Adobe CF: absolute directory gets webroot-prefixed. When fullPath doesn't contain the web root, the #873 branch calls ExpandPath(local.fullPath). Lucee's ExpandPath() returns existing absolute paths unchanged, so it happens to work there; Adobe's always resolves against the web root.

// container: /tmp/dlprobe/secret.txt exists; FileExists() = true
sendFile(file="secret.txt", directory="/tmp/dlprobe");
  • Lucee 7.0.1+100: 200, file served, Content-Disposition: attachment.
  • Adobe 2023.0.11: Wheels.FileNotFound — looked in /wheels-test-suite/public/tmp/dlprobe (probe confirmed ExpandPath("/tmp/dlprobe") webroot-prefixes on Adobe). The documented serve-from-outside-the-webroot feature does not work at all on Adobe CF.

Defect 2 — all engines: /wheels substring hijack. Before the ExpandPath fallback, the resolver does findNoCase("/wheels", local.fullPath) to support the /wheels mapping — and substring-matches ANY directory containing /wheels:

sendFile(file="secret.txt", directory="/tmp/wheels-dl"); // Lucee 7
// → Wheels.FileNotFound: looked in /wheels-test-suite/public/wheels-dl

Real-world installs under /var/www/wheels/uploads or /srv/wheelsapp/files get their paths silently rewritten.

Root cause

vendor/wheels/controller/miscellaneous.cfc ~254-290: the #873 "DOES NOT CONTAIN root → ExpandPath" branch plus the findNoCase("/wheels", fullPath) substring match, both applied to caller-supplied absolute paths.

Proposed direction

When arguments.directory is supplied and is an absolute path (e.g. DirectoryExists() or starts with / / drive letter), build fullPath = directory & "/" & file and skip both the /wheels mapping rewrite and the ExpandPath() fallback. Keep existing behavior for relative filePath-based resolution. The existing ..-traversal guard runs before this block and still applies.

Acceptance

  • sendFile(file=..., directory="/abs/outside/webroot") serves the file on Lucee AND Adobe.
  • A directory containing /wheels as a substring is not rewritten.
  • Specs cover both shapes; docs caution in file-uploads-and-downloads.mdx line 198 removed once fixed.

Reported by the guide-behavioral-audit P1 batch 2 (work item p1-8-uploads, claim C14; live on wheels-test-lucee7:v1.0.0 + wheels-test-adobe2023:v1.0.1).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions