feat: slim sandbox images + multi-harness restructure (bd openlock-8op)#47
Merged
Conversation
Single image: ubuntu + node (tarball-slim) + python3 + uv + corepack + sandbox user. Replaces 4-cap matrix. Tagged as ghcr.io/vessux/openlock-base:<sha256(content)[0..12]> by CI. Refs bd openlock-8op.
Defense-in-depth: TLS trust alone isn't enough for a security-focused sandbox tool. Per-arch SHA256 from nodejs.org/dist/v22.12.0/SHASUMS256.txt and github.com/astral-sh/uv/releases/download/0.5.11/. Refs bd openlock-8op.
Single base image carries node + python + uv, so policy allowlists both npm and pypi unconditionally. Paths updated for tarball node install (/usr/local/bin/node) and uv-based python tooling (no pip/venv). Refs bd openlock-8op.
Callers of defaultPolicyContent(caps) and policyKeyForCaps will be fixed in subsequent tasks (T8 rewrites openlock-folder.ts; T10 deletes detect-caps.ts). Refs bd openlock-8op.
Pure function generating the per-project Containerfile from harness selection + embedded base content + base hash. Snapshot-tested per harness combo. Refs bd openlock-8op.
Embeds base.Containerfile content at build time, computes ghcr-qualified tag from sha256, pulls from ghcr (fast path) or builds locally (dev mode / offline). Local builds get the same ghcr-qualified tag so user FROM lines resolve identically in both modes. Refs bd openlock-8op.
For FROM lines starting with ghcr.io/vessux/openlock-base:, resolve the base before building user's Containerfile. For third-party FROMs, defer pull to runtime. Embeds containers/base.Containerfile at compile time. Refs bd openlock-8op.
Sentinel-based harness-block preservation. Refuses to clobber if sentinel missing (user heavily restructured file). Shares HARNESS_SENTINEL const with seed-containerfile.ts. Refs bd openlock-8op.
- OpenlockFolderConfig no longer carries 'caps' (becomes warn-and-ignore via deprecations[]). - New 'restored-containerfile' origin: writes seed Containerfile on next sandbox create when only config+policy exist. - Deprecation warning surfaces on sandbox create. - session.ts now derives caps via detectCaps(projectPath) (load-bearing for image build until T9/T10 cut over). - npm-scoped-pkg integration test updated for caps-less defaultPolicyContent(). Refs bd openlock-8op.
- build-images.ts (update-images command): single ensureBase call, replacing 4-cap loop. - session.ts buildSandboxImage: reads user's .openlock/Containerfile and calls ensureSandbox, replacing cap-keyed DEFAULT_CONTAINERFILES lookup. Sandbox-create critical path is now M5-native. Refs bd openlock-8op.
- detect-caps.ts: no longer needed; single base image
- default-containerfiles.ts: replaced by .openlock/Containerfile
- containers/core*.Containerfile: replaced by base.Containerfile
- policies/default-{js,py,js-py}.yaml: collapsed into default.yaml
- scripts/render-default-policies.ts: render only single default.yaml,
updated paths to /usr/local/bin/node etc.
- session.ts/session-store.ts: strip caps from runtime + persisted state;
legacy meta files with caps load silently (caps dropped on read).
- integration tests: switch from DEFAULT_CONTAINERFILES + containerfileKeyForCaps
to BASE_CONTAINERFILE + ensureImage; harness-binary test now layers
claude_code via seedContainerfile + ensureSandbox.
Refs bd openlock-8op.
CI invokes this to compute the canonical ghcr tag from the embedded base.Containerfile, avoiding reimplementation of computeBaseTag in shell. Refs bd openlock-8op.
Rewrites .openlock/Containerfile FROM line to the current embedded base hash, preserving the harness block via sentinel matching. Top-level command (not sandbox subcommand) because the existing sandbox command doesn't support sub-routing. Refs bd openlock-8op.
Default mode removes stale openlock-sandbox tags (not referenced by an active container) + non-current ghcr.io/vessux/openlock-base tags. With --legacy, removes pre-M5 openlock-core* images. In-use detection via podman/docker ps (no session-store coupling). Refs bd openlock-8op.
The ln -sf /usr/bin/claude /usr/local/bin/claude lines were inherited from the old NodeSource-deb base (npm prefix /usr → binaries at /usr/bin). M5's tarball-slim node uses /usr/local prefix, so npm itself creates the correct /usr/local/bin/claude symlink — the ln -sf was clobbering it with a dangling target. Caught by the new live integration smoke test (gated behind OPENLOCK_LIVE_INTEGRATION=1) which verifies the full pipeline: - ensureBase() builds base.Containerfile - seedContainerfile + ensureSandbox produces working sandbox image - claude binary resolves at /usr/local/bin/claude Refs bd openlock-8op.
Refs bd openlock-8op.
This PR is the base for an eventual v0.9.0 but isn't the release itself. More features (lvc.1 init wizard, lvc.2 validate, lvc.3 doctor, awo ghcr push) will accumulate under 'Unreleased' before the version bump.
Moving to a release-time-only changelog pattern: feature PRs don't touch CHANGELOG.md; the release PR aggregates merged PRs since the last tag into a single curated entry. Avoids mid-cycle changelog churn and the conflict risk of an "Unreleased" section without losing curation.
Three issues found by CI on PR #47: - containers/base.Containerfile: add `mkdir -p /sandbox/repo` after the user switch. Old core.Containerfile had this; dropped during T1 review as a deferred concern, but post-create-exec-proxy.test.ts fails with "cd: /sandbox/repo: No such file or directory" when the bind-mount target doesn't pre-create the dir. - src/cli/update-base.ts: TOCTOU race flagged by CodeQL (high). Drop existsSync + readFileSync pair; use try/catch on readFileSync with ENOENT check. Same fix pattern as project_handover_2026_05_11.md. - tests/integration/slim-images.test.ts: was hardcoded to `podman run`, failed on the docker CI matrix (exit 125). Use resolveRuntime() so the test matches the runtime that built the image. Refs bd openlock-8op, PR #47.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Foundation work toward an eventual v0.9.0. Replaces the 4-cap image matrix (
openlock-core{,-js,-py,-js-py}) with a singleghcr.io/vessux/openlock-baseimage + per-project editable.openlock/Containerfile. Version stays at v0.8.0; changelog accumulates under "Unreleased" until release.Highlights
.openlock/Containerfileis the new source of truth — seeded by openlock, editable by useropenlock update-base,openlock prune-images [--legacy],openlock --print-base-tagBreaking changes (deferred until v0.9.0 ships)
capsfield in.openlock/config.yaml: warn-and-ignore (validate --fixcleanup arrives with lvc.2)openlock prune-images --legacyreclaims diskTest plan
bun test)bun run typecheck)bun run lint && bun run knip)OPENLOCK_LIVE_INTEGRATION=1 bun test tests/integration/slim-images.test.ts— 2/2 passing on Mac/podman (base + sandbox with claude_code).openlock/(config + policy only), runopenlock sandbox, verify Containerfile seededupdate-baseround-trip preserves a custom RUN in the harness sectionOut of scope (separate PRs, build toward v0.9.0 release)
lvc.1— interactiveopenlock initwizard (usesseedContainerfile())lvc.2—openlock validate --fix(removes deprecatedcapsfield)lvc.3—openlock doctorchecks for base drift + stale image inventoryawo— CI workflow pushingghcr.io/vessux/openlock-base:<hash>(multi-arch buildx)pi,codex,gemini-cli) — extendHARNESS_FRAGMENTSinseed-containerfile.tsSpec:
docs/superpowers/specs/2026-05-29-slim-images-design.md(gitignored, local-only)Plan:
docs/superpowers/plans/2026-05-29-slim-images.md(gitignored, local-only)