Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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 and restart (stop+start) are now granted; kill,
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
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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`.
Expand Down Expand Up @@ -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`) 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
Expand Down
31 changes: 31 additions & 0 deletions src/security.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ const MUTATING_ENDPOINTS: &[&str] = &[
const RUNTIME_ENDPOINTS: &[&str] = &[
"/containers/create",
"/containers/*/start",
"/containers/*/stop",
"/containers/*/restart",
"/containers/*/exec",
"/containers/*/wait",
"/containers/*/archive",
Expand Down Expand Up @@ -225,6 +227,8 @@ impl SecurityFilter {
endpoint.as_str(),
"/containers/create"
| "/containers/*/start"
| "/containers/*/stop"
| "/containers/*/restart"
| "/containers/*/exec"
| "/containers/*/wait"
| "/containers/*"
Expand Down Expand Up @@ -644,6 +648,33 @@ 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());
// 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/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"
);
}
// 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]
Expand Down