1
0
mirror of https://github.com/sredevopsorg/deepseek-kubernetes-sandbox.git synced 2026-11-03 01:13:24 +00:00
Files
deepseek-kubernetes-sandbox/docs/gitflow.md
SREDevOps.org Agent a09d4ac340 fix: ship bubblewrap for DSH's command sandbox, keep agents usable without it (#35)
* 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>
2026-10-30 08:36:21 -03:00

7.8 KiB

Gitflow: branches, pull requests and CI

This repository uses trunk-based development with release branches. main is always releasable; feature work happens on short-lived branches; releases are cut from main onto a release branch and tagged with an immutable release identity. See versioning for what the tags mean.

Branches

Branch Branched from Merged into Purpose
main — — The integration trunk. Protected, linear history, always releasable.
feat/<slug> main main (squash) New capability or example.
fix/<slug> main main (squash) Bug fix.
docs/<slug> main main (squash) Documentation only.
refactor/<slug>, chore/<slug>, test/<slug> main main (squash) Non-behavioral rework, maintenance, tests.
release/<identity> main at the release commit — Release stabilization and tagging. Only release-blocking fixes land here.
hotfix/<slug> The release branch (or the tagged commit) main and the release branch Urgent production fix.

Keep branches short-lived. Rebase on main rather than merging main into the branch; the PR is squash-merged, so individual branch commits are never part of main history.

Pull requests

  1. One scoped change per PR. Do not mix a version bump with unrelated work, and update the docs the change touches in the same PR.
  2. Conventional PR title. The title becomes the squash commit message, so it is linted by the pr-title check: feat(scope): ..., fix(scope): ..., docs(scope): ..., build: ..., ci: ..., refactor: ..., test: ..., chore: .... Breaking changes use feat(scope)!: ... or a BREAKING CHANGE: footer.
  3. CI must be green. All required checks below must pass before merge.
  4. Squash merge only. main stays linear and protected; merged PRs are one commit each.
  5. No direct pushes to main or to a release branch. Both are protected.
  6. Review for behavior changes. Infrastructure, workflow, release and gateway-contract changes need a maintainer review; trivial docs changes still need the checks.

Required checks

Check Workflow / job What it verifies
lint ci.yaml scripts/check-versions.sh (version pins and markdown tokens), scripts/check-examples.sh (example layout), shellcheck, actionlint, yamllint, hadolint and documentation links.
python ci-python.yaml Gateway ruff check/format and unit tests (pytest with coverage). Runs only when the gateway or this workflow changes.
manifests ci-manifests.yaml scripts/check-manifests.py (manifest shape) and a server-side dry-run of every example manifest against the agent-sandbox CRDs. Runs only when manifests, check scripts or versions.env change.
pr-title pr-title.yaml Conventional Commit PR title.
CodeQL codeql.yml Static analysis for Python and GitHub Actions.
dependency-review ci.yaml Dependency changes introduced by the PR (job dependency-review; runs on pull requests only).

python, manifests and build-amd64 use native on.pull_request.paths filtering, so PRs that do not touch the gateway, the manifests or the image definition skip them. The gateway image build and /healthz are no longer smoke-tested in a dedicated job: /healthz is covered by the gateway unit tests, the Dockerfile is linted by lint (hadolint), and the multi-arch image build is published by build-gateway.yaml on merge.

ci-image.yaml (build-amd64) is the PR-side check for the main image: when Dockerfile, .dockerignore or versions.env change it builds linux/amd64 with the pins from versions.env — no registry login, no push — and smoke-tests the result (--version, non-root UID, /workspace + DSH_HOME, the bubblewrap runner, the version label, and /favicon.svg served from a running container). The published multi-arch build still happens only in build-main.yaml on merge.

Because a skipped path-filtered check is not a passing check, only the always-on checks (lint, pr-title, CodeQL, dependency-review) should be enforced as required status checks; python, manifests and build-amd64 are on-demand checks that skip when unrelated to the change.

The image build workflows (_build-image.yaml, build-main.yaml, build-gateway.yaml) and e2e-kind.yaml are not required on every PR: the image builds run where they are needed, and the kind end-to-end suite is reserved for release validation and manual dispatch because it is slow.

Releases

Releases are cut from main:

  1. Confirm main is green and make check, make test, make lint pass.
  2. Bump versions.env if the DSH ref changed, using make set-version; otherwise keep the same DSH_VERSION and bump RELEASE_REVISION.
  3. Create release/<identity> from the release commit.
  4. Run make release-dry-run and review the generated notes.
  5. Create and push the annotated dsh-* release tag. The release.yaml workflow validates that the tag matches versions.env, builds and pushes the multi-arch images (version + latest), creates the GitHub Release from the git-cliff notes, and opens a chore(changelog) PR that refreshes CHANGELOG.md on main.
  6. Merge the changelog PR and any release-branch-only fixes back to main.

Full procedure: versioning.

Hotfixes

A released tag is never moved or re-tagged — tags and image identities are immutable. To fix a broken release:

  1. Branch hotfix/<slug> from the release tag (or its release branch).
  2. Make the minimal fix, add a test, open a PR; the same required checks apply.
  3. Publish a new identity with a higher RELEASE_REVISION (-r(N+1)). Do not reuse or delete the broken tag.
  4. Merge the fix back into main (or cherry-pick it) so the trunk is fixed too.

Rollback is also a re-release, never a retag: see versioning.

Tag protection

All release tags match dsh-* and are protected:

  • only maintainers (the release workflow) may create them;
  • they cannot be deleted or force-updated;
  • they are annotated and point at the release commit on a release branch.

Image tags derive from the same identity and are equally immutable; the mutable latest and main image channels always point at a release or a trunk build respectively.

One-time GitHub settings checklist

Configure these on the repository before relying on the workflow:

  • Branches → main: require a pull request; require the checks lint, python, manifests, pr-title, CodeQL, dependency-review; require branches to be up to date; require conversation resolution; restrict direct pushes; allow only squash merges and enable "Automatically delete head branches".
  • Branches → release/*: same protections, plus allow the release workflow to push tags.
  • Tags → dsh-*: restrict creation/update/deletion to maintainers; a tag push triggers the release workflow.
  • Actions → General: allow GitHub Actions; set workflow permissions to read-only by default; allow GITHUB_TOKEN to create pull requests only where required.
  • Actions → Runners: make the arm64 runners (ubuntu-24.04-arm) available to the public/private repository as appropriate.
  • Packages: grant the release workflow packages: write (workflow permissions do this) and protect the ghcr.io/sredevopsorg/deepseek-kubernetes-sandbox packages from deletion.
  • Security: enable CodeQL default setup or keep the advanced codeql.yml workflow (not both), enable Dependabot alerts and dependency review.
  • Secrets/Variables: none are needed for image publishing; the workflows use GITHUB_TOKEN.