workspace

package
v0.4.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 16, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

func New

func New(baseDir string) *Manager

func (*Manager) Create

func (m *Manager) Create(sessionID uuid.UUID) (string, error)

Create initializes an isolated workspace directory for a session.

Why 0o777 is required:

The directory is bind-mounted into the sandbox container, which runs as
the unprivileged user "nobody" (UID 65534). From the Linux kernel's
perspective, UID 65534 is "other" relative to the host process that owns
the workspace, so the container user can only access the directory if the
"other" permission bits are set. Without 0o777 (specifically the world-wx
bits) the container process cannot create output files or traverse into
subdirectories, causing sandbox runs to fail.

Why not use a tighter permission model:

The ideal alternative would be a per-session UID combined with a user
namespace so the container runs as a private UID that owns the directory
(allowing 0o700). That approach is out of scope for v1 and requires
additional kernel capabilities or a suid helper binary.

Limiting blast radius on the host:

0o777 on the session-specific directory means other host processes running
as arbitrary UIDs can also write to it. To prevent those host processes
from even discovering which session directories exist, the parent baseDir
is created with 0o700 (owner-only) in main.go. Only the owner of the host
process can list baseDir entries; the 0o777 child directories are therefore
not reachable by other host users without already knowing the session UUID.

umask behaviour:

os.MkdirAll honours the process umask, which can silently strip the
group/other bits we set (e.g. umask 0o077 → effective 0o700). To
guarantee the intended 0o777 we call os.Chmod after creation; Chmod is
not affected by the umask.

func (*Manager) CreateSnapshot

func (m *Manager) CreateSnapshot(sessionID, snapshotID uuid.UUID) (archivePath string, sizeBytes int64, err error)

CreateSnapshot creates a gzip-compressed tar archive of the session workspace. The archive is written to {baseDir}/snapshots/<snapshotID>.tar.gz and the archive path + uncompressed size are returned.

func (*Manager) Delete

func (m *Manager) Delete(sessionID uuid.UUID) error

Delete removes the workspace directory.

func (*Manager) DeleteSnapshot

func (m *Manager) DeleteSnapshot(archivePath string) error

DeleteSnapshot removes a snapshot archive from disk.

func (*Manager) Exists added in v0.3.2

func (m *Manager) Exists(sessionID uuid.UUID, userPath string) (bool, error)

Exists reports whether userPath refers to a real file/dir inside the session workspace. Symlinks that escape the workspace root are treated as absent (not an oracle for host paths).

func (*Manager) OpenFile

func (m *Manager) OpenFile(sessionID uuid.UUID, userPath string) (*os.File, error)

OpenFile opens a file inside the workspace for reading. Symlink targets are validated to remain inside the workspace.

func (*Manager) RestoreSnapshot

func (m *Manager) RestoreSnapshot(sessionID uuid.UUID, archivePath string) error

RestoreSnapshot extracts a snapshot archive into the session workspace. Files in the archive overwrite existing files; the workspace is not cleared first so existing files not in the snapshot are preserved.

func (*Manager) SafePath

func (m *Manager) SafePath(sessionID uuid.UUID, userPath string) (string, error)

SafePath resolves a user-provided path within the workspace, enforcing:

  1. URL-decoded (loop until stable) to catch double/triple-encoded traversals
  2. No ".." path components (checked after full decoding, before Clean)
  3. Resolved path stays inside the workspace root (filepath.Clean + HasPrefix)

func (*Manager) SessionDirs added in v0.4.1

func (m *Manager) SessionDirs() ([]uuid.UUID, error)

SessionDirs returns the session IDs that have a directory under the base directory. Entries whose name is not a UUID (snapshots/, warm pool) are skipped.

func (*Manager) SessionPath

func (m *Manager) SessionPath(sessionID uuid.UUID) string

func (*Manager) WriteFile

func (m *Manager) WriteFile(sessionID uuid.UUID, userPath string, r io.Reader, maxBytes int64) (int64, error)

WriteFile writes data to a safe path inside the workspace, creating intermediate directories as needed.

Security: guards against symlink-escape attacks where a container process creates a symlink inside /workspace pointing to a host path outside the workspace root. Two layers of protection are applied:

  1. The entire parent-directory chain is symlink-resolved and verified to stay inside the workspace root before any write takes place.
  2. The file is opened with O_NOFOLLOW so that if the final path component is itself a symlink (e.g. created in a TOCTOU window) the open fails.

Permissions: directories are created 0o777 and files 0o644 so that the container process (running as nobody/UID 65534) can read the files and write new output files. os.Chmod is called after creation to bypass the process umask, which would otherwise silently strip the group/other bits.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL