@capybara-hq/plugin-sandbox-e2b 0.0.0-stage → 0.1.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.capybara-plugin/plugin.json +159 -0
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +172 -2
- package/dist/index.mjs +806 -0
- package/package.json +19 -4
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"name": "sandbox-e2b",
|
|
4
|
+
"version": "0.1.0-rc.1",
|
|
5
|
+
"publisher": "Capybara",
|
|
6
|
+
"description": "Conversation sandboxes (sandbox@1) as E2B sandboxes (E2B cloud or self-hosted), with pause / resume and E2B egress controls",
|
|
7
|
+
"displayName": {
|
|
8
|
+
"zh": "E2B 沙箱",
|
|
9
|
+
"en": "E2B sandbox"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/capybara-hq/plugins/tree/main/plugins/sandbox-e2b#readme",
|
|
12
|
+
"extension": {
|
|
13
|
+
"entry": "./dist/index.mjs",
|
|
14
|
+
"manifest": {
|
|
15
|
+
"name": "sandbox-e2b",
|
|
16
|
+
"version": "0.1.0-rc.1",
|
|
17
|
+
"sdk": "^0.1.0-rc.0",
|
|
18
|
+
"provides": [
|
|
19
|
+
"sandbox@1"
|
|
20
|
+
],
|
|
21
|
+
"requires": [],
|
|
22
|
+
"optional": [],
|
|
23
|
+
"permissions": {},
|
|
24
|
+
"config": {
|
|
25
|
+
"deployment": [
|
|
26
|
+
{
|
|
27
|
+
"path": "allowOpenEgress",
|
|
28
|
+
"secret": false,
|
|
29
|
+
"hot": false
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"path": "apiKey",
|
|
33
|
+
"secret": true,
|
|
34
|
+
"hot": false
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"path": "apiUrl",
|
|
38
|
+
"secret": false,
|
|
39
|
+
"hot": false
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"path": "domain",
|
|
43
|
+
"secret": false,
|
|
44
|
+
"hot": false
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"path": "domainAllowlist",
|
|
48
|
+
"secret": false,
|
|
49
|
+
"hot": false
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"path": "template",
|
|
53
|
+
"secret": false,
|
|
54
|
+
"hot": false
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"path": "timeoutSeconds",
|
|
58
|
+
"secret": false,
|
|
59
|
+
"hot": false
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
"org": []
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"configSchemas": {
|
|
66
|
+
"deployment": {
|
|
67
|
+
"type": "object",
|
|
68
|
+
"properties": {
|
|
69
|
+
"apiKey": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"minLength": 1,
|
|
72
|
+
"x-secret": true,
|
|
73
|
+
"x-ui": {
|
|
74
|
+
"label": {
|
|
75
|
+
"zh": "API 密钥",
|
|
76
|
+
"en": "API key"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"title": "API key"
|
|
80
|
+
},
|
|
81
|
+
"apiUrl": {
|
|
82
|
+
"type": "string",
|
|
83
|
+
"format": "uri",
|
|
84
|
+
"x-ui": {
|
|
85
|
+
"label": {
|
|
86
|
+
"zh": "接口地址",
|
|
87
|
+
"en": "API URL"
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"title": "API URL"
|
|
91
|
+
},
|
|
92
|
+
"domain": {
|
|
93
|
+
"default": "e2b.app",
|
|
94
|
+
"type": "string",
|
|
95
|
+
"pattern": "^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$",
|
|
96
|
+
"x-ui": {
|
|
97
|
+
"label": {
|
|
98
|
+
"zh": "沙箱域名",
|
|
99
|
+
"en": "Sandbox domain"
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"title": "Sandbox domain"
|
|
103
|
+
},
|
|
104
|
+
"template": {
|
|
105
|
+
"type": "string",
|
|
106
|
+
"minLength": 1,
|
|
107
|
+
"x-ui": {
|
|
108
|
+
"label": {
|
|
109
|
+
"zh": "模板",
|
|
110
|
+
"en": "Template"
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
"title": "Template"
|
|
114
|
+
},
|
|
115
|
+
"timeoutSeconds": {
|
|
116
|
+
"default": 3600,
|
|
117
|
+
"type": "integer",
|
|
118
|
+
"minimum": 60,
|
|
119
|
+
"maximum": 86400,
|
|
120
|
+
"x-ui": {
|
|
121
|
+
"label": {
|
|
122
|
+
"zh": "沙箱超时",
|
|
123
|
+
"en": "Sandbox timeout"
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"title": "Sandbox timeout"
|
|
127
|
+
},
|
|
128
|
+
"domainAllowlist": {
|
|
129
|
+
"default": true,
|
|
130
|
+
"type": "boolean",
|
|
131
|
+
"x-ui": {
|
|
132
|
+
"label": {
|
|
133
|
+
"zh": "按域名出站白名单",
|
|
134
|
+
"en": "Domain egress allowlist"
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
"title": "Domain egress allowlist"
|
|
138
|
+
},
|
|
139
|
+
"allowOpenEgress": {
|
|
140
|
+
"default": false,
|
|
141
|
+
"type": "boolean",
|
|
142
|
+
"x-ui": {
|
|
143
|
+
"label": {
|
|
144
|
+
"zh": "允许开放出站",
|
|
145
|
+
"en": "Allow open egress"
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
"title": "Allow open egress"
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
"required": [
|
|
152
|
+
"apiKey",
|
|
153
|
+
"template"
|
|
154
|
+
],
|
|
155
|
+
"additionalProperties": false
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
package/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
package/NOTICE
ADDED
package/README.md
CHANGED
|
@@ -1,3 +1,173 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @capybara-hq/plugin-sandbox-e2b
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The E2B implementation of the conversation sandbox (`sandbox@1`): one E2B sandbox per conversation (E2B cloud or self-hosted E2B), using E2B's pause for hibernation
|
|
4
|
+
and connect for resume. It is an either-or choice with `@capybara-hq/plugin-sandbox-docker` (`sandbox@1` is a single-select capability), and uses the same image.
|
|
5
|
+
|
|
6
|
+
## Deployment config
|
|
7
|
+
|
|
8
|
+
| Key | Default | Description |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| `apiKey` | (required, secret) | E2B API key (`e2b_…`) |
|
|
11
|
+
| `template` | (required) | Template name or id, built from `plugins/sandbox-docker/image/Dockerfile` (see below) |
|
|
12
|
+
| `apiUrl` | `https://api.<domain>` | Control plane API address; for self-hosted E2B, use your own address |
|
|
13
|
+
| `domain` | `e2b.app` | Sandbox domain: the envd address is `https://49983-<sandbox id>.<domain>`; when the API returns the sandbox's own `domain`, the returned one wins |
|
|
14
|
+
| `timeoutSeconds` | `3600` | Sandbox timeout (60–86400 seconds); when it expires E2B **auto-pauses** (does not delete), see "Lifecycle" |
|
|
15
|
+
| `domainAllowlist` | `true` | Whether E2B supports a per-domain egress allowlist; set `false` for self-hosted versions that do not, and `allowlist` degrades to `none` with a warning |
|
|
16
|
+
| `allowOpenEgress` | `false` | Allow `sandbox.limits.egress = "open"`; when false, `create` fails immediately on open (an English error) |
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
export CAPYBARA_EXTENSIONS='["@capybara-hq/plugin-sandbox-e2b"]'
|
|
20
|
+
export CAPYBARA_EXTENSION_CONFIG='{"sandbox-e2b":{"apiKey":"e2b_…","template":"capybara-sandbox"}}'
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The deployment config schema is strict: extra keys are errors. The kernel config `sandbox.image` has no effect on this plugin (the image is decided by the template).
|
|
24
|
+
|
|
25
|
+
## Template (the same image as the Docker plugin)
|
|
26
|
+
|
|
27
|
+
The template is built from `plugins/sandbox-docker/image/Dockerfile` (E2B's Template SDK parses the Dockerfile's
|
|
28
|
+
FROM / RUN / COPY / ADD / WORKDIR / USER / ENV / ARG / CMD / ENTRYPOINT). From the repository root:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
export E2B_API_KEY=e2b_… # for self-hosted, also set E2B_DOMAIN / E2B_API_URL
|
|
32
|
+
mkdir -p /tmp/e2b-build && cd /tmp/e2b-build && npm init -y >/dev/null && npm i e2b@2.58.0
|
|
33
|
+
cat > build.mjs <<'EOF'
|
|
34
|
+
import { readFileSync } from "node:fs";
|
|
35
|
+
import { defaultBuildLogger, Template } from "e2b";
|
|
36
|
+
const dockerfile = readFileSync(process.argv[2], "utf8");
|
|
37
|
+
await Template.build(Template().fromDockerfile(dockerfile), "capybara-sandbox", {
|
|
38
|
+
cpuCount: 1, // matches the kernel's sandbox.cpu default
|
|
39
|
+
memoryMB: 2048, // matches the kernel's sandbox.memoryMb default
|
|
40
|
+
onBuildLogs: defaultBuildLogger(),
|
|
41
|
+
});
|
|
42
|
+
EOF
|
|
43
|
+
node build.mjs <repo path>/plugins/sandbox-docker/image/Dockerfile
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then set `template` to `capybara-sandbox`. Image conventions (the plugin depends on them): the user `sandbox` (uid 1000), with HOME and the working directory at
|
|
47
|
+
`/workspace`; `/skills` owned by root (0755); `/opt/capybara` owned by root and 0700; `runuser` / `setsid` (util-linux),
|
|
48
|
+
`pkill` / `pgrep` (procps) and GNU coreutils installed. Rebuild the template after changing the Dockerfile.
|
|
49
|
+
|
|
50
|
+
## Lifecycle
|
|
51
|
+
|
|
52
|
+
| Operation | E2B call |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `create` | First lists by the metadata `capybara-sandbox-name=<name>` (`GET /v2/sandboxes?metadata=…&state=running,paused`, paginated) and deletes any old instance of the same name (the kernel may have exited before recording the handle), then `POST /v2/sandboxes` (`templateID`, `metadata`, `timeout`, `autoPause: true`, `secure: true`, egress settings); the handle id is E2B's sandbox id |
|
|
55
|
+
| `resume` | `POST /v2/sandboxes/{id}/connect` (a paused one is resumed, and a running one gets its timeout pushed back); 404 → `gone` |
|
|
56
|
+
| `suspend` | `POST /sandboxes/{id}/pause` (files and memory are both kept: background processes and `/tmp` are still there after resume; the Docker plugin's hibernation instead ends processes and clears `/tmp`); a missing one (404) or an already paused one (409) also counts as success |
|
|
57
|
+
| `killAll` | After connect (a paused one is resumed first, and the processes in memory come back with it), runs a script as the sandbox user that kills every command started through `exec` that is still running (SIGKILL by process group and session); called when the kernel takes over from a crashed previous lock holder |
|
|
58
|
+
| Concurrent use (`concurrentExec`, `killTagged`) | Declares `concurrentExec: true`: envd runs each `process.Start` independently, so parallel sub-agents of one conversation run commands and file operations in its sandbox at the same time. Each exec's control file carries the holder's tag (`/tmp/.capybara-exec-<tag>-<uuid>`, tags checked against `SANDBOX_TAG_RE`); `killTagged` runs a script as the sandbox user that kills only the commands of the given tags. Concurrent `connect` calls are idempotent (a running sandbox only gets its timeout extended); a 409 answered while another call's resume is in flight is retried briefly (5 × 500 ms; to be confirmed on real E2B, see `test/live`). Uploads use UUID names in the staging directory. When a kill cannot run, the reboot fallback (pause + cold `connect`) also ends live siblings' commands (a last resort) |
|
|
59
|
+
| `destroy` / `destroyByName` | `DELETE /sandboxes/{id}` (a missing one also counts as success); by name it first lists by metadata |
|
|
60
|
+
| `exec` / file operations | First connect (a paused one is resumed, which is the contract's "auto-start"; the envd access token is fetched. **If no token can be obtained it does not connect and sends no request without a token; it reports `SandboxGoneError` (`resume` returns `gone`), and the kernel rebuilds a sandbox with a token on that basis; a sandbox created before `secure: true` was added never has a token**; the timeout is pushed to at least "command timeout + 120 seconds", and not beyond 24 hours), then calls envd; a connect that returns an E2B-format 404 (`{code: 404, …}`) → `SandboxGoneError`; a bare 404 (a proxy, a misconfigured `apiUrl`), 401 / 5xx / network errors → ordinary errors. pause / delete also treat only an E2B-format 404 as "does not exist" |
|
|
61
|
+
|
|
62
|
+
- **Timeout**: an E2B sandbox is handled when its timeout (`timeoutSeconds`) expires; this plugin sets `autoPause: true` when creating a sandbox, so on expiry it is **paused** instead of deleted,
|
|
63
|
+
and files are not lost even when the kernel has no chance to `suspend` (for example the whole kernel is down). Every use pushes it back through connect (it only extends, never shortens).
|
|
64
|
+
Idle hibernation and expiry destruction are handled by the kernel's maintenance tasks. E2B's continuous run limit (24 hours on Pro, 1 hour on Hobby) is recounted after pause / resume.
|
|
65
|
+
- **Hobby plan**: the `timeout` of connect / create is at most 1 hour, so the deployment config `timeoutSeconds` must be ≤ 3600, and a single command can actually run at most
|
|
66
|
+
about 1 hour minus 2 minutes; however large the `Bash` `timeout` (in milliseconds) is (capped by `sandbox.limits.maxCommandSeconds`, default 7200 seconds), it will be interrupted by the pause when the sandbox expires.
|
|
67
|
+
On Hobby, set `sandbox.limits.maxCommandSeconds` to below 3480.
|
|
68
|
+
- E2B keeps a paused sandbox indefinitely until it is deleted (the kernel deletes by `destroyAfterInactiveDays`).
|
|
69
|
+
|
|
70
|
+
## exec and files
|
|
71
|
+
|
|
72
|
+
- **exec**: envd's `process.Process/Start` (a server stream of the Connect protocol), run as the `sandbox` user:
|
|
73
|
+
`setsid -w /bin/sh -c <wrapper> … timeout -s KILL <timeout + 5 seconds> /bin/bash -c <command>`; the wrapper first writes the session id to
|
|
74
|
+
`/tmp/.capybara-exec-<uuid>.pid`. The `timeout` inside the sandbox is the backstop: when a kernel replica crashes the timer on the plugin side is gone, disconnecting envd's stream does not end
|
|
75
|
+
the process, and pause then resume would bring the process back; on expiry it kills the process group the command is in.
|
|
76
|
+
On timeout or abort: a kill-processes script runs as the sandbox user, SIGKILLing all processes the command spawned by process group and session (the same as the Docker plugin),
|
|
77
|
+
then envd's `SendSignal` by pid serves as a backstop (it only ends the session's first process); it returns only after the kill finishes, and disconnects after a 3-second grace period (disconnecting itself does not
|
|
78
|
+
end the processes in the sandbox).
|
|
79
|
+
**Last resort: reboot the sandbox.** When the kill-processes script did not finish (could not start, for example process count exhausted, timeout, or unreachable), or the output stream still has not ended 3 seconds after the kill
|
|
80
|
+
(a process escaped the session and still holds the output): first pause, then `POST /v2/sandboxes/{id}/connect` `{timeout, memory: false}`
|
|
81
|
+
(that is, the SDK `e2b@2.58.0`'s `Sandbox.connect(id, { onResume: "reboot" })`) to cold-start from the disk state: all processes in memory end,
|
|
82
|
+
files such as `/workspace` are kept, writes not flushed before the pause may be lost, and whether things in `/tmp` survive depends on whether they are on disk. A warn log is written;
|
|
83
|
+
the call returns as usual with `timedOut: true` or the abort result. The SDK notes: this requires the control plane to support connect's `memory` field (E2B cloud; self-hosted must be a
|
|
84
|
+
`e2b-dev/runtime` after 2026-08-20), and an older control plane will **ignore the field** and restore the memory snapshot (processes still there), and an
|
|
85
|
+
environment without "restore only the filesystem" enabled will reject the request; in both cases the backstop is ineffective (only logged).
|
|
86
|
+
When a command is ended by a signal we did not send (envd's end event `exited: false`, such as OOM): by shell convention it returns `128 + signal number`
|
|
87
|
+
(when Go's signal name is recognized), and when it is not recognized it returns `exitCode: null, timedOut: false` (the kernel hints "the command did not finish, the result may be incomplete").
|
|
88
|
+
A timeout resolves `timedOut: true, exitCode: null`; an abort after start resolves `exitCode: null, timedOut: false` (with the output collected so far);
|
|
89
|
+
an already aborted call rejects immediately. stdout + stderr together are truncated by `maxOutputBytes` and set `truncated`; when truncating it backs up to a full
|
|
90
|
+
UTF-8 character boundary. envd sends a keepalive event every 50 seconds, so a command with no output for a long time is not disconnected by an intermediate proxy.
|
|
91
|
+
- **readFile / stat**: small `head -c` / `stat` scripts run in the sandbox as the sandbox user (permissions and symlinks are resolved as the sandbox user). A missing file → null,
|
|
92
|
+
and readFile on a directory also returns null.
|
|
93
|
+
- **writeFile**: envd's upload endpoint opens the target as root and follows symlinks, so writing to the target directly could bypass permissions with something like `/workspace/l -> /etc`.
|
|
94
|
+
So the plugin only transfers the bytes to a root-only staging directory `/opt/capybara/upload/<uuid>` (`/opt/capybara` owned by root, 0700), then runs as root
|
|
95
|
+
`runuser -u sandbox -- sh -c 'mkdir -p …; cat > target' < staging file`: root only opens the staging file, while creating the directory and writing the target are both resolved as the
|
|
96
|
+
sandbox user; the staging file is deleted whether it succeeds or not (when aborted it may remain in the root-only directory and is removed with the sandbox).
|
|
97
|
+
- **/skills is read-only**: `/skills` is owned by root (0755) and the sandbox user can only read it. `writeFile("/skills/…")` writes directly as root (`umask 022`,
|
|
98
|
+
file mode 0644); the sandbox user cannot place symlinks in `/skills`, so root's writes cannot be redirected elsewhere.
|
|
99
|
+
|
|
100
|
+
## Egress (`limits.egress`)
|
|
101
|
+
|
|
102
|
+
E2B's egress control ([docs](https://docs.e2b.dev/sandbox/internet-access), verified 2026-10-21):
|
|
103
|
+
|
|
104
|
+
| `egress` | Setting when creating the sandbox |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `none` | `allow_internet_access: false` (equivalent to `denyOut: ["0.0.0.0/0"]`) |
|
|
107
|
+
| `allowlist` | `network: { allowOut: allowHosts, denyOut: ["0.0.0.0/0"] }`; when `allowHosts` is empty it is the same as `none` |
|
|
108
|
+
| `open` | `allow_internet_access: true`; `allowOpenEgress` must be turned on explicitly |
|
|
109
|
+
|
|
110
|
+
Limitations of `allowlist` (decided by E2B's implementation, written here for assessment):
|
|
111
|
+
|
|
112
|
+
- Per-domain filtering applies only to **HTTP on port 80 (by the Host header) and TLS on port 443 (by SNI)**; other ports are filtered only by IP / CIDR,
|
|
113
|
+
and with only domains in the allowlist they are always blocked. UDP (QUIC / HTTP3) does not support per-domain filtering.
|
|
114
|
+
- When a domain allowlist is used, E2B automatically allows DNS to `8.8.8.8` (port 53), and the DNS queries themselves can be used to exfiltrate small amounts of data.
|
|
115
|
+
- **E2B looks only at the Host header / SNI, and the docs do not say it checks the destination IP**: a sandbox can connect to ports 80 / 443 of any IP and write the Host / SNI as a domain on the allowlist
|
|
116
|
+
(such as `curl --resolve pypi.org:443:<any IP> https://pypi.org`), and so **may reach any address outside the allowlist** (including the intranet and cloud
|
|
117
|
+
metadata addresses, depending on whether E2B's own network allows them). The Docker plugin's egress proxy resolves the domain and checks every destination IP; the E2B version cannot do this.
|
|
118
|
+
- Other sites on the same CDN / object storage may also be allowed along with it (the E2B docs say this too).
|
|
119
|
+
- **For self-hosted E2B, network isolation is the operator's responsibility**: intranet services and cloud metadata addresses (169.254.169.254 and so on) that the sandbox node can reach must be blocked at E2B's node /
|
|
120
|
+
network layer; this plugin cannot do it for you.
|
|
121
|
+
- If egress that "checks the destination IP after resolving" is wanted later, E2B's `network.egressProxy` can be used (SOCKS5, see SDK 2.58 `buildEgressProxyBody`):
|
|
122
|
+
it sends all sandbox egress to our own proxy (for example the same checks as the Docker plugin's egress proxy); this plugin does not use it yet.
|
|
123
|
+
- A rejected TCP connection may look "connected" inside the sandbox; you only know it was rejected from the application layer (HTTP status, TLS handshake).
|
|
124
|
+
- `*.example.com` matches subdomains (same as the Docker plugin, not including `example.com` itself).
|
|
125
|
+
- For self-hosted E2B that does not support per-domain filtering, set `domainAllowlist: false`: `allowlist` degrades to `none` with a warning (it never silently opens up).
|
|
126
|
+
- **The allowlist cannot stop data sent to allowlisted hosts** (the same as the Docker version): `git push` to github.com and publishing to npm / PyPI are within the allowlist,
|
|
127
|
+
and attachments in a conversation and data the agent has read can leak this way. Orgs that handle sensitive data should set `sandboxToolPolicy` to
|
|
128
|
+
`ask` in the org settings, and / or set the sandbox network `sandboxEgress` to `none` (org-level egress, Ruling M19: an org can replace the allowlist or cut the network, and cannot set `open`).
|
|
129
|
+
|
|
130
|
+
**Limits take effect only at creation**: `sandbox.limits.egress` / `allowHosts` are written into the create-sandbox request, and changing the config affects only sandboxes created afterwards; existing
|
|
131
|
+
sandboxes (including ones resumed after a pause) keep the egress settings from creation until they are destroyed and rebuilt.
|
|
132
|
+
|
|
133
|
+
**Command time limit**: a single command runs at most `sandbox.limits.maxCommandSeconds`, and no more than the time left in the run it belongs to (`runtime.maxRunSeconds`,
|
|
134
|
+
default 900 seconds, Ruling M20); to run long commands, raise `runtime.maxRunSeconds` to at least `maxCommandSeconds`.
|
|
135
|
+
|
|
136
|
+
## Resources (`limits.cpu` / `memoryMb` / `diskMb`)
|
|
137
|
+
|
|
138
|
+
E2B's CPU and memory are fixed **when the template is built** (`cpuCount` / `memoryMB` of `Template.build`) and cannot be specified per sandbox at creation; disk size is decided by
|
|
139
|
+
E2B's plan / template. So for `limits` cpu / memoryMb / diskMb this plugin only validates them and **does not apply them per sandbox**: give the spec you want when
|
|
140
|
+
building the template (the example above matches the kernel defaults of 1 core / 2048 MB), and use different templates for different specs. The sandbox CPU / memory overrides an enterprise admin sets for an org in the admin console
|
|
141
|
+
(deployment setting `sandboxLimitOverrides`) likewise **have no effect on this plugin**: to give some orgs a bigger spec, the only way is to switch to a different deployment
|
|
142
|
+
(or template).
|
|
143
|
+
|
|
144
|
+
## Client
|
|
145
|
+
|
|
146
|
+
It does not use the official `e2b` SDK and instead has a minimal hand-written client (`src/e2b-api.ts`, the global `fetch`, no third-party dependencies):
|
|
147
|
+
|
|
148
|
+
- Every request carries the caller's AbortSignal directly, so abort semantics are controllable (the command stream is disconnected by the plugin itself: kill the process first, then disconnect);
|
|
149
|
+
- The official SDK reads process environment variables such as `E2B_API_KEY` / `E2B_DOMAIN` / `E2B_SANDBOX_URL` / `E2B_DEBUG` and caches a connection pool at module level
|
|
150
|
+
(the multi-replica discipline does not allow module-level mutable state in a plugin), and it also brings undici, Connect, protobuf and a string of other dependencies;
|
|
151
|
+
- It uses only 6 control plane endpoints and 3 envd endpoints, with the protocol following the SDK `e2b@2.58.0` implementation (listed in the header comment of `src/e2b-api.ts`).
|
|
152
|
+
|
|
153
|
+
Protocol sources (verified 2026-10-21): `dist/index.mjs` of `e2b@2.58.0`; the E2B docs
|
|
154
|
+
[internet-access](https://docs.e2b.dev/sandbox/internet-access), [persistence](https://docs.e2b.dev/sandbox/persistence),
|
|
155
|
+
[sandbox lifecycle](https://docs.e2b.dev/sandbox), [template quickstart](https://docs.e2b.dev/template/quickstart),
|
|
156
|
+
[template migration (fromDockerfile)](https://docs.e2b.dev/template/migration-v2); the envd source
|
|
157
|
+
[upload.go](https://github.com/e2b-dev/infra/blob/main/packages/envd/internal/api/upload.go).
|
|
158
|
+
|
|
159
|
+
## Test-only options
|
|
160
|
+
|
|
161
|
+
The third parameter of `createE2BSandbox(config, { log }, testOptions)` can be passed only by code that calls it directly; the plugin config cannot set it:
|
|
162
|
+
|
|
163
|
+
- `envdUrl`: the envd address is changed to this (routed by the `E2b-Sandbox-Id` request header), instead of being assembled from the sandbox id and domain.
|
|
164
|
+
|
|
165
|
+
## Tests
|
|
166
|
+
|
|
167
|
+
- `test/sandbox-e2b.test.ts`: against `startFakeE2B` from `@capybara-hq/testing` (control plane API + envd, Connect stream protocol,
|
|
168
|
+
commands really run in a local temp directory), covering each method, error mapping (404 → gone, 401 / 5xx → ordinary errors), abort, kill on timeout,
|
|
169
|
+
file reads and writes and the way `/skills` is written, egress mapping, and the reboot backstop when killing fails / the output stream does not end (the fake server's `memory: false` connect ends all processes).
|
|
170
|
+
- `test/live`: real E2B, run only when `CAPYBARA_TEST_E2B_API_KEY` and `CAPYBARA_TEST_E2B_TEMPLATE` are set (optionally
|
|
171
|
+
`CAPYBARA_TEST_E2B_API_URL` / `CAPYBARA_TEST_E2B_DOMAIN`), otherwise skipped: the sandbox user, files, `/skills` read-only (including a write through a symlink being
|
|
172
|
+
rejected), the egress allowlist, output truncation, kill on timeout (including nohup child processes), abort, long periods with no output, a child process that escaped the session triggering the reboot backstop, same-name replacement,
|
|
173
|
+
pause / resume, gone after destroy. The file header lists the "assumptions that can only be confirmed on real E2B"; check them one by one on the first run.
|