Skip to content

feat: honor OCI WorkingDir for Docker and Podman workspaces #2526

Description

@matthewgrossman

Problem Statement

Follow-up to #2331 and stacked on #2509.

#2331 removes the requirement for Docker and Podman images to contain a
sandbox:sandbox account, but intentionally keeps /sandbox as OpenShell's
fixed workspace. OCI images can already declare their intended workspace with
Config.WorkingDir; ignoring it means off-the-shelf images may still require
OpenShell-specific /sandbox preparation, and direct sessions, SSH sessions,
filesystem policy, persistence, transfers, and editor launch can disagree about
the effective workspace.

Docker and Podman should honor the image-declared working directory without
changing Kubernetes/OpenShift or VM workspace behavior.

Proposed Design

Introduce one internal resolved workspace root and use it consistently across
the local container drivers, supervisor, policy runtime, and SSH-based CLI
operations.

Resolution

  1. Docker and Podman inspect OCI Config.WorkingDir alongside Config.User
    from the same immutable image inspected by feat: preserve image-declared identity in Docker and Podman #2331.
  2. Use a normalized absolute WorkingDir when it is non-empty and not /.
  3. For an empty, /, or explicit /sandbox declaration, use /sandbox as an OpenShell-created
    fallback. The image does not need to contain or pre-own it.
  4. Reject malformed non-empty declarations, including relative paths. Reject a
    custom workspace root that is missing, resolves to a symlink or
    non-directory, or cannot be traversed and written by the final workload
    identity. Do not create it or change its ownership or permissions.
  5. Kubernetes/OpenShift and VM continue resolving their workspace root to
    /sandbox.

This is an internal runtime decision, not a new public setting or gateway
configuration field.

Runtime behavior

  • Pass the resolved root through the supervisor's existing workdir mechanism;
    do not add a new supervisor flag or identity protocol.
  • Prepare only the managed /sandbox fallback for the final effective UID/GID.
    Validate custom directories as that identity before launching agent code.
  • Start trusted runtime binaries from / so Podman does not create or enter a
    custom workspace before validation; agent processes use the resolved root as
    their cwd.
  • Keep the cross-runtime HOME contract in feat: define cross-runtime agent HOME behavior #3873; this change does not add a
    new HOME requirement.
  • Feed the resolved root to Landlock when filesystem.include_workdir is true.
  • Stop automatically injecting literal /sandbox as a baseline filesystem
    path. User-authored policy entries that explicitly name /sandbox remain
    literal and are never rewritten.

Driver behavior

  • Pin both identity and workspace resolution to the immutable image ID already
    inspected by Docker and Podman.
  • Create and initialize Podman's managed workspace volume only for the
    /sandbox fallback. Custom workspaces remain in the workload container's
    writable layer, with no workspace volume or ownership-setting archive.
  • Reject image-declared volumes or driver-config mounts that cover a custom
    workspace, and reject runtime/control-path overlaps after image inspection.
    Continue allowing mounts nested below the workspace.
  • Preserve existing image files, ownership, and permissions without named-volume
    copy-up or directory repair.
  • Workspace changes survive normal stop/start of the same workload container.
    Removing or replacing that container removes its workspace changes; a new
    sandbox starts from the image's original files. Export files that must be
    retained before deleting the sandbox.

CLI behavior

Use the existing SSH connection to discover the canonical remote workspace with
pwd -P; avoid adding a public workspace_root status field unless a
non-SSH consumer proves it necessary.

  • Download containment and symlink checks use the discovered root rather than
    a hardcoded /sandbox.
  • Upload without an explicit destination extracts into the discovered root
    rather than relying on SSH ~.
  • Editor launch opens the discovered remote root.

Alternatives Considered

  • Recursively chown /sandbox: rejected in feat(sandbox): use policy-first OCI image identity #2509. It mutates image content,
    risks crossing ownership boundaries, and treats the symptom rather than
    honoring OCI workspace metadata.
  • Always use /sandbox: retained as the empty// fallback and for explicit
    /sandbox declarations. It
    preserves compatibility but should not override a usable OCI WorkingDir.
  • Use the OCI user's account home as fallback: rejected because Podman must
    choose its managed-volume target before the supervisor can resolve
    /etc/passwd. A driver-known fallback is simpler and deterministic.
  • Mount a named volume at a custom WorkingDir: not selected. Keep the
    image directory in the container's writable layer without copy-up or
    ownership changes, with persistence tied to that container's lifetime.
  • Add a public gateway workspace_root field: avoid initially because the
    affected CLI operations already establish SSH sessions and can discover the
    canonical cwd directly.
  • Rewrite user-authored /sandbox policy paths: rejected. Explicit policy
    paths must retain their literal meaning.

Definition of Done

  • Docker and Podman use a valid OCI WorkingDir, with driver-created
    /sandbox fallback for empty, /, or explicit /sandbox declarations.
  • Direct and SSH children share the resolved cwd and can write there. The
    cross-runtime HOME contract remains a separate follow-up (feat: define cross-runtime agent HOME behavior #3873).
  • include_workdir grants the resolved root without rewriting explicit
    policy paths.
  • Custom workspaces preserve image contents and permissions without a
    workspace-covering volume. Changes persist across stop/start of the same
    container, with the container-lifetime limit documented.
  • Image-declared volumes and driver-config mounts cannot cover the resolved
    custom workspace root; nested mounts remain valid.
  • Upload defaults, download containment, and editor launch use the
    SSH-discovered workspace root.
  • Kubernetes/OpenShift and VM retain their current /sandbox behavior.
  • No public workspace setting, gateway schema field, or new supervisor
    launch flag is introduced.

Test Plan

  • OCI WorkingDir: absolute named path, absent, /, relative, malformed,
    missing, pre-populated, symlink, non-directory, and read-only.
  • Driver-owned fallback works without image-prepared /sandbox.
  • Direct and SSH identity, cwd, and write behavior agree. HOME is tracked in
    feat: define cross-runtime agent HOME behavior #3873 rather than required by this change.
  • Landlock include_workdir follows the resolved root; explicit /sandbox
    remains literal.
  • Custom workspaces retain image files and ownership without a workspace
    mount. Changes survive stop/start of the same container and do not transfer
    to a replacement container.
  • Nested/user mounts retain their ownership and existing behavior.
  • Upload, download traversal/symlink protection, and editor launch use the
    discovered root.
  • Kubernetes/OpenShift and VM regression coverage remains unchanged.

Agent Investigation

  • Searched open and closed issues; found no duplicate OCI WorkingDir proposal.
  • Docker's DockerImageMetadata and Podman's ImageInspect already bind OCI
    Config.User to immutable image launch, so Config.WorkingDir can be read in
    the same inspection.
  • The supervisor already accepts and threads a workdir through direct process
    launch, SSH process launch, and Landlock preparation.
  • Podman's managed workspace volume is currently hardcoded to /sandbox.
  • Mount validation currently reserves literal /sandbox.
  • CLI download guards and editor launch currently hardcode /sandbox; upload
    without a destination currently relies on the SSH home directory.

Scope Boundaries

Do not redesign OCI identity from #2331, add a public workspace setting, change
Kubernetes PVC/fsGroup behavior, change VM guest initialization, or fold this
work back into #2509. Keep cross-runtime HOME behavior in #3873 and
image-declared volume resource admission in #3890. A distinct
WorkspaceValidationFailed condition reason is not required; an unusable
workspace must still be rejected.

Checklist

  • I've reviewed existing issues and the architecture docs
  • This is a design proposal, not a "please build this" request

Current Implementation

The remaining Podman WORKDIR implementation is in #3982, with shared
Docker/Podman OCI behavior tests in #3931. The managed /sandbox ownership fix
landed in #3981. These supersede the earlier approach in #3801.

Activity

  1. matthewgrossman commented on Jul 28, 2026

    @matthewgrossman
    MemberAuthor

    🏗️ build-plan

    Implementation Plan

    Issue type: feat
    Complexity: Medium
    Confidence: High — RFC 0012 provides the required separation, and the Docker/shared workspace plumbing is already present.

    Summary

    Implement the remaining Podman half of OCI WORKDIR support against the RFC 0012 isolation architecture. Resolve Config.WorkingDir from the same pinned inspection as image identity and environment. Keep a custom workspace in the workload container's writable layer, without a workspace volume or copy-up. Pass its root through the existing supervisor --workdir path. Empty, /, and explicit /sandbox declarations retain the managed /sandbox fallback.

    Scope

    • crates/openshell-driver-podman/src/client.rs: deserialize and test OCI WorkingDir alongside User and Env.
    • crates/openshell-driver-podman/src/container.rs: group the requested reference, immutable ID, user, environment, and resolved workspace in image metadata; validate workspace/mount/control-path collisions; distinguish managed /sandbox from custom image directories; start trusted runtime binaries from / and pass the workspace to agent processes.
    • crates/openshell-driver-podman/src/driver.rs: resolve metadata before resource creation; create and initialize the owned workspace volume only for managed /sandbox.
    • Focused Podman unit tests and e2e/rust/tests/podman_oci_identity.rs: cover immutable pinning, custom cwd/write access, image-file and ownership preservation, fallback, and mount collisions. Shared Docker/Podman OCI behavior coverage belongs to fix(oci): report workspace rejections and share OCI USER and WORKDIR checks across Docker and Podman #3931.
    • Podman README, published runtime docs, and relevant operator skills: document workspace selection and the container-lifetime persistence contract.

    Implementation Steps

    1. Inspect the immutable workload image once and bind its requested reference, image ID, user, environment, and working directory in resolved metadata. Empty, /, and explicit /sandbox select managed /sandbox.
    2. Reject malformed workspace declarations, runtime/control-path overlaps, and image-declared volumes or driver mounts that cover the workspace. Continue allowing mounts nested below it.
    3. Create and initialize the workspace volume only for the managed fallback. Keep custom workspaces in the workload container's writable layer; do not create, chown, chmod, or copy their contents into a volume.
    4. Start trusted runtime binaries from / so Podman does not create or enter a custom workspace before validation. Pass the resolved path through the existing supervisor --workdir mechanism for agent processes.
    5. Validate the custom directory's effective traversal and write access as the final non-root workload identity before launching agent code. Reject an unusable workspace without requiring a particular condition reason.
    6. Document writable-layer persistence and validate shared OCI behavior on Docker and both Podman lanes in fix(oci): report workspace rejections and share OCI USER and WORKDIR checks across Docker and Podman #3931.

    Test Plan

    • Unit: image-inspect parsing; fallback and invalid declarations; pinned launch metadata; supervisor argv; trusted container cwd; mount collision directionality; managed-volume creation versus custom-directory launch; conditional workspace archive upload.
    • Integration: Podman create-spec serialization remains pinned to the inspected image ID while carrying the resolved workspace and original reference as metadata.
    • E2E: a pre-populated owner-only custom WORKDIR; verify cwd/write access, existing image files, root-owned file preservation, directory identity/mode, immutable image launch, and absence of a managed workspace mount. Keep managed /sandbox coverage without a baked directory.
    • Shared coverage (fix(oci): report workspace rejections and share OCI USER and WORKDIR checks across Docker and Podman #3931): Docker/Podman workspace behavior, rejection of an unwritable directory without requiring WorkspaceValidationFailed, persistence across stop/start of the same container, and no transfer of changes to a replacement container. Keep fallback and nested-mount checks.

    Persistence Contract and Risks

    • Custom workspace contents survive normal stop/start of the same workload container. Removing or replacing that container removes workspace changes; a new sandbox starts with the image's original files. Export files that must outlive the sandbox before deletion.
    • Custom directories retain the image's ownership and permissions. The final identity must be able to traverse and write them; OpenShell does not repair them. No named-volume copy-up is involved for custom roots.
    • Workspace validation runs in the capability-free sandbox runtime before untrusted execution. No pre-RFC probe container, privileged repair step, identity attestation, or new protobuf plumbing is introduced.
    • Driver mount validation uses structured paths after image inspection, not shell construction.
    • Cross-runtime HOME behavior stays in feat: define cross-runtime agent HOME behavior #3873; image-declared volume resource admission stays in bug(podman): resource admission rejects image-declared VOLUMEs; lacks Docker parity #3890. Cross-driver failure-classification improvements are not a prerequisite for WORKDIR support.

    Documentation Impact

    Update the Podman crate README, published runtime guide, and operator/debug guidance. Explain that custom workspaces use the container's writable layer and that the /sandbox fallback remains managed. No gateway TOML, driver option, Helm rendering, or default changes are introduced.

    Current PRs

    These supersede the earlier implementation in #3801.


    Revision 3 — custom workspaces use the workload container's writable layer; replaces the named-volume/copy-up design and documents container-lifetime persistence.
    Revision 2 — redesigned for RFC 0012 supervisor/workload separation.
    Revision 1 — initial stacked implementation plan.

  2. matthewgrossman commented on Jul 28, 2026

    @matthewgrossman
    MemberAuthor

    Implementation is available in stacked PR #2530 (based on #2509).

    Implemented OCI WorkingDir resolution for Docker/Podman, consistent supervisor cwd/HOME and workspace preparation, dynamic include_workdir policy handling, Podman workspace-volume placement, SSH-discovered CLI transfer/editor roots, focused tests, and documentation updates.

    Validation completed:

    • mise run pre-commit
    • mise run test
    • focused Docker custom_image E2E
    • focused Docker driver_config_volume E2E

    Live Podman E2E was unavailable locally; Podman inspect/spec/copy-up behavior is covered by focused unit tests.

  3. added 5 commits that reference this issue on Jul 28, 2026
    6838984
    493abe0
    6e40ae1
    af4fd6d
    ec4815f
  4. github-actions commented on Aug 12, 2026

    @github-actions

    This issue has had no activity for 14 days and is now marked stale. It may be closed in 7 days if there is no further activity. Comment or remove the state:stale label to keep it open.

  5. matthewgrossman commented on Sep 28, 2026

    @matthewgrossman
    MemberAuthor

    🏗️ build-from-issue-agent

    Implementation Ready — E2E Blocked

    The RFC 0012-aligned Podman WORKDIR implementation is complete on branch codex/2526-podman-workdir-rfc12 at commit e073caa915d8c3bd1e0015f523c5cb4717c845d4.

    Verification

    • mise run pre-commit: passed
    • Podman driver unit tests: 234 passed
    • Focused Podman E2E targets: compile and Clippy passed
    • mise run ci: Podman tests passed; the workspace task stopped on five unrelated VM-driver tests because this host does not have mke2fs or mkfs.ext4
    • mise run e2e:podman:ci: blocked before tests because the local podman-machine-default VM exits immediately after startup and its API endpoint refuses connections

    The required live Podman E2E gate is not green, so no pull request was opened. The branch is pushed for recovery or verification on a healthy Podman host.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:sandboxSandbox runtime and isolation workstate:agent-readyApproved for agent implementationstate:pr-openedPR has been opened for this issue

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions