fix(drivers): narrow OCI workspace path restrictions

Signed-off-by: Matthew Grossman <mgrossman@nvidia.com>
This commit is contained in:
Matthew Grossman
2026-09-28 16:58:21 -07:00
parent b95b66a650
commit 0d20c7ffe5
9 changed files with 171 additions and 127 deletions
+3 -2
View File
@@ -429,8 +429,9 @@ live.
Docker and Podman resolve OCI `Config.User` and `Config.WorkingDir` from one
immutable image inspection. Empty, `/`, and explicit `/sandbox` values use the
managed `/sandbox` workspace. Custom paths must be normalized absolute paths
that do not overlap `/proc`, `/sys`, `/dev`, or OpenShell's private paths. Image
and driver mounts cannot cover the workspace path or one of its parents.
that do not overlap `/proc`, `/sys`, `/dev`, or private mounts still inside the
workload container. Image and driver mounts cannot cover the workspace path or
one of its parents. Supervisor-only paths are not reserved in the workload.
Podman mounts its persistent workspace volume at a custom root. When the volume
is first created, Podman copies existing image-directory contents into it.
+35 -87
View File
@@ -84,9 +84,17 @@ pub fn validate_mount_subpath(subpath: &str) -> Result<(), String> {
/// Workspace collisions depend on the inspected image's resolved working
/// directory and are checked separately by `validate_workspace_mount_target`.
pub fn validate_container_mount_target(target: &str) -> Result<(), String> {
validate_container_mount_target_for_workload(target, CONTROL_ROOTS)
}
/// Validate a mount target against paths used by this specific workload.
pub fn validate_container_mount_target_for_workload(
target: &str,
workload_reserved_paths: &[&str],
) -> Result<(), String> {
let normalized = normalize_absolute_container_path(target, "mount target")?;
let path = Path::new(&normalized);
for reserved in CONTROL_ROOTS {
for reserved in workload_reserved_paths {
let reserved = Path::new(reserved);
if paths_overlap(path, reserved) {
return Err(format!(
@@ -106,6 +114,16 @@ pub fn validate_container_mount_target(target: &str) -> Result<(), String> {
/// value and the path passed to the supervisor cannot be interpreted
/// differently.
pub fn resolve_oci_workspace_root(working_dir: &str) -> Result<String, String> {
// The sandbox runtime checks syntax and OCI mounts again; each compute
// driver checks its own workload mounts before admitting the workspace.
resolve_oci_workspace_root_for_workload(working_dir, &[])
}
/// Resolve a workspace against paths still mounted inside this workload.
pub fn resolve_oci_workspace_root_for_workload(
working_dir: &str,
workload_reserved_paths: &[&str],
) -> Result<String, String> {
if working_dir.is_empty() || working_dir == "/" {
return Ok(DEFAULT_WORKSPACE_ROOT.to_string());
}
@@ -113,7 +131,7 @@ pub fn resolve_oci_workspace_root(working_dir: &str) -> Result<String, String> {
for runtime_path in OCI_RUNTIME_MOUNT_ROOTS {
validate_workspace_reserved_path(&workspace_root, runtime_path, "OCI runtime mount")?;
}
for control_path in CONTROL_ROOTS {
for control_path in workload_reserved_paths {
validate_workspace_control_path(&workspace_root, control_path)?;
}
@@ -178,23 +196,6 @@ fn validate_workspace_reserved_path(
Ok(())
}
/// Reject a mount that contains or is contained by a runtime-configured
/// `OpenShell` control path, such as the sandbox SSH socket.
pub fn validate_mount_control_path(target: &str, control_path: &str) -> Result<(), String> {
let normalized_target = normalize_absolute_container_path(target, "mount target")?;
let normalized_control =
normalize_absolute_container_path(control_path, "OpenShell control path")?;
if paths_overlap(
Path::new(&normalized_target),
Path::new(&normalized_control),
) {
return Err(format!(
"mount target '{target}' conflicts with OpenShell control path '{control_path}'"
));
}
Ok(())
}
/// Reject a user-supplied mount that would replace or contain the resolved
/// workspace root. Mounts below the workspace remain valid.
pub fn validate_workspace_mount_target(target: &str, workspace_root: &str) -> Result<(), String> {
@@ -278,86 +279,33 @@ mod tests {
}
#[test]
fn oci_workspace_root_rejects_runtime_and_openshell_control_path_collisions() {
fn oci_workspace_root_rejects_runtime_and_selected_workload_paths() {
let reserved = &["/control"];
for invalid in [
"/proc",
"/proc/self",
"/sys",
"/sys/fs/cgroup",
"/dev",
"/dev/shm",
"/etc",
"/opt",
"/opt/openshell",
"/opt/openshell/bin/project",
"/etc/openshell/tls/client",
"/etc/openshell/auth",
"/etc/openshell/skills",
"/etc/openshell-tls",
"/run",
"/run/openshell/cache",
"/run/openshell-sidecar/control.sock",
"/run/netns/project",
"/var/run/netns/project",
"/control",
"/control/data",
] {
assert!(
resolve_oci_workspace_root(invalid).is_err(),
"expected control-path workspace '{invalid}' to be rejected"
);
}
for valid in [
"/app",
"/etc/project",
"/home/app",
"/opt/app",
"/usr/bin/project",
"/usr/src/app",
"/var/lib/app",
"/var/app/current",
"/var/task",
"/var/www/app",
"/processor",
"/system",
"/device",
] {
assert_eq!(
resolve_oci_workspace_root(valid).unwrap(),
valid,
"expected application workspace '{valid}' to remain valid"
resolve_oci_workspace_root_for_workload(invalid, reserved).is_err(),
"expected workspace '{invalid}' to be rejected"
);
}
assert_eq!(
resolve_oci_workspace_root_for_workload("/etc/openshell", reserved).unwrap(),
"/etc/openshell"
);
}
#[test]
fn container_target_rejects_reserved_openshell_tls_legacy_path() {
let err = validate_container_mount_target("/etc/openshell-tls/proxy/client").unwrap_err();
assert!(err.contains("/etc/openshell-tls"));
}
#[test]
fn container_target_rejects_reserved_openshell_tree() {
let err = validate_container_mount_target("/etc/openshell/tls/client").unwrap_err();
assert!(err.contains("/etc/openshell"));
}
#[test]
fn container_target_does_not_prefix_match_unrelated_paths() {
validate_container_mount_target("/etc/openshell-tools").unwrap();
validate_container_mount_target("/run/openshell-tools").unwrap();
}
#[test]
fn mount_target_rejects_runtime_configured_control_path_overlap() {
for target in ["/custom", "/custom/ssh.sock", "/custom/ssh.sock/cache"] {
assert!(
validate_mount_control_path(target, "/custom/ssh.sock").is_err(),
"expected '{target}' to conflict with the configured control path"
);
}
validate_mount_control_path("/custom-other", "/custom/ssh.sock").unwrap();
fn container_target_uses_selected_workload_paths() {
let reserved = &["/control"];
assert!(validate_container_mount_target_for_workload("/control/data", reserved).is_err());
validate_container_mount_target_for_workload("/control-tools", reserved).unwrap();
validate_container_mount_target_for_workload("/etc/openshell", reserved).unwrap();
}
#[test]
+4 -2
View File
@@ -67,7 +67,8 @@ traverse every parent and write and enter the workdir; OpenShell does not
change its ownership or mode.
Image `VOLUME` declarations and user mounts must not cover the workdir, one of
its parents, or the reserved `/.openshell` runtime/channel tree. OpenShell asks
its parents, the workload's `/.openshell` runtime/channel tree, or its
`/run/openshell-supervisor-ca` mount. OpenShell asks
the kernel to validate access under the final identity, so POSIX ACL and host
LSM decisions remain authoritative.
@@ -114,7 +115,8 @@ mount types are:
Host bind mounts are disabled by default because they expose daemon-host paths
to sandbox requests. User bind and volume mounts are read-only by default.
Targets must be absolute, normalized paths and cannot overlap the workspace
root or OpenShell control paths.
root or private mounts still used inside the workload. Supervisor-only paths
are allowed.
Example:
+26 -14
View File
@@ -105,6 +105,10 @@ const BOUNDARY_CONFIG_MOUNT_PATH: &str = "/.openshell/channel/sandbox/bootstrap.
const BOUNDARY_SOCKET_MOUNT_PATH: &str = "/.openshell/channel/sandbox/control.sock";
const BOUNDARY_CERTIFICATE_MOUNT_PATH: &str = "/.openshell/channel/sandbox/server.crt";
const BOUNDARY_PRIVATE_KEY_MOUNT_PATH: &str = "/.openshell/channel/sandbox/server.key";
const WORKLOAD_RESERVED_PATHS: &[&str] = &[
"/.openshell",
openshell_sandbox_backend::SUPERVISOR_CA_RUNTIME_ROOT,
];
const SUPERVISOR_STATE_MOUNT_PATH: &str = "/.openshell/supervisor";
const SUPERVISOR_PROXY_AUTH_MOUNT_PATH: &str = "/.openshell/supervisor/upstream-proxy-auth";
const PROVIDER_SPIFFE_WORKLOAD_API_SOCKET_MOUNT_DIR: &str =
@@ -3759,7 +3763,8 @@ fn docker_bind_string(
"bind source path does not exist: {source}"
)));
}
driver_mounts::validate_container_mount_target(target).map_err(Status::failed_precondition)?;
driver_mounts::validate_container_mount_target_for_workload(target, WORKLOAD_RESERVED_PATHS)
.map_err(Status::failed_precondition)?;
let normalized_target = driver_mounts::normalize_mount_target(target);
let mut opts = Vec::new();
@@ -3897,8 +3902,11 @@ fn validate_docker_driver_mounts(
));
}
};
driver_mounts::validate_container_mount_target(target)
.map_err(Status::failed_precondition)?;
driver_mounts::validate_container_mount_target_for_workload(
target,
WORKLOAD_RESERVED_PATHS,
)
.map_err(Status::failed_precondition)?;
let normalized_target = driver_mounts::normalize_mount_target(target);
if !targets.insert(normalized_target.clone()) {
return Err(Status::failed_precondition(format!(
@@ -4524,8 +4532,11 @@ async fn prepare_docker_boundary_files(
gpu_requested: bool,
) -> Result<(), Status> {
let directory = docker_boundary_state_dir(sandbox, config)?;
let workspace_root = driver_mounts::resolve_oci_workspace_root(&image.working_dir)
.map_err(Status::failed_precondition)?;
let workspace_root = driver_mounts::resolve_oci_workspace_root_for_workload(
&image.working_dir,
WORKLOAD_RESERVED_PATHS,
)
.map_err(Status::failed_precondition)?;
let launch_authentication = sandbox
.spec
.as_ref()
@@ -5652,12 +5663,17 @@ fn build_container_create_body_for_image(
.as_ref()
.ok_or_else(|| Status::invalid_argument("sandbox.spec.template is required"))?;
let resource_limits = docker_resource_limits(template)?;
let workspace_root = driver_mounts::resolve_oci_workspace_root(&image.working_dir)
.map_err(Status::failed_precondition)?;
driver_mounts::validate_workspace_control_path(&workspace_root, BOUNDARY_MOUNT_PATH)
.map_err(Status::failed_precondition)?;
let workspace_root = driver_mounts::resolve_oci_workspace_root_for_workload(
&image.working_dir,
WORKLOAD_RESERVED_PATHS,
)
.map_err(Status::failed_precondition)?;
for volume in &image.volumes {
driver_mounts::validate_container_mount_target(volume).map_err(|error| {
driver_mounts::validate_container_mount_target_for_workload(
volume,
WORKLOAD_RESERVED_PATHS,
)
.map_err(|error| {
Status::failed_precondition(format!(
"invalid image-declared volume '{volume}': {error}"
))
@@ -5667,8 +5683,6 @@ fn build_container_create_body_for_image(
"image-declared volume '{volume}' masks OCI WorkingDir '{workspace_root}' before workspace validation"
))
})?;
driver_mounts::validate_mount_control_path(volume, BOUNDARY_MOUNT_PATH)
.map_err(Status::failed_precondition)?;
}
for mount in &driver_config.mounts {
let target = match mount {
@@ -5679,8 +5693,6 @@ fn build_container_create_body_for_image(
};
driver_mounts::validate_workspace_mount_target(target, &workspace_root)
.map_err(Status::failed_precondition)?;
driver_mounts::validate_mount_control_path(target, BOUNDARY_MOUNT_PATH)
.map_err(Status::failed_precondition)?;
}
let mut user_mounts = docker_driver_mounts(driver_config)?;
user_mounts.push(Mount {
+27 -3
View File
@@ -1551,10 +1551,10 @@ fn container_creation_rejects_invalid_oci_working_dir() {
#[test]
fn container_creation_rejects_openshell_control_path_working_dir() {
let metadata = DockerImageMetadata {
let mut metadata = DockerImageMetadata {
id: "sha256:immutable".to_string(),
user: "1234:1235".to_string(),
working_dir: "/opt/openshell/bin/project".to_string(),
working_dir: "/.openshell/runtime/project".to_string(),
volumes: Vec::new(),
};
let err = build_container_create_body_for_image(
@@ -1569,6 +1569,17 @@ fn container_creation_rejects_openshell_control_path_working_dir() {
assert_eq!(err.code(), tonic::Code::FailedPrecondition);
assert!(err.message().contains("OpenShell control path"));
metadata.working_dir = "/opt/openshell/bin/project".to_string();
build_container_create_body_for_image(
&test_sandbox(),
&runtime_config(),
&DockerSandboxDriverConfig::default(),
None,
&metadata,
&test_workload_identity(),
)
.expect("supervisor-only paths are not reserved in the workload");
}
#[test]
@@ -2145,7 +2156,7 @@ fn driver_config_rejects_reserved_mount_targets() {
"mounts": [{
"type": "volume",
"source": "work-nfs",
"target": "/etc/openshell/auth"
"target": "/.openshell/runtime"
}]
})));
@@ -2153,6 +2164,19 @@ fn driver_config_rejects_reserved_mount_targets() {
assert_eq!(err.code(), tonic::Code::FailedPrecondition);
assert!(err.message().contains("reserved OpenShell path"));
sandbox
.spec
.as_mut()
.unwrap()
.template
.as_mut()
.unwrap()
.driver_config = Some(json_struct(serde_json::json!({
"mounts": [{"type": "volume", "source": "work-nfs", "target": "/etc/openshell/auth"}]
})));
build_container_create_body(&sandbox, &runtime_config())
.expect("supervisor-only paths are not reserved in the workload");
}
#[test]
+7 -4
View File
@@ -95,9 +95,12 @@ environment belong to agent children, never the supervisor process.
OpenShell reads `WORKDIR` from the workload image. If it is unset, `/`, or
`/sandbox`, OpenShell uses its managed `/sandbox` workspace. A custom path must
be absolute, with no `.` or `..` segments. It cannot overlap container system
paths (`/proc`, `/sys`, `/dev`) or OpenShell's private paths (for example,
`/.openshell` and `/run/openshell`). Image and driver mounts may be inside the
workspace, but cannot replace the workspace path or one of its parents.
paths (`/proc`, `/sys`, `/dev`) or mounts still used in the workload:
`/.openshell` for the control channel, `/opt/openshell/bin` for the sandbox
runtime, and `/run/openshell-supervisor-ca` for generated CA material. Paths
used only by the separate supervisor are allowed. Image and driver mounts may
be inside the workspace, but cannot replace the workspace path or one of its
parents.
For a custom path, Podman mounts a persistent workspace volume there. When the
volume is first created, Podman copies any files already in that image directory
@@ -136,7 +139,7 @@ User `bind`, `volume`, `tmpfs`, and `image` mounts and CDI GPU selection remain
native Podman features and apply only to the workload. Bind mounts require the
operator's `enable_bind_mounts` opt-in and disabled label admission. Supplemental
image mounts also require disabled admission. Driver JSON requires
`allow_driver_config = true`. Reserved control paths and the workspace
`allow_driver_config = true`. The workload's private mounts and workspace
root cannot be replaced. User-owned volumes are never created or deleted.
See [gateway configuration](../../docs/how-it-works/gateways/configuration.mdx) for
+66 -13
View File
@@ -75,6 +75,11 @@ const PROVIDER_SPIFFE_WORKLOAD_API_SOCKET_MOUNT_DIR: &str =
const SUPERVISOR_MOUNT_DIR: &str = openshell_core::driver_utils::SUPERVISOR_CONTAINER_DIR;
/// Full path to the supervisor binary inside sandbox containers.
const SUPERVISOR_BINARY_PATH: &str = openshell_core::driver_utils::SUPERVISOR_CONTAINER_BINARY;
const WORKLOAD_RESERVED_PATHS: &[&str] = &[
"/.openshell",
SUPERVISOR_MOUNT_DIR,
openshell_sandbox_backend::SUPERVISOR_CA_RUNTIME_ROOT,
];
#[derive(Debug, Clone, Default, serde::Deserialize)]
#[serde(default, deny_unknown_fields)]
@@ -235,15 +240,18 @@ impl ResolvedPodmanImage {
/// - an image volume covering the workspace or any of its ancestors.
pub fn from_inspect(inspected: &ImageInspect) -> Result<Self, ComputeDriverError> {
let image_config = inspected.config.as_ref();
let workspace_root = driver_mounts::resolve_oci_workspace_root(
let workspace_root = driver_mounts::resolve_oci_workspace_root_for_workload(
image_config.map_or("", |config| config.working_dir.as_str()),
WORKLOAD_RESERVED_PATHS,
)
.map_err(ComputeDriverError::Precondition)?;
driver_mounts::validate_workspace_control_path(&workspace_root, "/.openshell")
.map_err(ComputeDriverError::Precondition)?;
if let Some(volumes) = image_config.and_then(|config| config.volumes.as_ref()) {
for volume in volumes.keys() {
driver_mounts::validate_container_mount_target(volume).map_err(|error| {
driver_mounts::validate_container_mount_target_for_workload(
volume,
WORKLOAD_RESERVED_PATHS,
)
.map_err(|error| {
ComputeDriverError::Precondition(format!(
"invalid image-declared volume '{volume}': {error}"
))
@@ -255,8 +263,6 @@ impl ResolvedPodmanImage {
))
},
)?;
driver_mounts::validate_mount_control_path(volume, "/.openshell")
.map_err(ComputeDriverError::Precondition)?;
}
}
@@ -871,7 +877,10 @@ fn podman_user_mounts(
None => {}
}
driver_mounts::validate_absolute_mount_source(&source, "bind source")?;
driver_mounts::validate_container_mount_target(&target)?;
driver_mounts::validate_container_mount_target_for_workload(
&target,
WORKLOAD_RESERVED_PATHS,
)?;
result.mounts.push(Mount {
kind: "bind".into(),
source,
@@ -887,7 +896,10 @@ fn podman_user_mounts(
} => {
reject_subpath(subpath.as_deref(), "podman volume mounts")?;
driver_mounts::validate_mount_source(&source, "volume source")?;
driver_mounts::validate_container_mount_target(&target)?;
driver_mounts::validate_container_mount_target_for_workload(
&target,
WORKLOAD_RESERVED_PATHS,
)?;
result.volumes.push(NamedVolume {
name: source,
dest: target,
@@ -913,7 +925,10 @@ fn podman_user_mounts(
{
options.push(format!("mode={mode:o}"));
}
driver_mounts::validate_container_mount_target(&target)?;
driver_mounts::validate_container_mount_target_for_workload(
&target,
WORKLOAD_RESERVED_PATHS,
)?;
result.mounts.push(Mount {
kind: "tmpfs".into(),
source: "tmpfs".into(),
@@ -929,7 +944,10 @@ fn podman_user_mounts(
} => {
reject_subpath(subpath.as_deref(), "podman image mounts")?;
driver_mounts::validate_mount_source(&source, "image source")?;
driver_mounts::validate_container_mount_target(&target)?;
driver_mounts::validate_container_mount_target_for_workload(
&target,
WORKLOAD_RESERVED_PATHS,
)?;
result.image_volumes.push(ImageVolume {
source,
destination: target,
@@ -1004,8 +1022,10 @@ fn validate_podman_driver_mounts(
target
}
};
driver_mounts::validate_container_mount_target(target)?;
driver_mounts::validate_mount_control_path(target, "/.openshell")?;
driver_mounts::validate_container_mount_target_for_workload(
target,
WORKLOAD_RESERVED_PATHS,
)?;
let normalized_target = driver_mounts::normalize_mount_target(target);
if !targets.insert(normalized_target.clone()) {
return Err(format!(
@@ -2104,6 +2124,26 @@ mod tests {
assert_eq!(image.workspace_root, "/workspace/project");
}
#[test]
fn resolved_image_allows_supervisor_only_workdir_but_reserves_workload_mounts() {
let inspect = |working_dir: &str| ImageInspect {
id: "sha256:image".into(),
config: Some(ImageConfig {
working_dir: working_dir.into(),
..Default::default()
}),
};
assert!(ResolvedPodmanImage::from_inspect(&inspect("/opt/openshell/bin/project")).is_err());
assert!(ResolvedPodmanImage::from_inspect(&inspect("/.openshell/channel")).is_err());
assert_eq!(
ResolvedPodmanImage::from_inspect(&inspect("/etc/openshell/tls/client"))
.unwrap()
.workspace_root,
"/etc/openshell/tls/client"
);
}
fn json_struct(value: Value) -> prost_types::Struct {
let Value::Object(object) = value else {
panic!("expected JSON object");
@@ -3449,7 +3489,7 @@ mod tests {
"mounts": [{
"type": "volume",
"source": "work-nfs",
"target": "/etc/openshell/tls/client"
"target": "/opt/openshell/bin"
}]
}))),
..Default::default()
@@ -3461,6 +3501,19 @@ mod tests {
let err = try_build_container_spec_with_token(&sandbox, &config, None).unwrap_err();
assert!(err.to_string().contains("reserved OpenShell path"));
sandbox
.spec
.as_mut()
.unwrap()
.template
.as_mut()
.unwrap()
.driver_config = Some(json_struct(serde_json::json!({
"mounts": [{"type": "volume", "source": "work-nfs", "target": "/etc/openshell/tls/client"}]
})));
try_build_container_spec_with_token(&sandbox, &config, None)
.expect("supervisor-only paths are not reserved in the workload");
}
#[test]
+2 -1
View File
@@ -271,7 +271,8 @@ On Docker and Podman, the image's `WORKDIR` becomes the workspace. Images with
no `WORKDIR`, `WORKDIR /`, or `WORKDIR /sandbox` use OpenShell's managed
`/sandbox` workspace. Custom paths must be absolute, with no `.` or `..`
segments, and cannot overlap container system paths (`/proc`, `/sys`, `/dev`)
or OpenShell's private paths. Image and driver mounts cannot replace the
or private mounts still used inside the workload. Paths used only by the
separate supervisor are allowed. Image and driver mounts cannot replace the
workspace or one of its parents.
Docker validates the directory in the image filesystem. Podman mounts the
+1 -1
View File
@@ -281,7 +281,7 @@ Common findings:
- Sandbox fails before readiness with an identity-resolution error: inspect the image's OCI `USER` and matching `/etc/passwd` and `/etc/group` entries, or explicitly set both process identity fields in policy. Numeric workload identities `1` through `4294967294` are accepted; root, the invalid identity sentinel, and missing identities are rejected.
- Sandbox fails before readiness with an OCI workspace validation error: inspect the image's `WorkingDir` using the immutable image ID reported by the gateway. Empty, `/`, and explicit `/sandbox` use the managed `/sandbox` compatibility workspace. Any other workdir must be an absolute normalized directory with no symlink components; the final policy UID, primary GID, and supplementary groups must pass the kernel's effective traverse/write checks, including POSIX ACL and LSM decisions. OpenShell does not create, chown, or chmod a non-default Docker workdir or a copied-up custom Podman workspace.
- Docker and Podman also reject an image `VOLUME` that covers the workdir or one of its parents because the runtime would mask the path before validation. Move the `VOLUME` below the workspace or remove the declaration.
- A workdir rejected as a special filesystem or OpenShell control-path collision cannot be made valid with permissions. Move the image workdir away from kernel-backed mounts and the concrete supervisor, TLS, token, runtime, and socket paths named in the error.
- A workdir rejected as a special filesystem or workload-mount collision cannot be made valid with permissions. Move it away from `/proc`, `/sys`, `/dev`, and the sandbox runtime or control-channel paths named in the error. Paths used only by the separate supervisor are allowed.
- Local Docker gateway setup cannot copy `openshell-sandbox` after exporting a supervisor image: the sandbox runtime and supervisor are separate artifacts. The runtime image must provide `/openshell-sandbox`; the supervisor image provides `/openshell-supervisor`.
- Docker driver cannot initialize because it cannot find `openshell-sandbox`: verify the sibling binary next to `openshell-gateway`, or that the configured `sandbox_runtime_image` contains `/openshell-sandbox`.
- Sandbox never registers: check gateway logs and the supervisor's gateway endpoint.