Git¶
sbx.git is the E2B SDK's git API on top of commands.run: each method
builds the git command with E2B's arguments and runs it inside the
sandbox as the sandbox user. rayito-base ships git-core
(git-core-2.50.1-1.amzn2023.0.1, pinned in image/Dockerfile).
E2B marks this module as deprecated
Rayito offers it for parity. Nothing stops you from using
sbx.commands.run("git ...") directly.
from rayito import Sandbox
with Sandbox.create() as sbx:
sbx.git.clone("https://github.com/octo/demo.git", path="/home/user/demo", depth=1)
sbx.git.configure_user("Agent", "agent@example.com")
sbx.files.write("/home/user/demo/NOTES.md", "hello\n")
sbx.git.add("/home/user/demo")
sbx.git.commit("/home/user/demo", "notes")
status = sbx.git.status("/home/user/demo")
print(status.current_branch, status.ahead, status.is_clean)
print(sbx.git.branches("/home/user/demo").branches)
import asyncio
import os
from rayito import AsyncSandbox
async def main() -> None:
async with await AsyncSandbox.create() as sbx:
repo = "/home/user/demo"
await sbx.git.clone("https://github.com/octo/demo.git", path=repo, depth=1)
await sbx.git.create_branch(repo, "changes")
await sbx.files.write(f"{repo}/NOTES.md", "hello\n")
await sbx.git.add(repo)
await sbx.git.commit(repo, "notes", author_name="Agent", author_email="agent@example.com")
# short-lived, single-repo token: the sandbox code can see it
await sbx.git.push(repo, username="x-access-token", password=os.environ["GIT_TOKEN"])
print((await sbx.git.status(repo)).ahead)
asyncio.run(main())
import { Sandbox } from "rayito";
await using sbx = await Sandbox.create();
const repo = "/home/user/demo";
await sbx.git.clone("https://github.com/octo/demo.git", { path: repo, depth: 1 });
await sbx.git.configureUser("Agent", "agent@example.com");
await sbx.git.createBranch(repo, "changes");
await sbx.files.write(`${repo}/NOTES.md`, "hello\n");
await sbx.git.add(repo);
await sbx.git.commit(repo, "notes");
const status = await sbx.git.status(repo);
console.log(status.currentBranch, status.isClean, status.ahead);
await sbx.git.push(repo, { username: "x-access-token", password: process.env.GIT_TOKEN ?? "" });
console.log((await sbx.git.branches(repo)).branches);
Methods (the same in Git and AsyncGit; in TypeScript in camelCase):
clone, init, remote_add, remote_get, status, branches,
create_branch, checkout_branch, delete_branch, add, commit,
reset, restore, push, pull, set_config, get_config,
dangerously_authenticate and configure_user. All of them also accept
envs, user, cwd, timeout and request_timeout, and set
GIT_TERMINAL_PROMPT=0 (a git that would ask for a password fails instead
of hanging). In TypeScript the required arguments remain positional
(path, branch, message, url in clone…) and the optional ones go in
an object ({ path, depth }, { username, password },
{ authorName, authorEmail }…); dangerouslyAuthenticate({ username,
password, host }) takes everything in the object.
status() returns a GitStatus (current_branch, upstream, ahead,
behind, detached, file_status and the properties is_clean,
has_changes, has_staged, has_untracked); branches(), a
GitBranches (branches, current_branch); get_config() and
remote_get(), the value or None. The rest return the command's
CommandResult.
Credentials¶
Treat the token as disclosed to the sandbox code
A username/password you pass to clone, push or pull is within
reach of any code that has already run in that sandbox with the same
user: it may have prepared its ~/.bash_profile (commands run with a
login shell), its git configuration or a process that watches /proc.
Use only short-lived, single-repository tokens with the minimum
permission (for example, a GitHub App installation token), never a
long-lived personal token in a sandbox that runs untrusted code. It is
the same rule as for injected secrets.
clone,pushandpullwithusername/password: the URL with credentials is in.git/configwhile the command lasts. After aclonethe SDK leaves theoriginremote with the URL without credentials (unlessdangerously_store_credentials=True); inpush/pullit changes the remote's URL, runs the command and always restores it, even if the command fails or expires. If the restore cannot be done (the sandbox is paused, the stream is cut), therayito.gitlogger (in TypeScript, the sandbox'slogger) warns, without the URL, that the token may still be in.git/config, and would travel in a snapshot or inpersist=. ApasswordwithoutusernameraisesInvalidArgumentExceptionbefore running anything.- Hardening: with credentials, the git command runs without hooks
(
-c core.hooksPath=/dev/null) and without credential helpers (-c credential.helper=, so a configured helper does not store the token on disk), and before sending anything the SDK checks that the git configuration does not rewrite URLs (url.*.insteadOf); if it does, it raisesGitAuthExceptionwithout having sent the credentials. - The credentials travel in the command (
StartRequest.cmd) torayd, which never logs commands; the SDK does not log them either and redacts them (***) fromstdout,stderrand the message of aCommandExitException. dangerously_authenticate(username, password, host="github.com")stores the credentials withgit credential approvein the sandbox user's~/.git-credentials: they are visible to the sandbox code, just as in E2B. Use it only with a minimal-scope, short-lived token (Security).
Errors¶
| Situation | Python | TypeScript |
|---|---|---|
| the remote asks for credentials | GitAuthException (never includes the URL) |
GitAuthError |
the git configuration rewrites URLs (url.*.insteadOf) and the call carries credentials |
GitAuthException, without sending them |
GitAuthError |
push/pull without a tracking branch |
GitUpstreamException with the missing command |
GitUpstreamError |
| any other git failure | CommandExitException (output redacted if there were credentials) |
CommandExitError |
| invalid arguments | InvalidArgumentException, before running anything |
InvalidArgumentError |
In the E2B shim, sbx.git is the same object and the exceptions have the
names from e2b.exceptions (GitAuthException, GitUpstreamException).