ESC
Type to search...
S
Soli Docs

File Class

Static methods for file operations: reading, writing, glob patterns, and file metadata.

Filesystem jail and symlink defense

When the application is started with soli serve <dir>, every path passed to slurp / barf / File.* / mkdir_p / file_write_* is resolved relative to <dir> and rejected if it escapes that root (via absolute paths, .. segments, or symlinks pointing outside). A controller calling File.read(req["params"]["path"]) therefore can no longer reach /etc/passwd.

On Unix, every File.* open additionally passes O_NOFOLLOW, and metadata lookups go through symlink_metadata. A path that is a symlink therefore fails to open through File.* even when the target is itself in-jail — this defends against local TOCTOU swaps that would otherwise race a canonicalised path against an attacker-supplied symlink.

CLI invocations (soli run, the REPL, the test runner) do not install a jail, so command-line scripts keep full filesystem access. Code that genuinely needs to step outside the jail or follow symlinks deliberately (log shippers, backup scripts, cron-style maintenance jobs, tailing a symlinked log file) should use the parallel Trusted class — Trusted.read("/var/log/..."), Trusted.write(...), etc. Its API mirrors File exactly but skips both the jail check and the O_NOFOLLOW flag, making the unsafe access explicit at the call site so reviewers and grep can find it.

File.read(path, encoding?)

Read file contents as a string. Pass a charset label ("latin1", "iso-8859-1", …) to decode a non-UTF-8 file. See Encoding.

content = File.read("config.json")
legacy  = File.read("clients.csv", "latin1")  # Latin-1 -> UTF-8
File.write(path, content)

Write content to file.

File.write("output.txt", "Hello!")
File.exists(path)

Check if file exists.

if File.exists("config.json") {
  # do something
}
File.delete(path)

Delete a file.

File.delete("old.txt")
File.glob(pattern)

Match files using glob pattern (non-recursive).

files = File.glob("docs/*.md")
File.glob_recursive(pattern)

Match files using glob pattern (recursive).

files = File.glob_recursive("docs/**/*.md")
File.lines(path)

Read file as array of lines.

lines = File.lines("data.txt")
File.is_dir(path)

Check if path is a directory.

File.is_file(path)

Check if path is a file.

File.size(path)

Get file size in bytes.

File.modified(path)

Get file modification timestamp (Unix epoch in seconds).

time = File.modified("config.json")
File.copy(src, dest)

Copy file from source to destination.

File.rename(old, new)

Rename/move a file.

File.append(path, content)

Append content to file.

Trusted.* — unjailed file access

Trusted mirrors the entire File API (Trusted.read, Trusted.write, Trusted.append, Trusted.exists, Trusted.is_dir, Trusted.glob, …) but skips the filesystem jail and the O_NOFOLLOW symlink guard. Reach for it only from server-side code that legitimately must step outside <dir> or follow a symlink — log shippers, backup scripts, cron-style maintenance jobs — and never with a path derived from request input. Calls to Trusted.* from app/controllers/, app/middleware/, or app/views/ are flagged by the smell/dangerous-server-builtin lint.

log = Trusted.read("/var/log/app/today.log")    # absolute path, outside the jail
Trusted.append("/var/log/app/audit.log", line)  # follows a symlinked logfile