Why this POC exists, what was verified, and what is left. Findings 1-11 describe the 0.2.0 kit (sbx 0.47.0 and 0.48.0); findings 12-16 cover v0.3.0 (sbx 0.49.2 and 0.50.0). reShapr 1.0.0. Paths such as reshapr/reshapr.yaml and scripts/bind.sh in the older findings no longer exist; see the README for the current layout.
| sbx | reShapr | |
|---|---|---|
| Question answered | "What can the agent reach / see?" | "What can the agent do with an API, and how much comes back?" |
| Boundary | microVM, egress allow-list, host-side credential injection | reShapr server: operation include/exclude, output filters, secrets, API-key/OAuth2, audit |
| Weak spot | Each API needs its own MCP server and allow-list entry | Needs a trustworthy client environment and a way to hold the client credential |
Combined: the agent talks to one allow-listed host:port. Backend credentials and hostnames stay on the reShapr server (host side), and the sbx proxy injects the single reShapr key, so the sandbox never holds it.
reShapr
- The reShapr server (:7777) serves MCP over HTTP at
/mcp/<org>/<exposition-name>(also/mcp/<expositionId>). - Per Configuration Plan, auth is an API key (header
x-reshapr-key), OAuth2 bearer, or none. reshapr run -r <release> [--ui]starts the official local stack (Postgres, control plane, MCP server, optional UI).reshapr import … --apiKey --auditpublishes an OpenAPI as an MCP server and prints the key once.
sbx
credentials[].apiKey{proxyManaged, inject[]}: the container holds a placeholder, the proxy swaps in the real value.- Kit capabilities used:
network-policy@1,credential@1,lifecycle@1(install hooks) andagent-context@1, plusargswithenv:. sbx secret set <service>stores the value on the host.
- Host-local services go through the sbx proxy, so key injection works.
host.docker.internalis not in the defaultNO_PROXY; the proxy rewrites the target tolocalhost:<port>. Do not copy the grafana kit'sNO_PROXYoverride: it bypasses the proxy and the injection. - Use
localhosteverywhere on the sbx side. The allow rule islocalhost:7777andinject.domainislocalhost.host.docker.internalin either place is blocked or not injected. The client config still useshttp://host.docker.internal:<port>/mcp/…. - A user-side binding is required. sbx injects only into domains the user allows
(
~/.config/sbx/credentials.yaml,reshapr→localhost, emptydiscovery). Without it:credential not sent: no binding authorizes this service.scripts/bind.shcreates it only if absent. - Store the secret before creating the sandbox.
SBX_CRED_RESHAPR_MODEis fixed at creation. Set it withsbx secret set reshapr(global); rotating applies live to running sandboxes. - Agents. Claude Code (
claude mcp add --transport http --header): Connected. Codex (http_headersinconfig.toml) and Copilot CLI (~/.copilot/mcp-config.json, jq merge): registered and listed. For all three, the MCP round trip (initialize,tools/list,tools/call) works from inside the sandbox. A live Copilot prompt also works (credential:sbx secret set copilot, withgh auth tokenpasted or via--command). The firsttools/callcan hit reShapr's 3 s backend timeout, soverifyretries it. Live Copilot prompt, 14 Oct 2026, sbx 0.48.0: with the published kitdocker.io/yadayac/reshapr-kit:0.2.0onsbx-kit-copilot:1.0.86, "give the forecast in SFO" made Copilot findreshapr-get_v1_forecastby tool search, call it, and answer from the reShapr server data (key injected by the proxy, never in the sandbox). - Negative tests. No or wrong key gives 401. Direct access from the sandbox to the backend is blocked (403 under deny-all) while the same API works through the reShapr server.
- Checks done:
make verifyfor the three agents on the v3 kit (sbx 0.48.0). TCK on the v3 kit:kit-tck3.0.0-m.8 (contribscripts/test-kit.sh) conforms, 19/19, on the local kit and on the publisheddocker.io/yadayac/reshapr-kit:0.2.0(republished multi-platform amd64+arm64 with provenance). On the v2 kit:sbx kit validate,TestKitTCKand the e2e suite. - v3 migration (done on branch
v3, merged intomain; v2 is taggedv0.1.0-v2). Answers to the open questions:localhost:7777(with the port) is accepted in the allow rule; hooks see${{ kit.args.* }}; the credential works asoptional: true.- No image recipe is needed:
agent-contextuses inlinecontent, and args are exported withenv:(RESHAPR_PORT,RESHAPR_EXPOSITION). The v2RESHAPR_MCP_URLvariable is gone. - Hook environments are deny-by-default: each hook declares
env: [RESHAPR_API_KEY]. The credential spans[install, runtime], so the allow rule is in both phases. sbx kit validaterejects v3 source kits, and the host build fails on Docker Desktop's classic image store.SBX_KIT_BUILDER=sandbox sbx kit inspect ./reshaprworks.- A v3 mixin needs a v3 workload. The built-in
claudeandcodexnames failed with "no workload kit in the set" (cause not confirmed). The publisheddocker.io/docker/sbx-kit-{claude,codex,copilot}:<version>workloads compose fine and are pinned inscripts/common.sh. - Building the contrib
claudeworkload from source in the builder hung onclaude.ai/install.sh; not needed, since the published image works. - Related upstream issues (all closed):
docker/sbx-releases#671,#682(fixed in 0.48.0),#677(Windows, fixed onmainfor 0.49.0).
- Publishing a v3 kit (first tested with a throwaway tag, since deleted; now published as
docker.io/yadayac/reshapr-kit:0.2.0):sbx kit pushis v1/v2 only on 0.48.0 (it requiresspec.yaml).docker buildx build ./reshapr -f ./reshapr/reshapr.yaml -t <ref> --pushwith Docker Desktop's defaultdockerdriver pushes a plain image;sbx kit inspectrejects it ("no artifactType").- The same command with a
docker-containerbuilder (--builder kitpub) pushes a valid kit artifact.sbx create <workload> --kit <ref>then pulls it, runs the hooks, and Claude shows the reshapr serverConnected. - The kit directory is named
reshapr, so a local build shows the kit asreshapr. With the olderkit/name it showed askit. - Releases:
v0.2.0(spec v3, latest) andv0.1.0-v2(legacy pre-release). The published image was checked by creating a sandbox from it for Claude, Codex and Copilot (MCP registration, keyproxy-managed).
- Sandbox name and "Bad credentials" (observed on sbx 0.47.0 and 0.48.0). A Copilot sandbox named
reshapr-pockept failing withGitHub returned: Bad credentialseven with thegithubandcopilotsecrets set before creation. The samesbx createcommand under any other name (reshapr-cop,other-poc,reshapr-sbx) worked, with the published workloadsbx-kit-copilot:1.0.86and with the built-incopilot.
- The daemon log shows
secrets: credential revokedfor thatvm_id(servicegithub, andcopilotearlier), about 3,000 times since 12 Oct. For working names it showsproxy: overriding client-supplied credential with host credentialinstead, which is the proxy swapping the placeholderGH_TOKENfor the real token. - The revoked state survives
sbx rm, recreating the sandbox andsbx daemon restart. The cause and the place where it is stored are unknown.sbx resetmight clear it (untested, destructive). - Update 28 Oct 2026: on sbx 0.50.0 the same name
reshapr-pocworks (Copilot answered), so this no longer reproduces; no upstream issue was filed. - An earlier theory, that the published v3 Copilot workload does not inject the
githubtoken, was wrong: it injects fine under a fresh name. The default sandbox name is nowreshapr-sbx; setSANDBOX=<name>to use another one.
- sbx MCP gateway as an alternative (spike, sbx 0.48.0, no kit).
sbx secret set mcp:reshapr:api-key, thensbx mcp add reshapr --url http://localhost:7777/mcp/... --header 'x-reshapr-key: ${api-key}' --skip-ssrf-check, thensbx create <workload> --static-mcp reshapr.
sbx mcp lsshowsready. Claude showsmcp-gatewayConnected; Copilot and Codex also register it (Copilot is not on the docs' supported list).- A
get_v1_forecastcall over MCP from inside the sandbox returned real Open-Meteo data through reShapr. The tool list isget_v1_forecast,code-mode,mcp-exec. - The API key is absent from the sandbox filesystem. No allow rule was needed, and a direct request to
host.docker.internal:7777returned 403. - If the secret is not stored before
sbx mcp add, the add fails with an OAuth discovery error. Rotating the secret needs a sandbox restart. - Live Claude run (15 Oct 2026): works. Downside: the agent sees one
mcp-gatewayand an unprefixed tool, so it cannot addressreshaprby name. The kit's agent-context note avoids this; with the gateway, add a line to the project instructions or the prompt. - Live Copilot run (15 Oct 2026,
sbx-kit-copilot:1.0.86,gh auth token): works on both paths. Kit: Copilot foundreshapr-get_v1_forecastby tool search and answered for SFO (no timeout). Gateway:get_v1_forecast (MCP: mcp-gateway)was called directly, and an hourly follow-up ("tomorrow, hour by hour") worked too, although Copilot is not on the docs' supported-agent list. - Live Codex run (15 Oct 2026,
sbx-kit-codex:0.162.1, OpenAI API key): works on both paths, but only when the prompt says "using the reshapr tool". Kit: Codex readkits-agent-context/reshapr-kit.mdand calledreshapr.get_v1_forecast(8 s). Gateway:mcp-gateway.get_v1_forecast(6 s). Without the hint, on both paths, Codex used its built-in web search and returned different numbers; the agent-context note did not change that. - Not verified: non-macOS hosts.
-
v0.3.0: slim outside kit (28 Oct 2026, sbx 0.49.2 and 0.50.0). With the gateway doing the registration, the kit shrinks to
agent-context@1only (no hooks, credential or network rule).sbx create <workload> --static-mcp reshapr --kit <outside kit>composes. Claude and Copilot use reShapr unprompted; Codex still web-searches unless the prompt says "using the reshapr tool", whatever the wording of the note. This replaces the 0.2.0 hooks kit (breaking;v0.2.0stays published). -
v0.3.0: reShapr inside the sandbox. The sandbox has its own Docker (29.8, Compose 5.6).
registry.reshapr.ioreturns 403 untilregistry.reshapr.io,quay.ioandcdn01.quay.io(blob CDN) are allowed. The stack starts in about 30 s and uses about 1 GB RAM;npm i -g @reshapr/reshapr-cli --prefix ~/.npm-gworks. The kit keeps per-agent registration hooks pointing atlocalhost:7777(the sbx gateway is host-side and cannot reach the sandbox's localhost) and needs no key.- Without
long-running@1the sandbox auto-stops with no client, and after a reboot the server starts before the control plane and never recovers. The startup hook therefore restartsreshapr-gateway-01when:7777is unhealthy. - Postgres is not persisted across stop/start, so each boot re-imports the API (skipped if the exposition exists). Startup takes about a minute.
- Live runs: Claude, Codex and Copilot all called the in-sandbox server; Claude also after
sbx stopand restart.
- Without
-
v0.3.0: validation.
make verifypasses 6/6 (3 agents × outside/inside) on sbx 0.49.2 and 0.50.0. kit-tck 3.0.0-m.8: 19/19 on both kits, local and on the published refs. -
v0.3.0: publish and sign. Both kits are published with buildx on a
docker-containerbuilder (linux/amd64,linux/arm64, provenance) asdocker.io/yadayac/reshapr-outside-sbx:0.3.0anddocker.io/yadayac/reshapr-inside-sbx:0.3.0, and signed keyless withsbx kit sign(identityyacine@microcks.io, issuerhttps://github.com/login/oauth).sbx kit verifypasses for both. -
e2e on v3 source kits is blocked on sbx 0.48.0 and 0.50.0. The scoped daemon (
--app-name sbx-kits-contrib-tck) cannot see thesbx-kit-src:*images built in the builder sandbox (403 pull failedorimage is not cached locally). Not a kit problem; reported as docker/sbx-kits-contrib#446. -
Revalidation on sbx 0.51.0 (7 Nov 2026).
make verifypasses in both modes for Claude 2.1.314 and Codex 0.166.0 (also on the older pins 2.1.296 and 0.162.1), and for the built-incopilot(CLI 1.0.86).docker.io/docker/sbx-kit-copilotno longer exists on Docker Hub (401 on pull, "object not found" in the Hub API), soscripts/common.shusesBASE=copilot. The built-inclaudeandcodexdo not work as a base for the inside kit:docker.io/sbx/claudeanddocker.io/sbx/codexhave no npm (Install the reShapr CLIexits 127).kit-tck3.0.0-m.11: 19/19 on both published kits.
- Mixin kit, minimal egress (only the reShapr server port); the install hooks only write agent config, nothing is downloaded.
- Registration is guarded per agent binary and idempotent (
|| trueforclaude mcp add, grep guard for Codex, jq merge for Copilot). - One exposition per kit instance: the key belongs to a Configuration Plan and the credential service name is fixed.
- Allowing
localhost:7777also matches any other local service on that port: the port is the trust boundary. - The kit is kept next to the gateway path (finding 11): it gives one
--kitcommand and the agent-context text, which the gateway does not. - API-key auth only. OAuth2 would need a host-side token refresher (JWTs expire) and an IdP; deferred.
| Risk | Mitigation |
|---|---|
| Key leaks into agent context or config | Placeholder only; configs reference $RESHAPR_API_KEY; the real key never enters the sandbox |
| Agent reaches the control plane (:5555) | The kit allows only the reShapr server port |
| The reShapr endpoint on the host is not isolated | reShapr include/exclude operations, --audit, per-sandbox keys via key renewal |
Dev-only defaults (admin/password, sample keys) |
Local use only; never reuse |
- e2e suite on the v2 kit, sbx 0.47.0 (
TestE2EKit,scripts/test-kit-e2e.sh <kit-dir>in sbx-kits-contrib): passed on macOS (arm64) with the kit's default args (notestdata/tck.yamlneeded, no/dev/kvmneeded). Subtestsenv,tmpfsandagentContextpass. It needs a one-timesbx --app-name sbx-kits-contrib-tck login, which can be rate-limited by Docker's auth service. The log notes that no binding authorizesreshapr, which is expected: the user-side binding is created byscripts/bind.sh. - Live prompt for Claude (15 Oct 2026, sbx 0.48.0, Claude Code 2.1.300, API key, published kit
0.2.0onsbx-kit-claude): Claude calledreshaprand answered with the Paris forecast. The first call hit the 3 s backend timeout and Claude retried with fewer fields. - Live prompt for Codex (15 Oct 2026,
sbx secret set openai, kit and gateway): passes with "using the reshapr tool"; unprompted it uses web search. Possible follow-up: stronger agent-context wording (needs a new kit version). - Live prompt through the sbx MCP gateway with Claude (15 Oct 2026):
mcp-gateway - get_v1_forecastreturned the Paris forecast on the first call. Asking Claude to "use the reshapr tool" failed (Tool "reshapr" not found in gateway), because the gateway hides the server name (finding 11). - Keep both paths? Decided in v0.3.0: the outside kit is the gateway plus agent-context (finding 12), and inside is a separate kit (finding 13).
- Kit and gateway together in one sandbox, custom
--kit-argvalues and a Claude subscription login: tested in v0.3.0 (--static-mcp reshaprwith the outside kit; inside with the Petstore API; inside with a Claude/loginand no API-key secret, 28 Oct 2026: Claude calledreshaprand answered). The first session, opened 4 minutes after creation, web-searched because the import had not finished; a new session worked. Still untested: non-macOS hosts. - TCK on the v3 kit: passed (see finding 7).
- e2e suite on the v3 kits: blocked on sbx 0.48.0 and 0.50.0, see finding 16 and #446. Original notes (contrib
v3scripts/test-kit-e2e.sh). Not a kit failure; the policy log underdeny-allwas empty. Three attempts:- default
./claudebase: the built-inclaudeagent already holds the name, and the renamed copy fails to build on the default builder; - with
SBX_KIT_BUILDER=sandbox: the build works, but the scoped daemon fails with403 pull failed for image "sbx-kit-src:claude-e2e-…"; E2E_HOST=claude(built-in):no workload kit in the set. Retried on sbx v0.50.0: same failure (andimage is not cached locallywith a published workload asE2E_HOST).
- default
-
make downandmake upwere run repeatedly during v0.3.0 validation; a truly clean machine is still untested. - Upstream issue about a revoked credential tied to a sandbox name (finding 10): not filed. On sbx 0.50.0 (28 Oct 2026) a Copilot sandbox named
reshapr-pocworks withgithubandcopilotset, so it no longer reproduces. - Isolated sbx daemon mode (
SBX_APP): dropped from the README. It needs an interactive login and e2e stays blocked (finding 16). - Deferred: OAuth2, several expositions, remote reShapr server, a "builder" kit (reShapr CLI + the
reshapr-builderskill, no Docker in the sandbox, low-privilege control-plane credential), Code Mode (reShapr 1.1 roadmap), registering reShapr behind sbx's own MCP gateway. - Deferred, watch docker/sandbox-kit-spec#118 (open): the
mcp-server@1capability declares an MCP server that runs inside a sandbox and lets sbx serve it to other sandboxes' MCP gateways. It doesn't help this POC (reShapr runs on the host and the kit only registers it). It could help a "reShapr in a sandbox" kit, which could drop the per-agent registration hooks. Revisit once it merges and ships in an sbx release. - Upstream: propose the kit to sbx-kits-contrib; a "Use with Docker Sandboxes" page in the reShapr docs.