Skip to content

Feature: write-only / append-only filesystem grants (fs_write_only / fs_append) #150

Description

@dzerik

Summary

Add write-only (and ideally append-only) filesystem grants to the public API, e.g. fs_write_only(path) / fs_append(path), in addition to the existing fs_read/fs_write`.

Today fs_write grants read and write: a path passed to fs_write is also readable (the write mask is built as READ_ACCESS | write/create/delete flags).
There is no way to let a sandboxed process write to a location it cannot read back, nor to enforce append-only (no truncate/overwrite of existing content).

Motivation

A supervisor/host frequently needs to hand a sandboxed workload a location for significant or audit-relevant output — logs, audit records, telemetry, a drop-box — that the workload must be able to append to but must not be
able to:

  • read back (confidentiality / no exfiltration of other tenants' or prior records that share the sink), or
  • overwrite / truncate existing records (tamper-evidence — the workload cannot rewrite history).

With the current read+write grant, sandboxed code that knows the path can open the sink for reading or O_TRUNC and destroy/alter earlier records. For a product exposed to untrusted external code, a tamper-evident audit trail is a
common requirement, and today it can only be approximated by writing the sink through a separate privileged channel rather than the filesystem.

Why this is a good fit for Landlock

Landlock already models these as distinct rights, and sandlock already computes per-grant masks (crates/sandlock-core/src/landlock.rs):

  • write-only: LANDLOCK_ACCESS_FS_MAKE_REG | LANDLOCK_ACCESS_FS_WRITE_FILE (+ directory traversal to reach the path), without READ_FILE / READ_DIR. open(path, O_RDONLY) from the sandbox then fails with EACCES.
  • append-only: additionally withhold LANDLOCK_ACCESS_FS_TRUNCATE so the sandbox cannot O_TRUNC/ftruncate existing content (combined with the host opening/handing the sink such that writes are appends).

So the enforcement is kernel-backed and needs no new supervisor logic — only a new grant constructor that emits the narrower mask instead of the current read+write one.

Proposed API (sketch)

// builder
.fs_write_only(path)   // MAKE_REG + WRITE_FILE (+ traversal), no READ_FILE/READ_DIR
.fs_append(path)       // as write_only, also no TRUNCATE

with the same surfaced through the Python/Go bindings (a write_only / append list alongside fs_readable / fs_writable).

Notes

  • Independent of Sandbox.run() deadlocks when the child writes more than the pipe buffer to stdout/stderr #149 (the run() stdout-capture deadlock); this is a separate confinement-granularity feature.
  • A sandboxed process that opened the file itself still holds whatever fd it opened, so write-only is meaningful primarily for sinks the process must open fresh (it cannot obtain a read fd) or that the supervisor pre-creates — the point is that the path grants no read/truncate right, so no read/rewind/ truncate is possible via the filesystem.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions