How to Configure SELinux Labels for Docker Mounts Using the `selinuxLabel` Option in Sandcastle

Set the selinuxLabel option to "z" for shared SELinux contexts, "Z" for private contexts, or false to disable labeling entirely when mounting volumes into Sandcastle's Docker sandbox.

Sandcastle is an open-source framework for running agentic code in sandboxed containers. When using the Docker provider on SELinux-enabled hosts like RHEL, CentOS, or Fedora, the selinuxLabel configuration option controls the security context suffix appended to bind mounts, preventing permission errors while maintaining appropriate filesystem access controls.

Where the selinuxLabel Option Is Defined

The SELinux labeling configuration is split between type definitions in src/mountUtils.ts and the Docker provider implementation in src/sandboxes/docker.ts.

The SelinuxLabel Type Definition

In src/mountUtils.ts, the SelinuxLabel type restricts the option to three valid values:

export type SelinuxLabel = "z" | "Z" | false;

This union type ensures compile-time safety, allowing only the shared label character ("z"), the private label character ("Z"), or explicit disabling (false).

The DockerOptions Interface

The docker() factory function accepts configuration through the DockerOptions interface located in src/sandboxes/docker.ts:

export interface DockerOptions {
  // ... other configuration options
  /** SELinux volume label suffix applied to bind mounts. */
  readonly selinuxLabel?: SelinuxLabel;
}

How SELinux Labels Are Applied to Mounts

When Sandcastle constructs the Docker run command, it processes each mount through the formatVolumeMount function in src/mountUtils.ts. This utility concatenates the SELinux suffix after other mount options like ro (read-only).

export const formatVolumeMount = (
  mount: { hostPath: string; sandboxPath: string; readonly?: boolean },
  selinuxLabel: SelinuxLabel | undefined,
): string => {
  const base = `${mount.hostPath}:${mount.sandboxPath}`;
  const options = [mount.readonly ? "ro" : undefined, selinuxLabel || undefined]
    .filter((option): option is string => option !== undefined)
    .join(",");

  return options ? `${base}:${options}` : base;
};

The function produces Docker volume strings following these rules:

  • "z" → Appends :z for shared SELinux labeling, allowing multiple containers to access the files with the same context
  • "Z" → Appends :Z for private SELinux labeling, restricting the mount to an unshared context exclusive to the container
  • false → Omits any SELinux suffix, resulting in a standard mount without security context modification

Configuration Examples

You can configure SELinux labeling when initializing the Docker sandbox provider. If omitted, Sandcastle automatically applies shared labeling.

Using the Default Shared Label (z)

When you call docker() without specifying selinuxLabel, Sandcastle defaults to "z" as implemented in the provider's constructor in src/sandboxes/docker.ts:

import { docker } from "sandcastle/sandboxes/docker";

await run({
  agent: claudeCode("claude-opus-4-7"),
  sandbox: docker(), // selinuxLabel defaults to "z"
});

This generates mount strings ending with :z, suitable for most development scenarios where the container needs to read and write files without changing their security context globally.

Configuring a Private Label (Z)

For scenarios requiring isolated security contexts—such as when handling sensitive data across multiple containers—set selinuxLabel to "Z":

import { docker } from "sandcastle/sandboxes/docker";

await run({
  agent: claudeCode("claude-opus-4-7"),
  sandbox: docker({
    selinuxLabel: "Z", // Mount suffix will be :Z
    mounts: [
      { hostPath: "~/my-data", sandboxPath: "/data", readonly: false },
    ],
  }),
});

This results in Docker volume strings like /home/user/data:/data:Z, ensuring the container uses a private unshared SELinux label that other containers cannot access.

Disabling SELinux Labeling

To completely bypass SELinux relabeling—useful for hosts without SELinux or when using alternative security modules like AppArmor—set the option to false:

import { docker } from "sandcastle/sandboxes/docker";

await run({
  agent: claudeCode("claude-opus-4-7"),
  sandbox: docker({
    selinuxLabel: false, // No :z or :Z suffix appended
  }),
});

The resulting Docker command excludes any SELinux suffix, mounting volumes without security context modification.

Default Behavior

If you do not explicitly provide a selinuxLabel in your configuration, Sandcastle automatically falls back to "z" (shared labeling). This default is established in src/sandboxes/docker.ts using the nullish coalescing operator:

const selinuxLabel = options?.selinuxLabel ?? "z";

This conservative default prevents the permission denials that occur on SELinux-enforcing systems when labels are missing, while maintaining compatibility across different Linux distributions.

Summary

  • Configure SELinux labels using the selinuxLabel option in the docker() factory function to control how bind mounts are labeled in the container runtime.
  • Choose "z" for shared contexts that multiple containers can access, "Z" for private contexts isolated to a single container, or false to disable SELinux labeling entirely.
  • Implementation relies on src/mountUtils.ts for the SelinuxLabel type and formatVolumeMount logic, while src/sandboxes/docker.ts handles the default "z" fallback.
  • Type safety is enforced through the SelinuxLabel union type, restricting values to "z", "Z", or false at compile time.

Frequently Asked Questions

What happens if I don't specify selinuxLabel in my Sandcastle configuration?

Sandcastle defaults to "z" (shared labeling). According to the source code in src/sandboxes/docker.ts, the provider checks options?.selinuxLabel ?? "z", which means undefined values automatically resolve to the shared label suffix, ensuring consistent behavior across SELinux-enabled hosts.

When should I use "Z" instead of "z" for SELinux labels?

Use "Z" (private label) when you want the mounted content to use an unshared SELinux context that only the current container can access. Use "z" (shared label) when multiple containers need to access the same files, or when you want the container to interact with files that other non-containerized processes also use. Private labels prevent security context leakage between containers.

Does the Podman provider in Sandcastle support the same SELinux options?

Yes. The Podman sandbox provider in src/sandboxes/podman.ts mirrors the Docker provider's implementation and accepts the same selinuxLabel configuration. Both providers import and utilize the SelinuxLabel type and formatVolumeMount function from src/mountUtils.ts, ensuring identical SELinux behavior across container runtimes.

Can I mix SELinux labels within the same sandbox configuration?

No. The selinuxLabel option applies globally to all bind mounts configured for that sandbox instance. Sandcastle processes every mount in the mounts array through the same formatVolumeMount function call using the single selinuxLabel value. If you require different SELinux contexts for different host paths, you must create separate sandbox instances with distinct configurations.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →