Problem Statement
Docker and Podman start the OpenShell supervisor as root and replace the image
entrypoint. Today, agent identity then falls back to the policy's named
sandbox:sandbox account, which requires every image to bake that exact user
and group.
The first implementation attempt in #2335 replaced that requirement with a
gateway-wide numeric identity, defaulting every local image to 10001:10001.
That is also the wrong local-driver abstraction: it discards the image's normal
OCI USER, can break image file ownership, and unnecessarily copies the
platform-assigned identity model used by Kubernetes/OpenShift.
Docker and Podman should preserve a sandbox image's declared non-root OCI
USER for agent children while continuing to run only the privileged
supervisor as 0:0.
This remains the Docker/Podman portion of #1959. PR #2335 is closed and
superseded by this design.
Design Decisions
- Reject newly created images with no OCI
USER; OCI defaults those images to
root.
- Reject
USER root, USER 0, or any name resolving to UID or GID 0.
- Use image identity by default for Docker and Podman.
- Keep an explicit operator-controlled fixed identity mode for shared-storage
deployments.
- Block creator-selected bind and named-volume mounts in image mode.
- Do not change Kubernetes/OpenShift or VM identity behavior.
User Contract
| Image declaration |
Agent behavior |
USER app |
Resolve app from the image passwd file and use its UID and primary GID. |
USER app:staff |
Resolve both names from the image account files. |
USER 1234:1235 |
Use the numeric pair without requiring passwd or group entries. |
USER 1234 with a passwd entry |
Use UID 1234 and that entry's primary GID. |
USER 1234 without a passwd entry |
Reject and require an explicit group. |
No USER |
Reject and require a non-root declaration. |
| An identity resolving to UID/GID 0 |
Reject. |
A passwd-less image therefore needs only:
It does not need useradd, groupadd, or a user named sandbox.
Identity Modes
Local drivers expose an operator-selected identity source:
image is the default. The agent uses OCI Config.User and may use only the
driver-owned per-sandbox workspace plus immutable image mounts.
fixed requires an explicit validated UID and GID. It supports deployments
that intentionally attach external or shared storage under an
operator-controlled principal.
The unshipped implicit 10001:10001 default and standalone Podman UID/GID flags
from #2335 must not return.
Image Inspection
Resolve or pull the final image before container creation, then inspect it and
carry both its immutable image ID and OCI Config.User through provisioning.
Create the container from that immutable ID so a mutable tag cannot change
between identity inspection and rootfs selection.
- Docker already calls Bollard
inspect_image() and currently discards
ImageInspect; read inspect.config.user and retain the image ID.
- Podman needs a typed wrapper for
GET /v5.0.0/libpod/images/{name}/json, reading the image ID and
Config.User.
- Podman create must not perform another pull decision after an immutable local
image ID has been selected.
- Docker keeps container
user = "0"; Podman keeps user = "0:0". Those
values apply only to the supervisor.
Pass the raw OCI user declaration, immutable image ID, and identity source to
the supervisor through protected driver-owned metadata inserted after image and
request environment.
Supervisor Resolution
This extends an existing mechanism. The supervisor already runs inside the
sandbox image rootfs and resolves the legacy sandbox:sandbox identity before
filesystem preparation and the verified setgid/setuid sequence. Direct and
SSH children already share that privilege-drop boundary.
Do not reuse the current libc NSS path unchanged for arbitrary image users.
Resolve names with a bounded file-only parser:
- Open
/etc/passwd and /etc/group without following symlinks.
- Require regular files and cap total, line, and field sizes.
- Reject malformed or duplicate matching names and IDs.
- Do not call
getpwnam, getgrnam, NSS modules, or initgroups from the
privileged supervisor.
- Resolve every accepted form to one complete numeric UID/GID.
- Use the passwd primary GID when the OCI declaration omits a group.
- Require an explicit group for an accountless numeric UID.
- Reject resolved UID/GID 0.
The resulting internal record contains at least:
ResolvedAgentIdentity {
uid,
gid,
presentation_user,
source,
image_id,
}
Agent children receive no supplementary groups. Clear and verify the group list
before setgid/setuid for both named and numeric image declarations. Preserve
HOME=/sandbox; use the declared name for USER/LOGNAME when available and
the numeric UID otherwise.
OpenShell must not create or rewrite /etc/passwd or /etc/group.
Storage Boundary
The image is untrusted and may select any valid non-root UID/GID. That identity
may authorize access to its rootfs and isolated workspace, but it must not
select the principal used for unrelated storage.
In image mode, reject all creator-selected bind mounts and named-volume mounts.
The driver-owned per-sandbox /sandbox volume and immutable image mounts remain
allowed. A global bind-mount enable switch is not sufficient authorization for
image-selected IDs.
Operators that require external or shared storage must select fixed mode until
OpenShell has operator-authored mount grants binding storage sources to allowed
identities.
The supervisor may chown /sandbox itself and missing paths OpenShell creates.
It must not recursively chown existing descendants or nested mounts. Privileged
directory creation must use no-symlink ancestor traversal rather than checking
only the final path before create_dir_all.
Policy And Observability
Creator policy must not claim authority over an identity the managed runtime
ignores.
- Reject explicitly supplied
run_as_user and run_as_group for new managed
Docker/Podman sandboxes before default normalization.
- Retain those fields only for existing containers and unmanaged/offline
execution.
- Persist and expose the resolved identity separately: source, immutable image
ID, UID, GID, and supplementary groups.
- Emit the same record as an OCSF configuration-state event, but do not use logs
as the authoritative API.
Resolve and persist an identity-versioned record before launching the first
agent. Restarts consume that record instead of re-resolving a mutable tag or
account file. Existing containers without the new metadata continue using their
legacy launch-effective policy and are not reinterpreted during upgrade.
User Namespaces
Validate resolved IDs against /proc/self/uid_map and
/proc/self/gid_map before chown or privilege drop. This covers rootless
Podman, rootless Docker, Docker userns-remap, and nested or remote setups.
Report the image declaration and unmapped ID without silently substituting a
different identity.
Respect /proc/self/setgroups: a denied clear is acceptable only if the current
supplementary-group list is already empty.
Compatibility And Documentation
- First-party images already declaring
USER sandbox retain their baked
identity.
- First-party and documented images with no final non-root
USER must add one.
- The BYOC and private-IP-routing examples need explicit users.
- The userless fixture should declare a distinct numeric UID/GID without
account entries.
- Existing sandboxes retain their creation-time identity. Changing identity
mode or fixed IDs applies only to newly created sandboxes; persisted
workspaces require recreation or an explicit migration.
- Update architecture docs, driver READMEs, BYOC documentation, gateway config
reference, and examples. Clearly distinguish local image identity from
Kubernetes/OpenShift platform-assigned identity.
Test Plan
Unit
- Parse named, numeric, named-group, numeric-group, and mixed OCI forms.
- Resolve named users to passwd UID and primary GID.
- Reject missing declarations, accountless UID-only declarations, root aliases,
malformed input, duplicate entries, symlinks, non-regular files, and
oversized account databases.
- Verify all image forms clear supplementary groups.
- Verify
/sandbox root ownership changes without touching descendants.
- Verify passwd/group files remain unchanged.
- Verify old containers without identity metadata retain legacy behavior.
Driver Integration
- Docker inspects after local hits and every pull policy, carries the immutable
image ID, and preserves supervisor user = 0.
- Podman image inspection handles compatible response shapes and escaping,
carries the immutable image ID, disables a second pull decision, and
preserves supervisor user = 0:0.
- Image mode rejects creator-selected external mounts; fixed mode uses explicit
validated UID/GID.
- Image and request environment cannot spoof protected identity metadata.
E2E
- Passwd-less
USER 1234:1235 runs as 1234:1235, has no supplementary
groups or account entries, and writes to /sandbox.
- Named
USER app uses the image account's UID and primary GID.
- Explicit named and mixed group forms resolve correctly.
- Missing
USER, explicit root, and root aliases fail clearly.
- Rootless Podman, rootless Docker, and Docker userns-remap report unmapped IDs.
- Direct and SSH-launched children use the same identity and environment.
- Existing sandbox resume retains its persisted identity.
Alternatives Considered
- Fixed
10001:10001 by default: rejected because it breaks normal local
image ownership and incorrectly copies the OpenShift model.
- Continue requiring
sandbox:sandbox: rejected because it prevents
passwd-less numeric images and arbitrary valid image users.
- Allow image identity with creator-selected external mounts: rejected
because an untrusted image could choose the principal owning unrelated
storage.
- Run the supervisor as OCI
USER: rejected because namespace, mount,
network, filesystem, and privilege-drop setup require root.
- Resolve names through NSS: rejected because privileged resolution must not
invoke image-controlled NSS configuration or modules.
Scope
This issue covers Docker, Podman, shared supervisor identity resolution,
creation-time identity persistence and observability, affected policy handling,
tests, examples, and documentation.
Kubernetes/OpenShift and VM identity selection are out of scope except for
regression tests protecting their existing behavior.
Problem Statement
Docker and Podman start the OpenShell supervisor as root and replace the image
entrypoint. Today, agent identity then falls back to the policy's named
sandbox:sandboxaccount, which requires every image to bake that exact userand group.
The first implementation attempt in #2335 replaced that requirement with a
gateway-wide numeric identity, defaulting every local image to
10001:10001.That is also the wrong local-driver abstraction: it discards the image's normal
OCI
USER, can break image file ownership, and unnecessarily copies theplatform-assigned identity model used by Kubernetes/OpenShift.
Docker and Podman should preserve a sandbox image's declared non-root OCI
USERfor agent children while continuing to run only the privilegedsupervisor as
0:0.This remains the Docker/Podman portion of #1959. PR #2335 is closed and
superseded by this design.
Design Decisions
USER; OCI defaults those images toroot.
USER root,USER 0, or any name resolving to UID or GID 0.deployments.
User Contract
USER appappfrom the image passwd file and use its UID and primary GID.USER app:staffUSER 1234:1235USER 1234with a passwd entry1234and that entry's primary GID.USER 1234without a passwd entryUSERA passwd-less image therefore needs only:
USER 1234:1235It does not need
useradd,groupadd, or a user namedsandbox.Identity Modes
Local drivers expose an operator-selected identity source:
imageis the default. The agent uses OCIConfig.Userand may use only thedriver-owned per-sandbox workspace plus immutable image mounts.
fixedrequires an explicit validated UID and GID. It supports deploymentsthat intentionally attach external or shared storage under an
operator-controlled principal.
The unshipped implicit
10001:10001default and standalone Podman UID/GID flagsfrom #2335 must not return.
Image Inspection
Resolve or pull the final image before container creation, then inspect it and
carry both its immutable image ID and OCI
Config.Userthrough provisioning.Create the container from that immutable ID so a mutable tag cannot change
between identity inspection and rootfs selection.
inspect_image()and currently discardsImageInspect; readinspect.config.userand retain the image ID.GET /v5.0.0/libpod/images/{name}/json, reading the image ID andConfig.User.image ID has been selected.
user = "0"; Podman keepsuser = "0:0". Thosevalues apply only to the supervisor.
Pass the raw OCI user declaration, immutable image ID, and identity source to
the supervisor through protected driver-owned metadata inserted after image and
request environment.
Supervisor Resolution
This extends an existing mechanism. The supervisor already runs inside the
sandbox image rootfs and resolves the legacy
sandbox:sandboxidentity beforefilesystem preparation and the verified
setgid/setuidsequence. Direct andSSH children already share that privilege-drop boundary.
Do not reuse the current libc NSS path unchanged for arbitrary image users.
Resolve names with a bounded file-only parser:
/etc/passwdand/etc/groupwithout following symlinks.getpwnam,getgrnam, NSS modules, orinitgroupsfrom theprivileged supervisor.
The resulting internal record contains at least:
Agent children receive no supplementary groups. Clear and verify the group list
before
setgid/setuidfor both named and numeric image declarations. PreserveHOME=/sandbox; use the declared name forUSER/LOGNAMEwhen available andthe numeric UID otherwise.
OpenShell must not create or rewrite
/etc/passwdor/etc/group.Storage Boundary
The image is untrusted and may select any valid non-root UID/GID. That identity
may authorize access to its rootfs and isolated workspace, but it must not
select the principal used for unrelated storage.
In image mode, reject all creator-selected bind mounts and named-volume mounts.
The driver-owned per-sandbox
/sandboxvolume and immutable image mounts remainallowed. A global bind-mount enable switch is not sufficient authorization for
image-selected IDs.
Operators that require external or shared storage must select fixed mode until
OpenShell has operator-authored mount grants binding storage sources to allowed
identities.
The supervisor may chown
/sandboxitself and missing paths OpenShell creates.It must not recursively chown existing descendants or nested mounts. Privileged
directory creation must use no-symlink ancestor traversal rather than checking
only the final path before
create_dir_all.Policy And Observability
Creator policy must not claim authority over an identity the managed runtime
ignores.
run_as_userandrun_as_groupfor new managedDocker/Podman sandboxes before default normalization.
execution.
ID, UID, GID, and supplementary groups.
as the authoritative API.
Resolve and persist an identity-versioned record before launching the first
agent. Restarts consume that record instead of re-resolving a mutable tag or
account file. Existing containers without the new metadata continue using their
legacy launch-effective policy and are not reinterpreted during upgrade.
User Namespaces
Validate resolved IDs against
/proc/self/uid_mapand/proc/self/gid_mapbefore chown or privilege drop. This covers rootlessPodman, rootless Docker, Docker
userns-remap, and nested or remote setups.Report the image declaration and unmapped ID without silently substituting a
different identity.
Respect
/proc/self/setgroups: a denied clear is acceptable only if the currentsupplementary-group list is already empty.
Compatibility And Documentation
USER sandboxretain their bakedidentity.
USERmust add one.account entries.
mode or fixed IDs applies only to newly created sandboxes; persisted
workspaces require recreation or an explicit migration.
reference, and examples. Clearly distinguish local image identity from
Kubernetes/OpenShift platform-assigned identity.
Test Plan
Unit
malformed input, duplicate entries, symlinks, non-regular files, and
oversized account databases.
/sandboxroot ownership changes without touching descendants.Driver Integration
image ID, and preserves supervisor
user = 0.carries the immutable image ID, disables a second pull decision, and
preserves supervisor
user = 0:0.validated UID/GID.
E2E
USER 1234:1235runs as1234:1235, has no supplementarygroups or account entries, and writes to
/sandbox.USER appuses the image account's UID and primary GID.USER, explicit root, and root aliases fail clearly.Alternatives Considered
10001:10001by default: rejected because it breaks normal localimage ownership and incorrectly copies the OpenShift model.
sandbox:sandbox: rejected because it preventspasswd-less numeric images and arbitrary valid image users.
because an untrusted image could choose the principal owning unrelated
storage.
USER: rejected because namespace, mount,network, filesystem, and privilege-drop setup require root.
invoke image-controlled NSS configuration or modules.
Scope
This issue covers Docker, Podman, shared supervisor identity resolution,
creation-time identity persistence and observability, affected policy handling,
tests, examples, and documentation.
Kubernetes/OpenShift and VM identity selection are out of scope except for
regression tests protecting their existing behavior.