mirror of
https://github.com/sredevopsorg/deepseek-kubernetes-sandbox.git
synced 2026-11-03 01:13:24 +00:00
* fix(example): keep agents usable when DSH's inner sandbox has no backend DSH confines bash commands itself (bubblewrap, else Landlock) and fails closed with SANDBOX_UNAVAILABLE when no runner is usable. In this hardened pod - non-root, capabilities dropped, seccomp RuntimeDefault - bubblewrap cannot start (it needs CAP_SYS_ADMIN) and the pod exposes no Landlock runner, so the shipped workspace-write default made every command fail: sandbox mode "workspace-write" is requested but no sandbox backend is usable The DSH base bundle at the pinned ref documents DSH_PERMISSION_MODE as the explicit deployment override, so set it to danger-full-access in both sandbox templates. The per-user pod is the isolation boundary in this example (its own pod, PVC and Service; non-root; all capabilities dropped), so a working agent is worth more than an inner file policy that cannot be enforced. Note that danger-full-access also turns approval prompts off. Trade-off documented rather than hidden: an agent command can now read its own DSH_HOME (including .credentials.yaml) and write anywhere in the container. Hosts where a runner works should keep workspace-write or read-only. - main-image docs: DSH_PERMISSION_MODE row plus a note on the runners and the fail-closed behavior - example README: limits bullet and a troubleshooting row with the in-pod backend check and how to restore a confined mode - architecture: tenant-isolation bullet extended - skill runbook/troubleshooting: operation guidance; never grant CAP_SYS_ADMIN or seccompProfile: Unconfined to "fix" this without an explicit decision Verified: make check (14 manifests), make lint, YAML assertions on both templates, and the override expression at the pinned dsh-v0.2.1-rc.5 base bundle (mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'). * fix(image): install bubblewrap for DSH's command sandbox DSH's Linux sandbox runner chain is bubblewrap, then Landlock, and it fails closed with SANDBOX_UNAVAILABLE when neither works. The image shipped neither, so a sandboxed agent had no way to execute confined commands. Add bubblewrap to the runtime stage. Debian ships it unsetuid (0.12.0), so it still needs unprivileged user namespaces plus the namespace/mount syscalls: in a hardened pod (all capabilities dropped, RuntimeDefault seccomp) it usually cannot create one, and DSH then falls back to Landlock or, failing that, to the DSH_PERMISSION_MODE=danger-full-access override the templates already set. Where the host allows an unprivileged user namespace, the first-choice runner now exists, so the confined modes can be restored. Docs corrected to match: bubblewrap is no longer "absent"; the blocker is the unsetuid binary's namespace requirement, not a missing CAP_SYS_ADMIN grant. Verified: make check, make lint; the package exists in the pinned base (node:24-trixie-slim, bubblewrap 0.12.0-1~deb13u1); a throwaway container with the pod's flags (--user 1000 --cap-drop ALL --security-opt no-new-privileges) still cannot create a namespace, which is why the override stays. No local image build was run. * ci(image): build and smoke-test the main image on PRs that touch it The Dockerfile and the pins in versions.env could only be validated by a merge to main or by running `make image` by hand; neither gives a PR-time signal. Add ci-image.yaml: on pull requests that change Dockerfile, .dockerignore or versions.env (and on manual dispatch) it builds linux/amd64 natively with the pins sourced from versions.env - no registry login, no push - loads the image locally and smoke-tests it: - the packaged CLI runs and reports the pinned version - the container is the non-root node user (1000) and owns /workspace + DSH_HOME - the bubblewrap command-sandbox runner is present - the OCI version label matches versions.env - a container serves /favicon.svg (the same unauthenticated probe the shipped manifests use), with logs filtered so the launch token is never printed Path-filtered like the other on-demand checks, so it is not a required status check; the published multi-arch build stays in build-main.yaml and release.yaml. Docs updated in the same change: AGENTS.md repository map and testing table, docs/gitflow.md checks section, docs/main-image.md build section. Verified: actionlint 1.7.12 and yamllint 1.38.0 (CI versions) clean over the whole repository, plus make check and make lint. --------- Co-authored-by: SREDevOpsAgents <agents@sredevops.org>
Examples
Every implementation in this repository is a self-contained example: one
directory, plain kubectl, an end-to-end README, and a cleanup command.
Examples consume the main DSH Web image; they are
reference material, not libraries.
| Example | What it demonstrates | Needs |
|---|---|---|
examples/dsh-agents-as-a-service/ |
The full multi-tenant platform pattern: one SandboxTemplate with per-user PVCs, a warm pool, one ~10-line SandboxClaim per user, suspend-when-idle / wake-on-return, and a browser gateway with signup, HttpOnly sessions, an HTTP + WebSocket proxy and two exposure variants (Ingress, or Gateway API + GitHub SSO). |
agent-sandbox with extensions; kind for the e2e script. |
examples/minimal-single-tenant/ |
The smallest useful setup: a single SandboxTemplate and one SandboxClaim, no gateway — reach DSH Web with a launch-token URL through kubectl port-forward. |
agent-sandbox with extensions. |
The multi-tenant example also documents opt-in gVisor, nodeSelector,
NetworkPolicy and extra provider-key hardening in
optional hardening.
The example contract (short version)
Full rules: docs/examples.md. scripts/check-examples.sh
enforces the layout, and make check runs it.
examples/<name>/
+-- README.md # walkthrough: prerequisites, steps, troubleshooting, cleanup
+-- manifests/ # NN-*.yaml, applied with plain kubectl
| +-- optional/<feature>/ # optional opt-in manifests...
| +-- optional/README.md # ...required whenever optional/ exists
| +-- exposure/<variant>/ # optional per-variant manifests...
| +-- exposure/README.md # ...required whenever exposure/ exists
+-- scripts/ # optional executable, idempotent, self-cleaning *.sh
+-- docs/ # optional architecture/ADR material
+-- <component>/ # optional component with Dockerfile + README + tests
- Secrets never ship populated: use
*.yaml.examplefiles with placeholders. - Manifests are numbered (
00-,10-, ...) and read as an ordered walkthrough. - Component images are published nested below the main image:
<registry>/<owner>/<repo>/<example>/<component>. - Every example must be listed above with its literal
examples/<name>/path. - Scripts under
scripts/must be executable, idempotent and clean up after themselves.
Running an example
# e.g. the multi-tenant platform example
kubectl apply -f examples/dsh-agents-as-a-service/manifests/
# or run the whole walkthrough (kind + agent-sandbox required) and let it clean up
examples/dsh-agents-as-a-service/scripts/e2e-kind.sh
kubectl apply -f manifests/ is non-recursive: optional/ and exposure/
subdirectories are applied explicitly. Each example README has the exact
prerequisites, optional hardening, exposure options and teardown.