Skip to content
Raster
Esc
navigateopen⌘Jpreview
On this page

Files

List, read, write, upload and download — with an encoding you name rather than one that is guessed.

File access needs no input lease.

List

const listing = await machine.files.list("/home/agent");
for (const entry of listing.entries) console.log(entry.name, entry.type, entry.size);
listing = machine.files.list("/home/agent")
for entry in listing["entries"]:
    print(entry["name"], entry["type"], entry["size"])

An entry’s type is file, dir, symlink or other.

Read

const text = await machine.files.read("/etc/hostname");
text.content;

const chunk = await machine.files.read("/var/log/app.log", { offset: 0, length: 4096 });
chunk.eof;

const raw = await machine.files.read("/tmp/logo.png", { encoding: "base64" });
text = machine.files.read("/etc/hostname")
text["content"]

chunk = machine.files.read("/var/log/app.log", offset=0, length=4096)
chunk["eof"]

raw = machine.files.read("/tmp/logo.png", encoding="base64")

Encoding is named, never guessed

A utf8 read of bytes that are not UTF-8 is an invalid_request telling you to ask for base64. It is never silently decoded, because a decode-and-re-encode is exactly where a PNG turns into a field of replacement characters — and the corruption surfaces later, somewhere else, as a mystery.

Write

await machine.files.write("/home/agent/notes.md", "# notes\n");
await machine.files.write("/home/agent/notes.md", "more\n", { append: true });
await machine.files.write("/home/agent/run.sh", script, { mode: "0755" });
machine.files.write("/home/agent/notes.md", "# notes\n")
machine.files.write("/home/agent/notes.md", "more\n", append=True)
machine.files.write("/home/agent/run.sh", script, mode="0755")
PropType
encoding?"utf8" | "base64"
Type"utf8" | "base64"
Default"utf8"
append?boolean

Append rather than replace.

Typeboolean
Defaultfalse
createDirs?boolean

TypeScript `createDirs` / Python `create_dirs`. Create parent directories as needed.

Typeboolean
Defaulttrue
mode?string

File mode, e.g. "0644".

Typestring

Upload and download

await machine.files.upload("/home/agent/logo.png", bytes); // Uint8Array in
const png = await machine.files.download("/home/agent/logo.png"); // Uint8Array out

const big = await machine.files.download("/var/log/huge.log", { maxBytes: 256 * 1024 * 1024 });
machine.files.upload("/home/agent/logo.png", png_bytes)           # bytes in
png = machine.files.download("/home/agent/logo.png")              # bytes out

big = machine.files.download("/var/log/huge.log", max_bytes=256 * 1024 * 1024)

upload is write with base64, which is an exact round trip rather than a guess.

download does the chunking for you. The guest bounds an inline read, so a large file arrives as several 4 MB ranges and is reassembled client-side. It ends on the guest’s own eof rather than a size comparison, so a file that grows under the read does not truncate, and it stops at 64 MB unless you raise the limit.

What is refused

The machine is your own computer and the microVM is the boundary, so the default readable area is everything. What is denied is the small set of paths that are not yours: the substrate’s guest credentials, the machine agent’s own, and the host’s control channel into the guest.

await machine.files.read("/etc/machine-agent/credentials.json");
// RasterError: permission_denied
machine.files.read("/etc/machine-agent/credentials.json")
# RasterError: permission_denied

The denylist is fixed and not configurable, and the check runs against every form the path takes — as written, each symlink hop, and the fully resolved result. See files for why each of those is necessary.

It does not sandbox the machine from itself: a process inside the machine still reads whatever it can. The denylist keeps the control plane’s own credentials out of the API surface.

Was this page helpful?