From a3c5b428f7a29ce09e4981a1ba79aaf0a29b14ab Mon Sep 17 00:00:00 2001 From: Axell Padilla <68310020+axellpadilla@users.noreply.github.com> Date: Fri, 14 Aug 2026 22:52:47 +0000 Subject: [PATCH 1/2] Allow container stop in container-runtime profile (#20) DockerRunLauncher.terminate() cancels runs via POST /containers/{id}/stop, which the container-runtime profile still denied. Grant stop alongside the other launcher lifecycle endpoints; kill, restart, pause, unpause, rename, update, resize, attach, and commit stay denied. --- CHANGELOG.md | 9 +++++++++ README.md | 4 ++-- src/security.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac6b368..7a7a8ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ All notable changes to this project are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [Unreleased] + +### Fixed +- **`container-runtime` now allows graceful container stop.** The profile + claimed to support `DockerRunLauncher` lifecycle calls, but + `POST /containers/{id}/stop` was still denied, so `DockerRunLauncher.terminate()` + could not cancel runs. Stop is now granted; kill, restart, pause, unpause, + rename, update, resize, attach, and commit remain denied. + ## [0.3.1] — 2026-08-13 Security and correctness fixes from a full code review, plus a repaired diff --git a/README.md b/README.md index a2ad100..c839740 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ the host. |---|---| | `default` | Read-only endpoints on GET and HEAD; mutation blocked | | `read-only` | The same reads, with every write method denied on every endpoint | -| `container-runtime` | Launching and managing containers, with create bodies inspected | +| `container-runtime` | Launching and managing containers, with create bodies inspected (graceful stop included) | | `none` | Nothing — your allowlist is the whole policy | `read-only` is a standard descriptive name for Docker API consumers that need inspection only. `container-runtime` is the generic profile for trusted workload orchestrators such as Dagster's official `DockerRunLauncher`. @@ -215,7 +215,7 @@ This proxy **reduces** the blast radius of socket exposure. It does not eliminat ### Container Runtime Profile -Use the opt-in `container-runtime` profile for Docker-backed orchestrators. It supports `DockerRunLauncher` lifecycle calls, custom containers, image builds and loads, bind/volume mounts, network connections, `docker exec`, and wait/log/archive operations. Privileged mode, capability changes, host devices, and namespace overrides remain blocked. +Use the opt-in `container-runtime` profile for Docker-backed orchestrators. It supports `DockerRunLauncher` lifecycle calls, custom containers, image builds and loads, bind/volume mounts, network connections, `docker exec`, and wait/log/archive operations. Graceful termination (`POST /containers/{id}/stop`) is included so `DockerRunLauncher` cancel/terminate works; kill, restart, and pause remain denied. Privileged mode, capability changes, host devices, and namespace overrides remain blocked. ```bash DOCKER_PROXY_PROFILE=container-runtime docker-socket-proxy diff --git a/src/security.rs b/src/security.rs index bf3d7e2..873803a 100644 --- a/src/security.rs +++ b/src/security.rs @@ -141,6 +141,7 @@ const MUTATING_ENDPOINTS: &[&str] = &[ const RUNTIME_ENDPOINTS: &[&str] = &[ "/containers/create", "/containers/*/start", + "/containers/*/stop", "/containers/*/exec", "/containers/*/wait", "/containers/*/archive", @@ -225,6 +226,7 @@ impl SecurityFilter { endpoint.as_str(), "/containers/create" | "/containers/*/start" + | "/containers/*/stop" | "/containers/*/exec" | "/containers/*/wait" | "/containers/*" @@ -644,6 +646,30 @@ mod tests { assert!(f.check("POST", "/images/load").is_ok()); assert!(f.check("POST", "/containers/abc/wait").is_ok()); assert!(f.check("DELETE", "/containers/abc").is_ok()); + // POST /containers/{id}/stop is the DockerRunLauncher terminate path. + assert!(f.check("POST", "/containers/abc/stop").is_ok()); + } + + #[test] + fn container_runtime_profile_keeps_destructive_ops_denied() { + let f = SecurityFilter::for_profile(&SecurityProfile::ContainerRuntime); + for (method, path) in [ + ("POST", "/containers/abc/restart"), + ("POST", "/containers/abc/kill"), + ("POST", "/containers/abc/pause"), + ("POST", "/containers/abc/unpause"), + ("POST", "/containers/abc/rename"), + ("POST", "/containers/abc/update"), + ("POST", "/containers/abc/resize"), + ("POST", "/containers/abc/attach"), + ("POST", "/commit"), + ] { + assert!( + f.check(method, path).is_err(), + "{method} {path} stays denied for container-runtime" + ); + } + assert!(f.check("POST", "/containers/abc/stop").is_ok()); } #[test] From fdc24ef4d7dc066ee3c36a27a3e6ab0ac948b679 Mon Sep 17 00:00:00 2001 From: Axell Padilla <68310020+axellpadilla@users.noreply.github.com> Date: Fri, 14 Aug 2026 22:55:26 +0000 Subject: [PATCH 2/2] Also grant container restart in container-runtime profile (#20) Restart is compositionally stop+start (SIGTERM -> timeout -> SIGKILL -> start), so denying it while granting both parts is a ceremonial boundary. Grant restart as a legitimate orchestrator lifecycle op; kill, pause, unpause, rename, update, resize, attach, and commit stay denied. --- CHANGELOG.md | 8 ++++---- README.md | 4 ++-- src/security.rs | 7 ++++++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7a7a8ae..9eec7f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,11 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Fixed -- **`container-runtime` now allows graceful container stop.** The profile - claimed to support `DockerRunLauncher` lifecycle calls, but +- **`container-runtime` now allows graceful container stop and restart.** The + profile claimed to support `DockerRunLauncher` lifecycle calls, but `POST /containers/{id}/stop` was still denied, so `DockerRunLauncher.terminate()` - could not cancel runs. Stop is now granted; kill, restart, pause, unpause, - rename, update, resize, attach, and commit remain denied. + could not cancel runs. Stop and restart (stop+start) are now granted; kill, + pause, unpause, rename, update, resize, attach, and commit remain denied. ## [0.3.1] — 2026-08-13 diff --git a/README.md b/README.md index c839740..a153d96 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ the host. |---|---| | `default` | Read-only endpoints on GET and HEAD; mutation blocked | | `read-only` | The same reads, with every write method denied on every endpoint | -| `container-runtime` | Launching and managing containers, with create bodies inspected (graceful stop included) | +| `container-runtime` | Launching and managing containers, with create bodies inspected (graceful stop and restart included) | | `none` | Nothing — your allowlist is the whole policy | `read-only` is a standard descriptive name for Docker API consumers that need inspection only. `container-runtime` is the generic profile for trusted workload orchestrators such as Dagster's official `DockerRunLauncher`. @@ -215,7 +215,7 @@ This proxy **reduces** the blast radius of socket exposure. It does not eliminat ### Container Runtime Profile -Use the opt-in `container-runtime` profile for Docker-backed orchestrators. It supports `DockerRunLauncher` lifecycle calls, custom containers, image builds and loads, bind/volume mounts, network connections, `docker exec`, and wait/log/archive operations. Graceful termination (`POST /containers/{id}/stop`) is included so `DockerRunLauncher` cancel/terminate works; kill, restart, and pause remain denied. Privileged mode, capability changes, host devices, and namespace overrides remain blocked. +Use the opt-in `container-runtime` profile for Docker-backed orchestrators. It supports `DockerRunLauncher` lifecycle calls, custom containers, image builds and loads, bind/volume mounts, network connections, `docker exec`, and wait/log/archive operations. Graceful termination (`POST /containers/{id}/stop`) and restart are included for orchestrator lifecycle (restart is stop+start); kill, pause, unpause, rename, update, resize, attach, and commit remain denied. Privileged mode, capability changes, host devices, and namespace overrides remain blocked. ```bash DOCKER_PROXY_PROFILE=container-runtime docker-socket-proxy diff --git a/src/security.rs b/src/security.rs index 873803a..ca5476d 100644 --- a/src/security.rs +++ b/src/security.rs @@ -142,6 +142,7 @@ const RUNTIME_ENDPOINTS: &[&str] = &[ "/containers/create", "/containers/*/start", "/containers/*/stop", + "/containers/*/restart", "/containers/*/exec", "/containers/*/wait", "/containers/*/archive", @@ -227,6 +228,7 @@ impl SecurityFilter { "/containers/create" | "/containers/*/start" | "/containers/*/stop" + | "/containers/*/restart" | "/containers/*/exec" | "/containers/*/wait" | "/containers/*" @@ -648,13 +650,14 @@ mod tests { assert!(f.check("DELETE", "/containers/abc").is_ok()); // POST /containers/{id}/stop is the DockerRunLauncher terminate path. assert!(f.check("POST", "/containers/abc/stop").is_ok()); + // restart is stop+start, within the launcher lifecycle. + assert!(f.check("POST", "/containers/abc/restart").is_ok()); } #[test] fn container_runtime_profile_keeps_destructive_ops_denied() { let f = SecurityFilter::for_profile(&SecurityProfile::ContainerRuntime); for (method, path) in [ - ("POST", "/containers/abc/restart"), ("POST", "/containers/abc/kill"), ("POST", "/containers/abc/pause"), ("POST", "/containers/abc/unpause"), @@ -669,7 +672,9 @@ mod tests { "{method} {path} stays denied for container-runtime" ); } + // stop and restart (stop+start) are granted for orchestrator lifecycle. assert!(f.check("POST", "/containers/abc/stop").is_ok()); + assert!(f.check("POST", "/containers/abc/restart").is_ok()); } #[test]