Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
fae9470
feat(execd): execd as sandbox init (OSEP-0018 phase 1)
Pangjiping Aug 11, 2026
bbe5de0
feat(execd): pre-exec hardening floor (OSEP-0018 phase 2)
Pangjiping Aug 11, 2026
fb1e3fc
feat(server): runtime.execd_run_as_init config injects EXECD_INIT
Pangjiping Aug 12, 2026
8818ba4
test(execd): real-container init-mode regression (OSEP-0018)
Pangjiping Aug 12, 2026
81924c3
feat(execd): landlock confinement (OSEP-0018 phase 3)
Pangjiping Aug 12, 2026
2111654
feat(execd): eBPF observation variant (OSEP-0018 phase 4)
Pangjiping Aug 12, 2026
ab3263f
docs(spec): drop phase markers from hardening layer descriptions
Pangjiping Aug 12, 2026
a81109c
feat(sdks): surface hardening status across all execd-capabilities SDKs
Pangjiping Aug 12, 2026
f06f1ff
feat(server,k8s): pool tasks run execd as the task root (OSEP-0018 ph…
Pangjiping Aug 12, 2026
9f9db05
docs(oseps): mark 0018 as implementing with granular phase status
Pangjiping Aug 12, 2026
dc1f48f
fix: address Codex/human review feedback across five rounds + CI repairs
Pangjiping Aug 12, 2026
fa7a494
fix: address Codex round 6 (credential identity, audit sink, /opt, sa…
Pangjiping Aug 12, 2026
d5ac089
test(e2e): dedicated execd-as-init real-e2e for python, plus nightly job
Pangjiping Aug 12, 2026
b88cd97
ci(e2e): fix execd-init e2e failures in python and k8s nightly jobs
Pangjiping Aug 12, 2026
be2dc8a
feat(execd): ship execd-ebpf in default image; docs and e2e sync (OSE…
Pangjiping Aug 12, 2026
8ae0b49
fix(execd): address Codex round 7 (landlock jupyter log, init-mode re…
Pangjiping Aug 12, 2026
cba75bc
docs(oseps): fold remaining-work tracking into OSEP-0018 status section
Pangjiping Aug 13, 2026
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
13 changes: 13 additions & 0 deletions .github/workflows/execd-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ jobs:
run: |
go test -v -coverpkg=./... -coverprofile=coverage.out -covermode=atomic ./pkg/...

- name: Run execd-ebpf variant tests
working-directory: components/execd
run: |
CGO_ENABLED=1 go test -tags ebpf -count=1 ./pkg/ebpf/

- name: Calculate coverage and generate summary
working-directory: components/execd
id: coverage
Expand Down Expand Up @@ -130,6 +135,14 @@ jobs:
chmod +x components/execd/tests/smoke_bwrap.sh
bash components/execd/tests/smoke_bwrap.sh

- name: Init-mode container regression (OSEP-0018)
if: matrix.os == 'ubuntu-latest'
shell: bash
timeout-minutes: 30
run: |
chmod +x components/execd/tests/init_container.sh
bash components/execd/tests/init_container.sh

- name: Show logs
if: always()
run: |
Expand Down
71 changes: 71 additions & 0 deletions .github/workflows/kubernetes-nightly-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,77 @@ jobs:
run: |
kind delete cluster --name "${KIND_CLUSTER}" || true

execd-init-e2e:
name: Execd-Init E2E
needs: changes
if: needs.changes.outputs.relevant == 'true'
runs-on: ubuntu-latest
env:
KIND_CLUSTER: opensandbox-e2e
KIND_K8S_VERSION: v1.30.4
KUBECONFIG_PATH: /tmp/opensandbox-kind-kubeconfig
KUBECONFIG: /tmp/opensandbox-kind-kubeconfig
OPENSANDBOX_E2E_SANDBOX_CPU: 250m
OPENSANDBOX_E2E_SANDBOX_MEMORY: 512Mi
steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.11"

- name: Set up Go
uses: actions/setup-go@v6
with:
go-version: "1.25.0"

- name: Add Go bin to PATH
run: echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH"

- name: Set up uv
uses: astral-sh/setup-uv@v7
with:
version: "latest"

- name: Set up kubectl
uses: azure/setup-kubectl@v4

- name: Set up Helm
uses: azure/setup-helm@v4

- name: Run execd-init E2E
run: bash scripts/python-k8s-execd-init-e2e.sh

- name: Dump kind diagnostics
if: always()
run: |
kubectl get pods -A -o wide || true
kubectl get batchsandboxes -A || true
kubectl describe deployment -n opensandbox-system opensandbox-server || true

- name: Eval in-cluster server logs
if: always()
run: |
kubectl logs -n opensandbox-system deployment/opensandbox-server || true
cat /tmp/opensandbox-server-port-forward.log || true

- name: Upload Python test artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: execd-init-e2e-logs
path: |
/tmp/opensandbox-server-port-forward.log
if-no-files-found: ignore
retention-days: 5

- name: Clean up Kind cluster
if: always()
run: |
kind delete cluster --name "${KIND_CLUSTER}" || true

publish-nightly-latest:
name: Publish latest (${{ matrix.component }} nightly)
needs: k8s-mini-e2e
Expand Down
78 changes: 78 additions & 0 deletions .github/workflows/real-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,84 @@ jobs:
docker rm -f opensandbox-e2e-redis || true
docker run --rm -v /tmp:/host_tmp alpine rm -rf /host_tmp/opensandbox-e2e || true

python-execd-init-e2e:
name: Python Execd-Init E2E (docker bridge)
needs: changes
if: needs.changes.outputs.relevant == 'true'
runs-on: self-hosted
steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Clean Docker runner cache
run: bash scripts/ci-docker-cleanup.sh

- name: Set up uv
uses: astral-sh/setup-uv@v7

- name: Verify uv
run: |
uv --version
uv run python --version

- name: Clean up previous E2E resources
run: |
docker ps -aq --filter "label=opensandbox" | xargs -r docker rm -f || true
docker rm -f opensandbox-e2e-redis || true
docker run --rm -v /tmp:/host_tmp alpine rm -rf /host_tmp/opensandbox-e2e || true
docker image prune -f || true

- name: Build local egress image
run: docker build -t opensandbox/egress:local -f components/egress/Dockerfile .

- name: Run execd-init E2E
run: |
set -e

cat <<EOF > ~/.sandbox.toml
[server]
host = "127.0.0.1"
port = 8080
api_key = ""
[log]
level = "INFO"
[runtime]
type = "docker"
execd_image = "opensandbox/execd:local"
execd_run_as_init = true
[egress]
image = "opensandbox/egress:local"
mode = "dns+nft"
[docker]
network_mode = "bridge"
[storage]
allowed_host_paths = ["/tmp/opensandbox-e2e"]
[renew_intent]
enabled = true
min_interval_seconds = 60
EOF

bash scripts/python-execd-init-e2e.sh

- name: Eval server logs
if: ${{ always() }}
run: cat server/server.log

- name: Upload execd logs
if: always()
uses: actions/upload-artifact@v7
with:
name: execd-log-for-execd-init-e2e
path: /tmp/opensandbox-e2e/logs/
retention-days: 5

- name: Clean up after E2E
if: always()
run: |
docker ps -aq --filter "label=opensandbox" | xargs -r docker rm -f || true
docker rm -f opensandbox-e2e-redis || true
docker run --rm -v /tmp:/host_tmp alpine rm -rf /host_tmp/opensandbox-e2e || true

java-e2e:
name: Java E2E (docker bridge)
needs: changes
Expand Down
44 changes: 44 additions & 0 deletions components/execd/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ RUN apk add --no-cache git musl-dev meson ninja gcc libcap-dev libcap-static pkg
COPY components/execd/native/session-gate.c /build/session-gate.c
RUN gcc -Os -static -s -Wall -Wextra -Werror \
-o /build/opensandbox-session-gate /build/session-gate.c
COPY components/execd/native/launcher.c /build/launcher.c
RUN gcc -Os -static -s -Wall -Wextra -Werror \
-o /build/opensandbox-launcher /build/launcher.c
RUN git clone --depth 1 --branch v0.11.2 \
https://github.com/containers/bubblewrap /build/bwrap
WORKDIR /build/bwrap
Expand All @@ -93,11 +96,52 @@ RUN rm /usr/lib/libcap.so /usr/lib/libcap.so.2 && \
ninja -C builddir && \
cp builddir/bwrap /build/bwrap/bwrap

# execd-ebpf: observation variant (OSEP-0018 §5). The default image below
# never contains eBPF code; this stage builds a CGO variant with the
# exec/connect/privilege audit hooks attached.
FROM golang:1.25.9 AS ebpf-builder
Comment thread
Pangjiping marked this conversation as resolved.
ARG VERSION=dev
ARG GIT_COMMIT=unknown
ARG BUILD_TIME=unknown
RUN apt-get update && apt-get install -y --no-install-recommends clang
WORKDIR /build
COPY components/internal/go.mod components/internal/go.sum ./components/internal/
COPY components/execd/go.mod components/execd/go.sum ./components/execd/
RUN cd components/internal && go mod download
RUN cd components/execd && go mod download
COPY components/internal ./components/internal
COPY components/execd ./components/execd
WORKDIR /build/components/execd
# cilium/ebpf is pure Go, so the variant builds fully static.
RUN CGO_ENABLED=0 go build -tags ebpf -trimpath -buildvcs=false \
-ldflags "-buildid= -B none \
-X 'github.com/alibaba/opensandbox/internal/version.Version=${VERSION}' \
-X 'github.com/alibaba/opensandbox/internal/version.BuildTime=${BUILD_TIME}' \
-X 'github.com/alibaba/opensandbox/internal/version.GitCommit=${GIT_COMMIT}'" \
-o /build/execd-ebpf ./main.go

# The observation variant keeps the exact layout of the default image so it
# can be used as runtime.execd_image: /execd, /bootstrap.sh and the native
# helpers must be present for the server distribution paths.
FROM alpine:latest AS ebpf
COPY --from=bwrap-builder /build/bwrap/bwrap /usr/local/bin/bwrap
COPY --from=bwrap-builder --chown=0:0 --chmod=0555 /build/opensandbox-session-gate /usr/local/libexec/opensandbox-session-gate
COPY --from=bwrap-builder --chown=0:0 --chmod=0555 /build/opensandbox-session-gate /opt/opensandbox/opensandbox-session-gate
COPY --from=bwrap-builder --chown=0:0 --chmod=0555 /build/opensandbox-launcher /usr/local/libexec/opensandbox-launcher
COPY --from=bwrap-builder --chown=0:0 --chmod=0555 /build/opensandbox-launcher /opt/opensandbox/opensandbox-launcher
COPY --from=ebpf-builder /build/execd-ebpf ./execd
COPY --from=ebpf-builder /build/execd-ebpf ./execd-ebpf
COPY components/execd/bootstrap.sh ./bootstrap.sh
COPY components/execd/install.bat ./install.bat
ENTRYPOINT ["./execd"]

FROM alpine:latest

COPY --from=bwrap-builder /build/bwrap/bwrap /usr/local/bin/bwrap
COPY --from=bwrap-builder --chown=0:0 --chmod=0555 /build/opensandbox-session-gate /usr/local/libexec/opensandbox-session-gate
COPY --from=bwrap-builder --chown=0:0 --chmod=0555 /build/opensandbox-session-gate /opt/opensandbox/opensandbox-session-gate
COPY --from=bwrap-builder --chown=0:0 --chmod=0555 /build/opensandbox-launcher /usr/local/libexec/opensandbox-launcher
COPY --from=bwrap-builder --chown=0:0 --chmod=0555 /build/opensandbox-launcher /opt/opensandbox/opensandbox-launcher
COPY --from=builder /build/execd .
COPY --from=builder /build/execd.exe ./execd.exe
COPY --from=builder /build/opensandbox-supervisor ./opensandbox-supervisor
Expand Down
58 changes: 56 additions & 2 deletions components/execd/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ SESSION_GATE_SOURCE_INSTALL_DIR := /usr/local/libexec
SESSION_GATE_RUNTIME_DIR := /opt/opensandbox
SESSION_GATE_CFLAGS ?= $(CFLAGS) -O2 -Wall -Wextra -Werror
SESSION_GATE_LDFLAGS ?= -static -s
LAUNCHER_BINARY := bin/opensandbox-launcher
LAUNCHER_SOURCE := native/launcher.c
LAUNCHER_RUNTIME_DIR := /opt/opensandbox
INSTALL ?= install
DESTDIR ?=
ifeq ($(strip $(DESTDIR)),)
Expand All @@ -49,7 +52,7 @@ PROJECT_LDFLAGS := -buildid= -B none -X 'github.com/alibaba/opensandbox/internal
GO_BUILD_FLAGS := $(strip $(GOFLAGS) $(PROJECT_GOFLAGS))
GO_LDFLAGS := $(strip $(LDFLAGS) $(PROJECT_LDFLAGS))

.PHONY: build-session-gate install-session-gate
.PHONY: build-session-gate install-session-gate build-launcher install-launcher
build-session-gate:
@set -eu; \
host_goos="$$(go env GOHOSTOS)"; \
Expand Down Expand Up @@ -99,11 +102,62 @@ install-session-gate:
$(INSTALL) $(SESSION_GATE_INSTALL_OWNER_ARGS) -m 0555 "$(SESSION_GATE_BINARY)" \
"$(DESTDIR)$(SESSION_GATE_RUNTIME_DIR)/opensandbox-session-gate"

build-launcher:
@set -eu; \
host_goos="$$(go env GOHOSTOS)"; \
host_goarch="$$(go env GOHOSTARCH)"; \
target_goos="$(if $(GOOS),$(GOOS),$$(go env GOOS))"; \
target_goarch="$(if $(GOARCH),$(GOARCH),$$(go env GOARCH))"; \
if [ "$$target_goos" != "linux" ]; then \
echo "Skipping launcher: hardening requires Linux (target=$$target_goos/$$target_goarch)"; \
exit 0; \
fi; \
if [ "$$host_goos/$$host_goarch" != "$$target_goos/$$target_goarch" ]; then \
echo "launcher cross-build is unsupported (host=$$host_goos/$$host_goarch, target=$$target_goos/$$target_goarch)" >&2; \
echo "use the execd Docker build for multi-architecture Linux artifacts" >&2; \
exit 1; \
fi; \
mkdir -p bin; \
$(CC) $(CPPFLAGS) $(SESSION_GATE_CFLAGS) "$(LAUNCHER_SOURCE)" \
$(SESSION_GATE_LDFLAGS) -o "$(LAUNCHER_BINARY).tmp"; \
mv -f "$(LAUNCHER_BINARY).tmp" "$(LAUNCHER_BINARY)"

install-launcher:
@if [ "$$(uname -s)" != "Linux" ]; then \
echo "install-launcher requires Linux" >&2; \
exit 1; \
fi
@if [ ! -x "$(LAUNCHER_BINARY)" ]; then \
echo "$(LAUNCHER_BINARY) is missing; run make build-launcher first" >&2; \
exit 1; \
fi
@if [ -z "$(DESTDIR)" ] && [ "$$(id -u)" -ne 0 ]; then \
echo "install-launcher requires root unless DESTDIR is set" >&2; \
exit 1; \
fi
@umask 022; mkdir -p "$(DESTDIR)$(LAUNCHER_RUNTIME_DIR)"
@if [ -z "$(DESTDIR)" ]; then \
chown root:root "$(DESTDIR)$(LAUNCHER_RUNTIME_DIR)"; \
fi
chmod go-w "$(DESTDIR)$(LAUNCHER_RUNTIME_DIR)"
$(INSTALL) $(SESSION_GATE_INSTALL_OWNER_ARGS) -m 0555 "$(LAUNCHER_BINARY)" \
"$(DESTDIR)$(LAUNCHER_RUNTIME_DIR)/opensandbox-launcher"

.PHONY: build
build: vet build-session-gate ## Build execd and the Linux session gate.
build: vet build-session-gate build-launcher ## Build execd and the Linux native helpers.
@mkdir -p bin
go build $(GO_BUILD_FLAGS) -ldflags "$(GO_LDFLAGS)" -o bin/execd main.go

.PHONY: build-ebpf
build-ebpf: ## Build the execd-ebpf observation variant (CGO + cilium/ebpf).
@if [ "$$(uname -s 2>/dev/null || echo non-linux)" != "Linux" ]; then \
echo "execd-ebpf requires Linux (BPF attachable host)" >&2; \
exit 1; \
fi
@mkdir -p bin
CGO_ENABLED=1 go build -tags ebpf $(GO_BUILD_FLAGS) -ldflags "$(GO_LDFLAGS)" -o bin/execd-ebpf main.go
@echo "built bin/execd-ebpf"

.PHONY: test-integration
test-integration: ## Run integration tests (Linux + bwrap required).
go test -v -tags="linux,bwrap" -run Integration ./pkg/runtime/bwrap_test/
Expand Down
26 changes: 17 additions & 9 deletions components/execd/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,6 @@ if [ -n "${EXECD_BOOTSTRAP_PRE_SCRIPT:-}" ]; then
fi

echo "starting OpenSandbox Execd daemon at $EXECD."
$EXECD &
EXECD_PID=$!

# Allow chained shell commands (e.g., /test1.sh && /test2.sh)
# Usage:
Expand All @@ -337,17 +335,27 @@ if [ -z "$SHELL_BIN" ]; then
fi
fi

# Resolve the user command into a concrete argv shared by both branches.
if [ "$CMD" != "" ]; then
"$SHELL_BIN" -c "$CMD" &
CMD_PID=$!
set -- "$SHELL_BIN" -c "$CMD"
elif [ $# -eq 0 ]; then
"$SHELL_BIN" &
CMD_PID=$!
else
"$@" &
CMD_PID=$!
set -- "$SHELL_BIN"
fi

# Init mode (OSEP-0018): exec into execd so it becomes the sandbox init (PID 1
# on the direct paths) and supervises the user command itself. The shell must
# exec, never background, or execd would run as a subreaper without the kernel
# signal shield.
if is_truthy "${EXECD_INIT:-}"; then
exec "$EXECD" --init -- "$@"
fi

"$EXECD" &
EXECD_PID=$!

"$@" &
Comment thread
Pangjiping marked this conversation as resolved.
CMD_PID=$!

set +e
wait "$CMD_PID" 2>/dev/null
CMD_STATUS=$?
Expand Down
Loading
Loading