# Using secrets (https://tenki.cloud/docs/sandbox/secrets)

> For the complete documentation index, see [llms.txt](https://tenki.cloud/llms.txt)

Give your sandbox the credentials it needs through requests, environment variables, or files.

[Save a workspace secret](https://tenki.cloud/docs/secrets/manage-secrets.md#create-a-secret) before using these examples. The examples use a GitHub personal access token saved as `GITHUB_TOKEN`.

Choose the option that fits your app:

* [Transparent injection](#call-an-api-with-transparent-injection) replaces a placeholder in outgoing HTTPS requests, outside the sandbox.
* [Files](#deliver-a-secret-file) put the real value in a file the app can read.

Use SDK version `1.4.0` or later for the SDK examples. Your API key needs [Secrets access](https://tenki.cloud/docs/secrets/manage-secrets.md#access-permissions). Transparent injection also requires an image with Tenki's managed HTTPS trust.

## Call an API with transparent injection

[Create the `github` request policy](https://tenki.cloud/docs/secrets/request-policies.md#create-a-policy) first. It allows `GITHUB_TOKEN` in the `Authorization` header of GitHub user requests. Select it when creating the sandbox:

```bash
tenki sandbox create --secret-policy github
```

The new sandbox becomes your active session. Run:

```bash
tenki sandbox exec -- curl -fsS \
  -H 'Authorization: Bearer secrets://GITHUB_TOKEN' \
  https://api.github.com/user
```

GitHub returns the account associated with your token. Inside the sandbox, the command contains only the placeholder. Tenki substitutes the token before forwarding the matching request to GitHub.

**Tenki does not add `Bearer`.** Your application supplies the header format the API expects. For example, `Bearer secrets://GITHUB_TOKEN` becomes `Bearer YOUR_TOKEN`. For an API that expects a raw key in `X-API-Key`, send just the placeholder in that header and allow `X-API-Key` in the policy.

When finished with this sandbox:

```bash
tenki sandbox terminate
```

Keep the secret and policy to reuse them in the next sandbox.

### Set a placeholder once

If an application reads its credential from an environment variable, set that variable when creating the sandbox:

```bash
tenki sandbox create --secret-policy github \
  --env GITHUB_TOKEN=secrets://GITHUB_TOKEN
```

Later commands inherit this environment variable. Its value is the literal placeholder. Tenki substitutes it only when the application sends it in a request that matches a selected policy.

For a single command, use `env`:

```bash
tenki sandbox exec -- env GITHUB_TOKEN=secrets://GITHUB_TOKEN your-command
```

Replace `your-command` with the application you want to run. `sandbox exec --` executes a program directly, so a leading `GITHUB_TOKEN=...` without `env` is not a shell assignment.

### Select more than one policy

Pass saved policy names separated by commas. Both policies must already exist in your workspace:

```bash
tenki sandbox create --secret-policy github,internal-api
```

You can also repeat `--secret-policy`. Permissions from the selected policies are combined. A matching rule from any selected policy can authorize a request.

Policies control secret substitution. They do not restrict all network traffic. Ordinary requests still follow the sandbox's network settings, and a policy does not override a network restriction.

### Transparent injection from an SDK

These examples use the same `GITHUB_TOKEN` secret and `github` policy. Install your SDK and configure `TENKI_API_KEY` as described in the [quickstart](https://tenki.cloud/docs/sandbox/quickstart.md). Use a key for the workspace containing the secret and policy.

**TypeScript**
```typescript
import { stdoutText, TenkiSandbox } from "@tenkicloud/sandbox";

const client = new TenkiSandbox();
await using session = await client.createAndWait({
  secretPolicies: ["github"],
});

const result = await session.exec("curl", {
  args: ["-fsS", "-H", "Authorization: Bearer secrets://GITHUB_TOKEN", "https://api.github.com/user"],
});
console.log(stdoutText(result));
```

**Python**
```python
from tenki import Sandbox

with Sandbox.create(secret_policies=["github"]) as sandbox:
    result = sandbox.exec(
        "curl",
        "-fsS",
        "-H",
        "Authorization: Bearer secrets://GITHUB_TOKEN",
        "https://api.github.com/user",
    )
    print(result.stdout_text)
```

**Go**
```go
package main

import (
    "context"
    "fmt"
    "log"
    "time"

    tenkisandbox "github.com/LuxorLabs/tenki-sdk-go/sandbox"
)

func main() {
    if err := run(); err != nil {
        log.Fatal(err)
    }
}

func run() error {
    ctx := context.Background()
    client, err := tenkisandbox.New()
    if err != nil {
        return err
    }
    defer client.Close()

    session, err := client.CreateAndWait(ctx, 3*time.Minute,
        tenkisandbox.WithSecretPolicies("github"),
    )
    if err != nil {
        return err
    }
    defer session.Close(ctx)

    result, err := session.Exec(ctx, "curl", tenkisandbox.WithArgs(
        "-fsS", "-H", "Authorization: Bearer secrets://GITHUB_TOKEN",
        "https://api.github.com/user",
    ))
    if err != nil {
        return err
    }
    fmt.Print(result.StdoutString())
    if result.ExitCode != 0 {
        return fmt.Errorf("curl exited with code %d", result.ExitCode)
    }
    return nil
}
```

Each example closes its sandbox when it leaves scope. To set placeholders in the session environment, use TypeScript's `env`, Python's `env`, or Go's `WithEnvs` option when creating the sandbox.

## Deliver a secret file

Use a file when your application reads credentials from disk. Tenki delivers it at the path you choose inside the sandbox.

This example uses a Node.js app that reads `GITHUB_TOKEN` from `.env`. Replace `https://github.com/your-org/your-app.git` with your public repository. It should have a `package-lock.json` and an `npm run dev` script that loads `.env`.

Create a local `.env.example` using the `GITHUB_TOKEN` secret saved earlier:

```dotenv
GITHUB_TOKEN=secrets://GITHUB_TOKEN
```

Create a sandbox with the secret file, clone your app, and start it:

```bash
tenki sandbox create --secret-file ./.env.example:/app/.env

tenki sandbox exec -- git clone https://github.com/your-org/your-app.git /app/web

tenki sandbox exec --stream -- bash -c \
  'cd /app/web && cp /app/.env .env && npm ci && npm run dev'
```

Tenki writes the real token into `/app/.env`. The command copies it into the cloned app's directory before starting the dev server. Your local `.env.example` still contains only the placeholder. No request policy is needed.

The dev server runs in the foreground. When finished, terminate the sandbox from another terminal:

```bash
tenki sandbox terminate
```

You can repeat `--secret-file` for multiple files. Destinations must be below `/app/`, `/workspace/`, or `/home/tenki/`. Tenki creates files with owner-only read/write permissions, `0600`.

Replacement is plain text: Tenki does not escape values for JSON, YAML, or shell syntax.

## Secret files from an SDK

To use a `.env.example` already in your repository, see [Using secrets with templates](https://tenki.cloud/docs/sandbox/templates.md#using-secrets).

Declare the `.env` contents when creating the sandbox, then clone the app, copy in `.env`, install dependencies, and run the dev server. Use your own repository URL, with the same requirements as above. The SDK call waits while the server runs; the sandbox closes when the example exits normally.

**TypeScript**
```typescript
import { stdoutText, TenkiSandbox } from "@tenkicloud/sandbox";

const client = new TenkiSandbox();

await using session = await client.createAndWait({
  secretFiles: [{ path: "/app/.env", content: "GITHUB_TOKEN=secrets://GITHUB_TOKEN\n" }],
});
await session.git.clone("https://github.com/your-org/your-app.git", { directory: "/app/web" });
const result = await session.exec("bash", {
  args: ["-c", "cp /app/.env .env && npm ci && npm run dev"],
  cwd: "/app/web",
});
if (result.exitCode !== 0) throw new Error("App failed");
console.log(stdoutText(result));
```

**Python**
```python
from tenki import Sandbox

with Sandbox.create(secret_files=[{"path": "/app/.env", "content": "GITHUB_TOKEN=secrets://GITHUB_TOKEN\n"}]) as sandbox:
    sandbox.git.clone("https://github.com/your-org/your-app.git", directory="/app/web")
    result = sandbox.exec(
        "bash", "-c", "cp /app/.env .env && npm ci && npm run dev", cwd="/app/web"
    )
    if result.exit_code != 0:
        raise RuntimeError("App failed")
    print(result.stdout_text)
```

**Go**
```go
package main

import (
    "context"
    "fmt"
    "log"
    "time"

    tenkisandbox "github.com/LuxorLabs/tenki-sdk-go/sandbox"
)

func main() {
    if err := run(); err != nil {
        log.Fatal(err)
    }
}

func run() error {
    ctx := context.Background()
    client, err := tenkisandbox.New()
    if err != nil {
        return err
    }
    defer client.Close()

    session, err := client.CreateAndWait(ctx, 3*time.Minute, tenkisandbox.WithSecretFiles(
        &tenkisandbox.RuntimeSecretFile{
            Path: "/app/.env",
            Format: &tenkisandbox.RuntimeSecretFileContent{Content: "GITHUB_TOKEN=secrets://GITHUB_TOKEN\n"},
        },
    ))
    if err != nil {
        return err
    }
    defer session.Close(ctx)

    if _, err := session.Git.Clone(ctx, "https://github.com/your-org/your-app.git", tenkisandbox.GitCloneParams{
        Directory: "/app/web",
    }); err != nil {
        return err
    }
    result, err := session.Exec(ctx, "bash", tenkisandbox.WithArgs(
        "-c", "cd /app/web && cp /app/.env .env && npm ci && npm run dev",
    ))
    if err != nil {
        return err
    }
    if result.ExitCode != 0 {
        return fmt.Errorf("app failed")
    }
    fmt.Print(string(result.Stdout))
    return nil
}
```

### Reuse an app with another secret

Keep the same `.env.example` file while choosing a different GitHub token at launch:

```bash
tenki sandbox create --secret-file ./.env.example:/app/.env \
  --secret-override GITHUB_TOKEN=STAGING_GITHUB_TOKEN
```

Both names refer to workspace secrets; create `STAGING_GITHUB_TOKEN` first. Overrides apply to environment and file references, not transparent injection policies.

## Rotation, revocation, and pause/resume

| Change                    | Transparent injection                                          | Environment variables and files                                                     |
| ------------------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| Rotate a secret           | Subsequent requests use the active value.                      | Existing sandboxes keep the delivered value. Start a new sandbox to use the update. |
| Revoke or delete a secret | Further requests using it are denied.                          | Copies already inside the sandbox remain. New launches cannot resolve that secret.  |
| Edit or delete a policy   | Subsequent requests use the current permissions.               | No effect; policies do not control guest delivery.                                  |
| Pause and resume          | Selected policies stay attached and current permissions apply. | The sandbox retains its existing values. Resume does not refresh them.              |

Files and process memory can be retained in snapshots, including copies your app makes. Treat snapshots and images created from a sandbox with delivered secrets as containing those secrets. Revoking a secret in Tenki does not erase existing copies or revoke the credential at its provider.

## Troubleshooting

If access is denied, check that your account can edit the workspace and your API key has **Allow Secrets access**. If the app cannot find an environment variable, check that its dev script loads the `.env` file copied into `/app/web`.

| Symptom                                                          | What to check                                                                                                                              |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| The CLI does not recognize `secrets policies`                    | Update the CLI.                                                                                                                            |
| Secret or policy not found                                       | Check the active workspace, spelling, and whether the object was deleted. Use the secret's actual name in `secrets://NAME`.                |
| Tenki returns `403 secret_injection_denied`                      | Check the selected policies, secret status, origin, method, path, and placeholder location. A matching rule must allow that secret.        |
| The provider returns `401`                                       | Check the token's validity, permissions, and required header format. Tenki does not add `Bearer`.                                          |
| The application rejects the placeholder before sending a request | That client validates credentials locally. Use a client that accepts placeholders or explicit secret delivery.                             |
| TLS certificate verification fails                               | Use an image with Tenki's managed CA trust and make sure the application's trust store includes it. Keep certificate verification enabled. |
| A request is too large for JSON injection                        | Reduce the JSON body below 256 KiB.                                                                                                        |

An allowed destination receives the real credential. Choose destinations you trust and keep both provider permissions and policy rules limited to what the sandbox needs. Transparent injection keeps the credential out of the guest's request construction; it cannot prevent an allowed service from returning it in a response.