feat(sdk/go): add user-scoped filesystem views - #1348
Conversation
Add Files.ForUser as an immutable identity scope and propagate the selected user through E2B-compatible HTTP file requests and filesystem RPC authorization headers. Preserve unscoped request behavior and existing access tokens. Refs TencentCloud#1346 Autonomously-by: Codex:GPT-5 Signed-off-by: Xu Wang <ericxwang.xu@gmail.com>
3d904c6 to
a03d703
Compare
| func setFilesystemRPCUser(req *http.Request, options ...fileRequestOption) { | ||
| opts := resolveFileRequestOptions(options...) | ||
| if opts.user != "" { | ||
| req.SetBasicAuth(opts.user, "") |
There was a problem hiding this comment.
req.SetBasicAuth unconditionally overwrites any existing Authorization header. Nothing sets it before this point today (newEnvdRequest only adds X-Access-Token + traffic tokens), so this isn't a live bug — but it's fragile and duplicates the existing basicAuthUser helper, which builds exactly this value. Consider req.Header.Set("Authorization", basicAuthUser(opts.user)) inside the guard instead.
Also worth a comment: this guard is deliberate, but it makes the wire format asymmetric with startProcess (which sends Basic root: for an empty user via basicAuthUser("") → defaultEnvdUser). Here, unscoped requests emit no Authorization header while ForUser("root") emits Basic root:. That preserves legacy unscoped behavior, but it's subtle enough to warrant a one-line note so a future reader doesn't "fix" the guard.
| sb := &Sandbox{client: client, SandboxID: "sb-files", EnvdAccessToken: "tok"} | ||
|
|
||
| if err := sb.Files().Write(context.Background(), "/tmp/x.txt", []byte("hi")); err != nil { | ||
| if err := sb.Files().ForUser("app").Write(context.Background(), "/tmp/x.txt", []byte("hi")); err != nil { |
There was a problem hiding this comment.
This switches the only octet-stream→multipart fallback test to the user-scoped path, so the unscoped Files().Write fallback — and its request shape (no username param) — is no longer exercised. TestFilesWithoutUserPreservesUnscopedRequestShape covers only Read and List. Since the PR's stated goal is to preserve unscoped request behavior, it'd be worth keeping (or adding) an unscoped Write assertion that the fallback still works with no username in the query on both attempts.
Review of PR #1348 — feat(sdk/go): add user-scoped filesystem viewsOverall: This is a clean, well-scoped addition. I did not find functional bugs in the production change. Every public Findings1. (Moderate) The godoc claims the view "executes every operation as user," and the README says "Execute all filesystem operations as a specific sandbox user." But as the PR's own integration test ( 2. (Nit) The two transports carry identity differently. HTTP 3. (Nit) Integration-test template dependency. Verification notes
This review was generated by an AI assistant. |
Restore coverage for the unscoped octet-stream-to-multipart fallback and document why filesystem RPC authentication is omitted unless a user is explicitly scoped. Autonomously-by: Codex:GPT-5 Signed-off-by: Xu Wang <ericxwang.xu@gmail.com>
| if opts.user != "" { | ||
| // Preserve the legacy unscoped request shape. An explicit ForUser("root") | ||
| // is intentionally different and sends root through Basic authentication. | ||
| req.SetBasicAuth(opts.user, "") |
There was a problem hiding this comment.
Two nits on the new envd Basic-auth construction:
- This builds the same header as the existing
basicAuthUserhelper (Basic base64(user + ":")) but bypasses it, so the envd Basic-header format now lives in two places. Considerreq.Header.Set("Authorization", basicAuthUser(opts.user))to keep a single source of truth — including thedefaultEnvdUserfallback semantics. - Because this is guarded by
opts.user != "", an empty user sends noAuthorizationheader, whereasbasicAuthUser("")would sendBasic cm9vdDo=(root). So on the RPC pathForUser("root")is wire-different from unscoped. The comment documents the intent, but the README'sForUser("root")example (used to read/root/hello.txt) could lead users to think it's a no-op relative to the default — worth surfacing to SDK users.
| watchDir(context.Context, string, ...fileRequestOption) (*Watcher, error) | ||
| } | ||
|
|
||
| // ForUser returns an immutable filesystem view that executes every operation |
There was a problem hiding this comment.
Consider documenting how the user is actually propagated, since it differs by transport: HTTP file operations (Read/Write/WriteFiles) send ?username=<user> on /files, while the filesystem RPC operations (List/Stat/Remove/Rename/MakeDir/WatchDir) send Authorization: Basic <user>:. The view is only consistent if envd honors both mechanisms. Also worth noting: ForUser("root") is wire-different from the unscoped default on the RPC path (explicit Basic root vs no header), even though both are semantically "root".
|
LGTM, this solution is quite elegant. I ran some tests, and they all passed. I notice that in #1346 Acceptance Criteria part that you mention
@wangxuw Could you please add an integration test in |
Autonomously-by: Codex:GPT-5 Signed-off-by: ericxwang <ericxwang.xu@gmail.com>
sure, added an integration test in the latest commit. The integration test passed via a TencentCloud AGS sandbox setup. @luzhixing12345 |
| watchDir(context.Context, string, ...fileRequestOption) (*Watcher, error) | ||
| } | ||
|
|
||
| // ForUser returns an immutable filesystem view that executes every operation |
There was a problem hiding this comment.
Documentation accuracy: "executes every operation as user" overstates the isolation this view provides. Per the integration test added in this PR (TestIntegrationFilesForUserIsolation), Files.Read/Files.Stat are "privileged envd management operations" — the selected user controls path expansion and ownership, but these endpoints do not switch the envd process UID, so they are not a POSIX permission boundary. A caller doing sb.Files().ForUser("nobody").Read(rootOnlyPath) should not expect a permission denial (the test verifies the real boundary via Commands/cat, deliberately not via Files.Read).
Suggest a caveat such as: "runs operations in the filesystem context of user (ownership and path expansion). Note the envd file HTTP/RPC endpoints are privileged and do not enforce POSIX permissions — use Commands for a permission boundary." The same applies to the README's "Execute all filesystem operations as a specific sandbox user" line.
Add Files.ForUser as an immutable identity scope and propagate the selected user through E2B-compatible HTTP file requests and filesystem RPC authorization headers. Preserve unscoped request behavior and existing access tokens.
Refs #1346
Autonomously-by: Codex:GPT-5