Conversation
|
@breardon2011 must be a member of the diggerhq team on Vercel to deploy. Learn more about collaboration on Vercel and other options here. |
motatoes
approved these changes
Jan 30, 2026
motatoes
added a commit
that referenced
this pull request
Apr 8, 2026
Two interacting bugs in Sandbox.create({ envs }) that produced very
confusing behavior for users.
Bug #1 — snapshot fork dropped user envs.
createFromCheckpointCore re-bound only { Timeout int } from the request
body and forwarded originalCfg.Envs from cp.SandboxConfig, so calling
Sandbox.create({ snapshot, envs: { FOO: "x" } }) produced an empty $FOO
inside the guest. Fix: thread user envs through to the core via a
narrow userEnvs map[string]string parameter and merge them over
originalCfg.Envs (user keys win) after re-resolving the inherited
secret store. Scope is intentionally limited to envs — every other
field still inherits from the checkpoint.
Bug #2 — every env was sealed unconditionally.
secretsproxy.CreateSealedEnvs tokenized every entry of cfg.Envs, so
even user-supplied plaintext envs reached the guest as osb_sealed_…
tokens. echo $TEST_VAR returned the token, breaking every non-HTTP
use of the variable. Sealing is only meaningful for values sourced
from a SecretStore (so the MITM proxy can swap them on outbound
HTTPS). Fix: track which env names came from the store via a new
SealedEnvKeys []string on types.SandboxConfig (json:"-", never
persisted), populate it from resolveSecretStoreInto on both the
fresh-create and fork paths, plumb it through CreateSandboxRequest
(new field sealed_env_keys = 15) and the worker gRPC server, and
have CreateSealedEnvs only tokenize keys in that set. Non-sealed
envs pass through as plaintext; the proxy session is only registered
when there is something to substitute. On the fork path the seal-set
is computed before merging user envs so user keys are never sealed.
Worker deploy
The Azure dev box silently shipped without an OPENSANDBOX_S3_*
checkpoint store, which made every snapshot/fork RPC fail with
"checkpoint store not configured on this worker" — there was no
clear pointer at the missing config. Wire the worker to Azure Blob
via the existing OPENSANDBOX_S3_* env vars (the worker switches to
azureBlobClient when the endpoint contains .blob.core.windows.net,
see internal/storage/blob.go:39). Secrets are sourced from a
gitignored deploy/azure/.dev-env-secrets-<location> file and the
deploy now fails fast with a clear error if the checkpoint store
config isn't present.
Test coverage
New sdks/typescript/examples/test-snapshot-envs.ts asserts:
- plain Sandbox.create({ envs }) → guest sees plaintext
- Sandbox.create({ snapshot, envs }) → user envs survive the fork
AND remain plaintext
- Sandbox.create({ secretStore, envs }) → user envs are plaintext
while store-derived envs are still sealed (osb_sealed_…)
Registered in run-all-tests.ts so it runs in the production suite.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4 tasks
motatoes
added a commit
that referenced
this pull request
Apr 8, 2026
…113) * Fix Sandbox.create envs on snapshot/fork and stop sealing user envs Two interacting bugs in Sandbox.create({ envs }) that produced very confusing behavior for users. Bug #1 — snapshot fork dropped user envs. createFromCheckpointCore re-bound only { Timeout int } from the request body and forwarded originalCfg.Envs from cp.SandboxConfig, so calling Sandbox.create({ snapshot, envs: { FOO: "x" } }) produced an empty $FOO inside the guest. Fix: thread user envs through to the core via a narrow userEnvs map[string]string parameter and merge them over originalCfg.Envs (user keys win) after re-resolving the inherited secret store. Scope is intentionally limited to envs — every other field still inherits from the checkpoint. Bug #2 — every env was sealed unconditionally. secretsproxy.CreateSealedEnvs tokenized every entry of cfg.Envs, so even user-supplied plaintext envs reached the guest as osb_sealed_… tokens. echo $TEST_VAR returned the token, breaking every non-HTTP use of the variable. Sealing is only meaningful for values sourced from a SecretStore (so the MITM proxy can swap them on outbound HTTPS). Fix: track which env names came from the store via a new SealedEnvKeys []string on types.SandboxConfig (json:"-", never persisted), populate it from resolveSecretStoreInto on both the fresh-create and fork paths, plumb it through CreateSandboxRequest (new field sealed_env_keys = 15) and the worker gRPC server, and have CreateSealedEnvs only tokenize keys in that set. Non-sealed envs pass through as plaintext; the proxy session is only registered when there is something to substitute. On the fork path the seal-set is computed before merging user envs so user keys are never sealed. Worker deploy The Azure dev box silently shipped without an OPENSANDBOX_S3_* checkpoint store, which made every snapshot/fork RPC fail with "checkpoint store not configured on this worker" — there was no clear pointer at the missing config. Wire the worker to Azure Blob via the existing OPENSANDBOX_S3_* env vars (the worker switches to azureBlobClient when the endpoint contains .blob.core.windows.net, see internal/storage/blob.go:39). Secrets are sourced from a gitignored deploy/azure/.dev-env-secrets-<location> file and the deploy now fails fast with a clear error if the checkpoint store config isn't present. Test coverage New sdks/typescript/examples/test-snapshot-envs.ts asserts: - plain Sandbox.create({ envs }) → guest sees plaintext - Sandbox.create({ snapshot, envs }) → user envs survive the fork AND remain plaintext - Sandbox.create({ secretStore, envs }) → user envs are plaintext while store-derived envs are still sealed (osb_sealed_…) Registered in run-all-tests.ts so it runs in the production suite. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Refactor: carry secret-store envs in their own field (SecretEnvs) Replace the SealedEnvKeys side-channel introduced in the previous commit with a real provenance-preserving field. Three bug-classes collapse into "structurally impossible" instead of "fixed by careful threading". Why --- The previous fix added a parallel []string of env-var names that the API layer computed, the proto carried, and the worker re-hydrated into a set, all to tell the secrets proxy "tokenize these keys, not those". That worked but kept the underlying mistake intact: secret-store-derived plaintext was still inlined into cfg.Envs alongside user envs, and the provenance had to be reconstructed from a side-channel everywhere downstream wanted it. As soon as that channel desynced from the values themselves (as it did on the snapshot/fork path) you got either silent drops or silent plaintext leaks. What ---- New types.SandboxConfig.SecretEnvs map[string]string (json:"-"). resolveSecretStoreInto now writes decrypted values into SecretEnvs and never touches Envs. The two maps remain disjoint end-to-end: through cfgForPersistence (only SecretAllowedHosts needs scrubbing now — SecretEnvs can never reach PG since it's json-tagged out), through the gRPC proto (CreateSandboxRequest.sealed_env_keys = 15 becomes secret_envs = 15, a real map), through the worker, and into secretsproxy.CreateSealedEnvs which now takes (plaintextEnvs, secretEnvs) directly. Everything in secretEnvs is tokenized; everything in plaintextEnvs is forwarded as-is; user-supplied keys win on collision. createFromCheckpointCore no longer needs the "compute seal-set BEFORE merging user envs" ordering trick, because the maps are independent — the merge order is irrelevant. The "secretStore + snapshot/image" combination is now rejected at the API edge with a clear 400. The pre-existing inherit-only contract ("a fork inherits the snapshot's secret store and cannot override it") was previously enforced implicitly by "the fork pipeline doesn't bind SecretStore from the body", which silently dropped a user-provided store on fork. The first fix in this PR turned that silent drop into a silent leak (parent-resolved store-B plaintext smuggled through cfg.Envs into the fork-time merge under names that weren't in the seal-set). With the rejection in place users get an explicit error instead, and even if they could bypass it the leak is structurally impossible because secret values no longer travel via cfg.Envs. Test coverage ------------- test-snapshot-envs.ts grew a 4th case asserting the rejection. All 10 assertions pass on the redeployed dev box. test-secretstore.ts (the existing 21-assertion lifecycle suite) also passes unchanged against the refactored worker, confirming the user-facing SecretStore behavior is preserved. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8 tasks
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.
Code agent example