Skip to content

fix(agent-server): PLTF-3565 use a minimal Debian Python/Node runtime - #5077

Merged
neubig merged 7 commits into
mainfrom
security/minimal-debian-python-node
Sep 15, 2026
Merged

neubig merged 7 commits into
mainfrom
security/minimal-debian-python-node

Conversation

@neubig

@neubig neubig commented Sep 15, 2026 •

Copy link
Copy Markdown
Member

HUMAN:

I iterated pretty extensively with the agent to get the binary-minimal image down to what's truly minimal to pass tests.

I also brought up an agent server with the binary-minimal image and it is performing work properly.

Screenshot 2026-09-15 at 8 48 48 AM

AGENT:

Why

The published Python agent-server image uses nikolaik/python-nodejs, which includes tooling the server does not need. This change replaces it with a canonical Debian runtime and makes binary-minimal a strict runtime image to reduce size and Trivy findings.

Summary

  • Build python-node-runtime from debian:trixie-slim with Python 3.13.15 and Node.js 24.21.0 copied from their official images.
  • Resolve package upgrades from dated Debian snapshots; temporarily pin 20260913T000000Z to pick up Debian 13.7 before the normal seven-day observation period.
  • Keep only Bash, Git, tmux, tini, and required runtime libraries in binary-minimal.
  • Move compilers, sudo, uv/uvx, repository helpers, and general development utilities to the full image.
  • Preserve custom BASE_IMAGE support and update Python image CI and TypeScript integration builds to use the internal runtime.
  • Add a weekly/manual workflow that advances the snapshot monotonically after seven days, builds and Trivy-scans binary-minimal, then opens or refreshes a maintenance PR.

Why are REST API changes listed? This image PR does not modify those endpoints. CI tests the branch merged with current main, while the summary compares against the PR's earlier base commit 29383741587f. After that base, main merged #4991, which corrected FastAPI query metadata from lte=100 to le=100; OpenAPI therefore now publishes maximum: 100 for limits that were already enforced at runtime. The REST breakage checker explicitly accepts this wire-compatible metadata repair, and all API checks pass.

Trivy results

Using the same Trivy 0.74.0 vulnerability database and scanner settings, the local binary-minimal scan improved as follows:

binary-minimal Before After Change
Critical 2 0 -2
High 233 59 -174
Medium 820 80 -740
Low 731 94 -637
Unknown 8 1 -7
Total 1,794 234 -1,560 (-87.0%)
Debian packages 196 121 -75
Image size 377 MB 252 MB -125 MB

The remaining 59 high findings comprise 53 Debian findings without currently available upgrades, four npm findings, and two Python-package findings. There are no critical findings.

Issue Number

Contributes to #4643

How to Test

docker build \
  --target binary-minimal \
  --build-arg INSTALL_ACP_PROVIDERS= \
  -t agent-server:minimal-runtime \
  -f openhands-agent-server/openhands/agent_server/docker/Dockerfile .
docker run -d --name agent-server-minimal \
  -p 127.0.0.1:8000:8000 \
  agent-server:minimal-runtime --host 0.0.0.0
curl --fail http://127.0.0.1:8000/health
docker exec agent-server-minimal sh -lc \
  'cat /etc/debian_version; python --version; node --version; git --version; tmux -V'

Local validation:

  • Built strict binary-minimal and full image targets.
  • Verified /health, Bash execution, Git operations, tmux, Python, and Node in binary-minimal.
  • Verified the full image retains GCC/G++, make, sudo, uv, curl/wget, jq, Chromium, Docker, and GitHub CLI.
  • Passed 74 focused Docker build/metadata tests and all pre-commit checks.
  • Built and smoke-tested binary-minimal against Debian 13.7 snapshot 20260913T000000Z; added 17 passing snapshot policy/workflow tests.
  • Debian 13.7 Trivy scan: 233 findings (0 critical, 59 high, 79 medium, 94 low, 1 unknown), essentially restoring the original post-refactor result while retaining snapshot reproducibility.

Video/Screenshots

Not applicable.

Design Doc

Not applicable; this is a contained Docker image refactor.

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

This pull request description was generated by an AI agent (OpenHands) on behalf of the user.


🐳 Agent Server images for this PR — GHCR package, pull/run commands, and all pushed tags (click to expand)

• GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python-slim amd64, arm64 python-node-runtime Link
python amd64, arm64 python-node-runtime Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:d1df117-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-d1df117-python \
  ghcr.io/openhands/agent-server:d1df117-python

All tags pushed for this build

ghcr.io/openhands/agent-server:d1df117-golang-amd64
ghcr.io/openhands/agent-server:d1df117ee8cbf9761a9b7f94158dde6b084351f0-golang-amd64
ghcr.io/openhands/agent-server:security-minimal-debian-python-node-golang-amd64
ghcr.io/openhands/agent-server:d1df117-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:d1df117-golang-arm64
ghcr.io/openhands/agent-server:d1df117ee8cbf9761a9b7f94158dde6b084351f0-golang-arm64
ghcr.io/openhands/agent-server:security-minimal-debian-python-node-golang-arm64
ghcr.io/openhands/agent-server:d1df117-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:d1df117-java-amd64
ghcr.io/openhands/agent-server:d1df117ee8cbf9761a9b7f94158dde6b084351f0-java-amd64
ghcr.io/openhands/agent-server:security-minimal-debian-python-node-java-amd64
ghcr.io/openhands/agent-server:d1df117-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:d1df117-java-arm64
ghcr.io/openhands/agent-server:d1df117ee8cbf9761a9b7f94158dde6b084351f0-java-arm64
ghcr.io/openhands/agent-server:security-minimal-debian-python-node-java-arm64
ghcr.io/openhands/agent-server:d1df117-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:d1df117-python-amd64
ghcr.io/openhands/agent-server:d1df117ee8cbf9761a9b7f94158dde6b084351f0-python-amd64
ghcr.io/openhands/agent-server:security-minimal-debian-python-node-python-amd64
ghcr.io/openhands/agent-server:d1df117-python-node-runtime-amd64
ghcr.io/openhands/agent-server:d1df117-python-arm64
ghcr.io/openhands/agent-server:d1df117ee8cbf9761a9b7f94158dde6b084351f0-python-arm64
ghcr.io/openhands/agent-server:security-minimal-debian-python-node-python-arm64
ghcr.io/openhands/agent-server:d1df117-python-node-runtime-arm64
ghcr.io/openhands/agent-server:d1df117-python-slim-amd64
ghcr.io/openhands/agent-server:d1df117ee8cbf9761a9b7f94158dde6b084351f0-python-slim-amd64
ghcr.io/openhands/agent-server:security-minimal-debian-python-node-python-slim-amd64
ghcr.io/openhands/agent-server:d1df117-python-node-runtime-slim-amd64
ghcr.io/openhands/agent-server:d1df117-python-slim-arm64
ghcr.io/openhands/agent-server:d1df117ee8cbf9761a9b7f94158dde6b084351f0-python-slim-arm64
ghcr.io/openhands/agent-server:security-minimal-debian-python-node-python-slim-arm64
ghcr.io/openhands/agent-server:d1df117-python-node-runtime-slim-arm64
ghcr.io/openhands/agent-server:d1df117-golang
ghcr.io/openhands/agent-server:d1df117ee8cbf9761a9b7f94158dde6b084351f0-golang
ghcr.io/openhands/agent-server:security-minimal-debian-python-node-golang
ghcr.io/openhands/agent-server:d1df117-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:d1df117-java
ghcr.io/openhands/agent-server:d1df117ee8cbf9761a9b7f94158dde6b084351f0-java
ghcr.io/openhands/agent-server:security-minimal-debian-python-node-java
ghcr.io/openhands/agent-server:d1df117-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:d1df117-python-slim
ghcr.io/openhands/agent-server:d1df117ee8cbf9761a9b7f94158dde6b084351f0-python-slim
ghcr.io/openhands/agent-server:security-minimal-debian-python-node-python-slim
ghcr.io/openhands/agent-server:d1df117-python-node-runtime-slim
ghcr.io/openhands/agent-server:d1df117-python
ghcr.io/openhands/agent-server:d1df117ee8cbf9761a9b7f94158dde6b084351f0-python
ghcr.io/openhands/agent-server:security-minimal-debian-python-node-python
ghcr.io/openhands/agent-server:d1df117-python-node-runtime

About Multi-Architecture Support

  • Each variant tag (e.g., d1df117-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., d1df117-python-amd64) are also available if needed

Build the default Python runtime from Debian slim plus official Python and Node artifacts, and upgrade Debian packages before use.

Co-authored-by: openhands <openhands@all-hands.dev>
@github-actions

github-actions Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result: ✅ PASSED

Action log

@neubig
neubig marked this pull request as draft September 15, 2026 01:19
@github-actions

github-actions Bot commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-agent-server/openhands/agent_server/docker
   build.py58813078%101, 104–105, 119, 124, 128–130, 134, 139, 156–161, 164, 187, 189, 197, 199–200, 202–206, 208, 212, 215, 218–221, 223–225, 227, 229, 231, 233, 250, 349–350, 354–355, 359–360, 364–365, 378, 380–388, 511, 525, 531–532, 550, 556, 576–577, 579, 653, 667, 670, 674–675, 679–680, 684, 700–702, 705, 711–714, 724, 739, 760, 790, 809–810, 814–816, 870, 981, 991–992, 1173–1175, 1179–1180, 1183, 1200–1205, 1207–1208, 1210–1212, 1215–1216, 1221, 1225, 1271–1273, 1275–1276, 1278–1282, 1295
TOTAL431751799158% 

@neubig
neubig marked this pull request as ready for review September 15, 2026 03:13
Co-authored-by: openhands <openhands@all-hands.dev>
@neubig neubig changed the title fix(agent-server): replace nikolaik runtime base fix(agent-server): use a minimal Debian Python/Node runtime Sep 15, 2026
@all-hands-bot

Copy link
Copy Markdown
Collaborator

🤖 OpenHands is reviewing this PR.

Head commit: a455a3f8320b660d04d18b68950ddc1b1a441394
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/95f6b013-1bb5-4e5c-9bbd-8f483898b8b9

This comment was posted by an AI agent (OpenHands).

all-hands-bot
all-hands-bot previously approved these changes Sep 15, 2026

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.

Summary

This PR replaces the third-party nikolaik/python-nodejs base image with a canonical Debian runtime built from debian:trixie-slim + official Python 3.13.15 and Node 24.21.0 images. It also makes binary-minimal a strict runtime image (only bash, git, tini, tmux) while moving dev tools (compilers, sudo, uv, curl/wget, jq) to the full base-image stage.

Assessment

The change is well-structured and low-risk:

  • Published images are unaffected: Both python and python-slim CI variants use TARGET: binary, which builds from the full base-image stage. All dev tools (build-essential, curl, wget, sudo, uv, jq, Chromium, Docker, gh) remain available in published images. The binary-minimal target is opt-in only.

  • No server code depends on removed tools: No agent-server Python code references curl, wget, or sudo. The server uses Python HTTP libraries for network access, and the agent terminal tool runs commands in the user workspace, not the server runtime.

  • build.py default fix: The old build.py default was nikolaik/python-nodejs:python3.12-nodejs22-slim (Python 3.12), while the workflow used the 3.13 variant. This PR aligns both to python-node-runtime (Python 3.13.15), fixing a stale default. The removed PyInstaller/Python 3.12 compatibility comment (#1886) is no longer relevant since the builder stage already uses Python 3.13.

  • Custom BASE_IMAGE preserved: ARG BASE_IMAGE=python-node-runtime still allows external image overrides. When overridden, the internal python-node-runtime stage is simply unused by BuildKit.

  • Test coverage: test_python_image_uses_canonical_minimal_runtime verifies the runtime stage structure, minimal/full package separation, and that nikolaik references are fully removed.

  • Trivy improvement: 87% vulnerability reduction (1,794 to 234 findings) in binary-minimal with 0 criticals, as documented in the PR description.

Risk Assessment

LOW - Infrastructure/Docker refactor. No agent behavior changes, no prompt/tool/loop logic modifications. Published images retain full tooling. The binary-minimal target is opt-in and its reduced toolset is appropriate for a strict runtime image.

Verdict: Approve. Clean separation of minimal vs. full runtime, good test coverage, and meaningful security improvement.

@all-hands-bot
all-hands-bot dismissed their stale review September 15, 2026 05:05

Automated review used the wrong decision (APPROVED instead of COMMENT) and is dismissed. Findings are reposted as a comment.

@all-hands-bot

Copy link
Copy Markdown
Collaborator

The review was successfully posted to GitHub with state APPROVED and HTTP 200.

GITHUB_REVIEW_POSTED

This comment was posted by an AI agent (OpenHands).

Resolve runtime upgrades from dated Debian snapshots and add a weekly workflow that builds, scans, and proposes the next eligible snapshot.

Co-authored-by: openhands <openhands@all-hands.dev>
@all-hands-bot

Copy link
Copy Markdown
Collaborator

🤖 OpenHands is reviewing this PR.

Head commit: 604d6fa6863de09631bd538fd273960c9e9fbd13
View the conversation: https://oss-agent-canvas.ngrok.dev/conversations/c65ebba3-a203-483b-b941-2a75c5e6c106

This comment was posted by an AI agent (OpenHands).

all-hands-bot
all-hands-bot previously approved these changes Sep 15, 2026

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was created by an AI agent (OpenHands) on behalf of the repository maintainers.

Summary

This PR replaces the third-party nikolaik/python-nodejs base image with a canonical Debian trixie runtime (python-node-runtime) built by copying Python 3.13.15 and Node.js 24.21.0 from their official images into debian:trixie-slim. It splits the Dockerfile into a strict binary-minimal runtime (Bash, Git, tmux, tini, runtime libraries only) and a full base-image stage that retains compilers, sudo, uv, and development utilities. A dated Debian snapshot with a 7-day observation period governs package upgrades, and a weekly workflow automates snapshot advancement with build + Trivy scan before opening a maintenance PR.

Analysis

The approach is sound:

  • The python-node-runtime stage correctly copies /usr/local from the Python image and Node binaries from the Node image, then installs only the shared libraries those runtimes need. The explicit library list (libssl3t64, libffi8, libgcc-s1, libstdc++6, libsqlite3-0, etc.) covers the known Python C-extension dependencies.
  • The PyInstaller spec already excludes libgcc_s.so, libtinfo.so, and libncurses from bundled binaries to prevent builder/runtime version mismatches. Since the builder uses python:3.13-bookworm and the runtime is now trixie, this exclusion remains critical and is correctly preserved.
  • The snapshot pinning via debian.sources with Check-Valid-Until: no persists into downstream stages, so base-image-minimal's apt-get update resolves against the same dated snapshot. Custom BASE_IMAGE support is preserved — non-Debian images fall through to their native package managers.
  • The update_debian_snapshot.py script correctly computes a snapshot at least 7 days old, verifies it exists via HEAD requests to snapshot.debian.org, and replaces exactly one ARG DEBIAN_SNAPSHOT= pin.
  • The workflow builds and Trivy-scans binary-minimal before opening the PR, and uses force-with-lease on a dedicated maintenance branch.
  • Tests cover the key invariants: snapshot age validation, Dockerfile pin replacement, workflow step ordering, and package separation between minimal and full stages.
  • build.py default changed from Python 3.12 to python-node-runtime (Python 3.13), aligning with the Dockerfile default and the builder stage which already uses Python 3.13.

Risk Assessment

LOW — This is a CI/Infrastructure change (Docker image refactor). No agent behavior, prompts, tool logic, or evaluation harness code is modified. The PR description includes concrete validation: built both binary-minimal and full image targets, verified runtime functionality (health, Bash, Git, tmux, Python, Node), passed 74 Docker build/metadata tests, and smoke-tested binary-minimal against the snapshot. Trivy findings dropped 87% (1,794 to 234) with zero critical findings.

Verdict

Worth merging — Clean Docker image refactor that reduces image size and vulnerability surface while preserving all runtime capabilities. No material issues found.

openhands-agent and others added 2 commits September 15, 2026 13:58
Temporarily advance the package snapshot ahead of the normal observation window and keep scheduled updates monotonic so they do not revert the exception.

Co-authored-by: openhands <openhands@all-hands.dev>
@all-hands-bot
all-hands-bot dismissed their stale review September 15, 2026 14:29

Automated review used the wrong decision (APPROVED instead of COMMENT) and is dismissed. Findings are reposted as a comment.

@neubig
neubig merged commit 4c89b68 into main Sep 15, 2026
61 of 62 checks passed
@neubig
neubig deleted the security/minimal-debian-python-node branch September 15, 2026 15:50
VascoSch92 pushed a commit that referenced this pull request Sep 15, 2026
Co-authored-by: openhands <openhands@all-hands.dev>
(cherry picked from commit 4c89b68)
@aivong-openhands aivong-openhands changed the title fix(agent-server): use a minimal Debian Python/Node runtime fix(agent-server): PLTF-3565 use a minimal Debian Python/Node runtime Sep 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants