# The proxy and sandboxes

> Authored code runs in a sandbox whose only route out is the proxy, and the proxy is the only thing that ever holds your credential.

Source: https://docs.getgraft.ai/graft/concepts/the-proxy-and-sandboxes

Two pieces keep your credential out of the code, the sandbox and the model. The **sandbox** is where authored code runs, and its only egress is the proxy. The **proxy** is the one route from a sandbox to a vendor, and it injects the credential on the way out.

## The sandbox

A sandbox is a short-lived Docker container in self-hosted Graft and a Blaxel sandbox on Graft Cloud. It runs Node 24 with the toolbox mounted. Its network reaches the proxy and nothing else: no vendor directly, no npm registry, no DNS for the outside world.

Packages are never installed inside it. A tool that declares a dependency has it installed at publish time, in a separate step that alone may reach the registry, and only when the package clears the [package policy](https://docs.getgraft.ai/graft/toolbox/packages-and-sdks.md). The published version carries its own `node_modules`, pinned, so a run needs nothing from the network but the proxy.

## What a module can do

A module's whole route out is `ctx`:

- `ctx.fetch(path, init)` calls the vendor with a vendor-relative path, `/repos/getmodern-ai/graft/issues`. The proxy supplies the connection's primary host and injects the credential. An absolute URL is refused before any request is made.
- `ctx.proxyBase(host?)` is the base URL an SDK is pointed at, and `ctx.proxyKey` is the credential it is constructed with. An SDK reaches the vendor through the proxy or not at all; the static check refuses a module whose SDK is bound to anything else.

A module never holds a key, never names a host, and never sets an `Authorization` header of its own. The environment variables the runner needs are removed before the module loads, and the check refuses a module that names them.

## The capability token

Every run is handed a **capability token**: a short-lived EdDSA JWT minted for that one run, naming the agent, the connections it may reach, the tool, and whether this is a dry run. It expires in minutes. The proxy verifies it statelessly and reads everything it needs from it, so a module running for one agent cannot reach a connection that agent was never given.

This is the value a module sees as `ctx.proxyKey`. Code can print it; what that buys is bounded to those connections for those minutes, and nothing about your real credential.

## What the proxy does

On every request from a sandbox, in order:

1. Verifies the capability token and refuses an expired or unknown one.
2. Strips whatever authentication the request carried.
3. Resolves the connection and pins the request to its declared host set. A host outside the set is refused, and so is any name that resolves to a private, loopback, link-local or cloud-metadata address.
4. Injects the real credential according to the connection's scheme: a header, a query parameter, a bearer token, basic auth, an HMAC signature, a refreshed OAuth access token.
5. Forwards the request and returns the vendor's answer verbatim. Redirects are not followed unless the deployment turns them on, and then only within the host set and three hops deep.
6. Redacts the credential by value on the way back. A vendor that echoes the key it refused in a 401 body would otherwise hand it to the sandbox; the proxy replaces every occurrence with `[redacted:credential]` and marks the response `x-graft-redacted: credential`.

## Dry runs

When the token says dry run, the proxy forwards `GET` and `HEAD` as usual and stops every other method. Instead of reaching the vendor, the request comes back as a `202` with the header `x-graft-dry-run: intercepted` and a JSON preview of what would have been sent: the method, the path, the header names (never their values) and the body. Nothing changes at the vendor. This is how `acquire` proves a write tool's request shape before anyone is asked to approve it.

## Where the credential lives

Credentials are encrypted at rest under a keyring: a local key derived from the deployment's secret in self-hosted Graft, a KMS key on Graft Cloud. The proxy decrypts at injection time, and the console never reads a credential back.

- [How credentials are protected](https://docs.getgraft.ai/graft/security/how-credentials-are-protected.md): The full picture, from entry to injection.
- [What the model can and cannot do](https://docs.getgraft.ai/graft/security/what-the-model-can-and-cannot-do.md): The boundaries authored code runs inside.

## Try it

```text
Which hosts can the tool you just built actually reach?
```

```text
Dry-run the order tool with a quantity of 2 and show me the request it would send.
```
