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.

Call What it does
GET /machines/{id}/files list a directory
GET /machines/{id}/files/content read a file, or a byte range
PUT /machines/{id}/files/content write or append

File access needs no input lease.

Encoding is named, never guessed

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

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 shows up later, somewhere else, as a mystery.

Reading

offset and length read a range. The response carries eof, which is what you loop on:

const png = await machine.files.download("/home/agent/logo.png");

download does that loop 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 maxBytes.

Writing

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.upload("/home/agent/logo.png", pngBytes); // base64 on the wire
Option Default Meaning
encoding utf8 utf8 or base64
append false append rather than replace
createDirs true create parent directories as needed
mode file mode, e.g. "0644"

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

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

The denylist is fixed and not configurable. The check runs against every form the path takes — the path as written, each symlink hop, and the fully resolved result — because a dangling link is still a link (open(2) follows one whether or not it resolves today), and resolution can rewrite a prefix out of the denylist where a parent directory is itself a link.

This is a property of the guest agent, so it holds however you reach the file: SDK, CLI, MCP tool or a vendor adapter. What it does not do is stop a process inside the machine from reading whatever that process can read — the denylist keeps the control plane’s own credentials out of the API surface, it does not sandbox the machine from itself.

Nothing is logged

File contents, paths, clipboard text, typed text and command lines all pass through the guest agent, and its request logger records route, status, size and duration — never a body, a header or a query parameter. There is an integration test asserting it.

Was this page helpful?