Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions services/core/internal/sandbox/e2b/installed_paths_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"testing"

"github.com/MiniMax-AI/OpenAgentCore/services/core/internal/sandbox"
"github.com/google/uuid"
)

func TestInstalledPathsRejectMissingAndRelativeRoots(t *testing.T) {
Expand Down Expand Up @@ -43,3 +44,26 @@ func TestConfigurationDiscoveryUsesSuppliedProcessPaths(t *testing.T) {
t.Fatalf("missing roots: %v", err)
}
}

func TestBuildDirectCreatesItsPrivateStateDirectory(t *testing.T) {
paths := sandbox.ProcessPaths{ArtifactRoot: t.TempDir(), StateRoot: t.TempDir()}
binary, state, err := InstalledPaths(paths)
if err != nil {
t.Fatal(err)
}
if err := os.MkdirAll(filepath.Dir(binary), 0700); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(binary, []byte("#!/bin/sh\nexit 1\n"), 0700); err != nil {
t.Fatal(err)
}
selection := sandbox.Selection{Provider: "e2b", Configuration: &DeploymentConfiguration{APIKey: "synthetic-key", Template: "test:" + uuid.NewString()}}
for range 2 {
if _, err := BuildDirect(sandbox.DirectConfig{ProcessPaths: paths, InstallationID: uuid.NewString(), Selection: selection}); err != nil {
t.Fatal(err)
}
}
if info, err := os.Stat(state); err != nil || info.Mode().Perm() != 0700 {
t.Fatalf("state directory: %v %v", info, err)
}
}
4 changes: 4 additions & 0 deletions services/core/internal/sandbox/e2b/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ func newDirect(c sandbox.DirectConfig) (*Provider, error) {
if err != nil {
return nil, err
}
// The adapter owns its private directory under the state root.
if err := os.Mkdir(state, 0o700); err != nil && !errors.Is(err, os.ErrExist) {
return nil, errors.New("E2B provider cannot create its private state directory")
}
// Only a candidate that omitted its resources has none; its validation
// reads them from the template build before the candidate is rebuilt.
var resources *sandbox.Resources
Expand Down
4 changes: 2 additions & 2 deletions services/core/tools/e2b-provider/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The account key is stored encrypted in Core's database and is write-only. It rea
| Helper operation | Core use | Behavior |
| --- | --- | --- |
| `create` | `Create` | Creates the sandbox once and runs `managed_init.py`; see [Create](#create) |
| `inspect` | `GetInfo` | Reads the sandbox by recorded ID, or by ownership metadata when no ID is recorded, and checks ownership, domain, template and resources |
| `inspect` | `GetInfo` | Reads the sandbox by recorded ID, or by ownership metadata when no ID is recorded, and checks ownership, domain (an omitted domain is the configured one), template and resources |
| `renew` | `Renew` | Extends the lease of the running sandbox to the configured timeout, then rereads it |
| `kill` | `Kill` | Destroys every matching sandbox and confirms that none remains |
| `command` | `RunCommand` | Runs one bounded command as the Runtime user on a running sandbox whose bootstrap completed; output is limited to 1 MiB per stream |
Expand All @@ -40,7 +40,7 @@ The request and credentials arrive on standard input; standard output carries on

## Receipts and state directory

The state directory is the fixed `/state/e2b` ([private Provider state](../../../../docs/configuration.md#compose-installations)). It must be owned by the helper's user and grant no group or other access, and it lives on durable private storage across Core restarts. Its receipts hold SDK connection material, ownership identities and one-shot creation claims; they are not Core execution state. Losing the directory cannot authorize recreation or successful cleanup; never delete receipts after an uncertain call.
The state directory is the fixed `/state/e2b` ([private Provider state](../../../../docs/configuration.md#compose-installations)), which Core creates when it loads the Provider. It must be owned by the helper's user and grant no group or other access, and it lives on durable private storage across Core restarts. Its receipts hold SDK connection material, ownership identities and one-shot creation claims; they are not Core execution state. Losing the directory cannot authorize recreation or successful cleanup; never delete receipts after an uncertain call.

A helper holds its allocation's lock until the SDK operation returns, even after Core's caller times out. Core tracks the helper's actual exit, and a credential change waits for running helpers without killing them. Helper exit never proves that a remote Create settled. Core serializes lifecycle requests per allocation and never replays Create.

Expand Down
4 changes: 3 additions & 1 deletion services/core/tools/e2b-provider/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,9 @@ def owns(self, cloud):

def check_domain(self, cloud):
domain = self.options()['domain']
sandbox_domain = cloud.sandbox_domain
# The API may omit the domain of a sandbox on the configured one: SDK
# Sandbox objects then fill in that domain, SandboxInfo leaves None.
sandbox_domain = domain if cloud.sandbox_domain is None else cloud.sandbox_domain
if not isinstance(sandbox_domain, str) or not (
sandbox_domain == domain or sandbox_domain.endswith('.' + domain)):
raise Failure('ownership')
Expand Down
7 changes: 7 additions & 0 deletions services/core/tools/e2b-provider/provider_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,13 @@ def test_custom_endpoint_reaches_unknown_create_discovery(self):
self.assertEqual(self.api.list.call_args.kwargs['api_url'], self.config['APIURL'])
self.assertEqual(self.api.list.call_args.kwargs['domain'], self.config['Domain'])

def test_info_without_a_domain_is_on_the_configured_one(self):
self.api.get_info.return_value = SimpleNamespace(
sandbox_id='owned-id', sandbox_domain=None, state='running', metadata=self.cloud.metadata,
template_id=self.cloud.template_id, cpu_count=self.cloud.cpu_count, memory_mb=self.cloud.memory_mb)
self.assertEqual(self.call('create')['ErrorCode'], '')
self.assertEqual(self.call('renew')['ErrorCode'], '')

def test_create_refuses_foreign_data_plane_before_envd(self):
self.cloud.sandbox_domain = 'foreign.example'
self.assertEqual(self.call('create')['ErrorCode'], 'ownership')
Expand Down
Loading