diff --git a/EXAMPLES.md b/EXAMPLES.md
index a036aa01d..b419b5eb5 100644
--- a/EXAMPLES.md
+++ b/EXAMPLES.md
@@ -12,7 +12,7 @@ Runnable examples live in [`examples/`](./examples).
- [Devbox Snapshot and Resume](#devbox-snapshot-resume)
- [Devbox Tunnel (HTTP Server Access)](#devbox-tunnel)
- [MCP Hub + Claude Code + GitHub](#mcp-github-tools)
-- [Secrets with Devbox (Create, Inject, Verify, Delete)](#secrets-with-devbox)
+- [Secrets with Devbox and Agent Gateway](#secrets-with-devbox)
## Blueprint with Build Context
@@ -168,20 +168,19 @@ uv run pytest -m smoketest tests/smoketests/examples/
**Source:** [`examples/mcp_github_tools.py`](./examples/mcp_github_tools.py)
-## Secrets with Devbox (Create, Inject, Verify, Delete)
+## Secrets with Devbox and Agent Gateway
-**Use case:** Create a secret, inject it into a devbox as an environment variable, verify access, and clean up.
+**Use case:** Use a normal secret for sensitive app data in the devbox and agent gateway for upstream API credentials that should never be exposed to the agent.
-**Tags:** `secrets`, `devbox`, `environment-variables`, `cleanup`
+**Tags:** `secrets`, `devbox`, `agent-gateway`, `credentials`, `environment-variables`, `cleanup`
### Workflow
-- Create a secret with a test value
-- Create a devbox with the secret mapped to an env var
-- Execute a command that reads the secret from the environment
-- Verify the value matches
-- Update the secret and verify
-- List secrets and verify the secret appears
-- Shutdown devbox and delete secret
+- Create a secret for application data that should be available inside the devbox
+- Create a separate secret for an upstream API credential
+- Create an agent gateway config for an upstream API
+- Launch a devbox with one secret injected directly and the credential wired through agent gateway
+- Verify the devbox can read MAGIC_NUMBER while the upstream API credential is replaced with gateway values
+- Shutdown the devbox and delete the gateway config and both secrets
### Prerequisites
- `RUNLOOP_API_KEY`
diff --git a/examples/registry.py b/examples/registry.py
index edc52f445..7362e2612 100644
--- a/examples/registry.py
+++ b/examples/registry.py
@@ -55,7 +55,7 @@
},
{
"slug": "secrets-with-devbox",
- "title": "Secrets with Devbox (Create, Inject, Verify, Delete)",
+ "title": "Secrets with Devbox and Agent Gateway",
"file_name": "secrets_with_devbox.py",
"required_env": ["RUNLOOP_API_KEY"],
"run": run_secrets_with_devbox_example,
diff --git a/examples/secrets_with_devbox.py b/examples/secrets_with_devbox.py
index c992c53e2..c0eaafcb9 100644
--- a/examples/secrets_with_devbox.py
+++ b/examples/secrets_with_devbox.py
@@ -1,20 +1,21 @@
#!/usr/bin/env -S uv run python
"""
---
-title: Secrets with Devbox (Create, Inject, Verify, Delete)
+title: Secrets with Devbox and Agent Gateway
slug: secrets-with-devbox
-use_case: Create a secret, inject it into a devbox as an environment variable, verify access, and clean up.
+use_case: Use a normal secret for sensitive app data in the devbox and agent gateway for upstream API credentials that should never be exposed to the agent.
workflow:
- - Create a secret with a test value
- - Create a devbox with the secret mapped to an env var
- - Execute a command that reads the secret from the environment
- - Verify the value matches
- - Update the secret and verify
- - List secrets and verify the secret appears
- - Shutdown devbox and delete secret
+ - Create a secret for application data that should be available inside the devbox
+ - Create a separate secret for an upstream API credential
+ - Create an agent gateway config for an upstream API
+ - Launch a devbox with one secret injected directly and the credential wired through agent gateway
+ - Verify the devbox can read MAGIC_NUMBER while the upstream API credential is replaced with gateway values
+ - Shutdown the devbox and delete the gateway config and both secrets
tags:
- secrets
- devbox
+ - agent-gateway
+ - credentials
- environment-variables
- cleanup
prerequisites:
@@ -33,37 +34,85 @@
# Note: do NOT hardcode secret values in your code!
# This is example code only; use environment variables instead!
-_EXAMPLE_SECRET_VALUE = "my-secret-value"
-_UPDATED_SECRET_VALUE = "updated-secret-value"
+_EXAMPLE_GATEWAY_ENDPOINT = "https://api.example.com"
+_UPSTREAM_CREDENTIAL_VALUE = "example-upstream-api-key"
+_MAGIC_NUMBER_VALUE = "42"
def recipe(ctx: RecipeContext) -> RecipeOutput:
- """Create a secret, inject it into a devbox, and verify it is accessible."""
+ """Demonstrate direct secret injection for app data and agent gateway protection for upstream credentials."""
cleanup = ctx.cleanup
sdk = RunloopSDK()
resources_created: list[str] = []
checks: list[ExampleCheck] = []
- secret_name = unique_name("RUNLOOP_SDK_EXAMPLE").upper().replace("-", "_")
+ magic_number_name = unique_name("magic-number-secret")
+ upstream_credential_name = unique_name("agent-gateway-secret")
- secret = sdk.secret.create(name=secret_name, value=_EXAMPLE_SECRET_VALUE)
- resources_created.append(f"secret:{secret_name}")
- cleanup.add(f"secret:{secret_name}", lambda: secret.delete())
+ magic_number_secret = sdk.secret.create(name=magic_number_name, value=_MAGIC_NUMBER_VALUE)
+ resources_created.append(f"secret:{magic_number_name}")
+ cleanup.add(f"secret:{magic_number_name}", magic_number_secret.delete)
- secret_info = secret.get_info()
+ magic_number_info = magic_number_secret.get_info()
checks.append(
ExampleCheck(
- name="secret created successfully",
- passed=secret.name == secret_name and secret_info.id.startswith("sec_"),
- details=f"name={secret.name}, id={secret_info.id}",
+ name="magic number secret created successfully",
+ passed=(magic_number_secret.name == magic_number_name and magic_number_info.id.startswith("sec_")),
+ details=f"name={magic_number_secret.name}, id={magic_number_info.id}",
+ )
+ )
+
+ upstream_credential_secret = sdk.secret.create(
+ name=upstream_credential_name,
+ value=_UPSTREAM_CREDENTIAL_VALUE,
+ )
+ resources_created.append(f"secret:{upstream_credential_name}")
+ cleanup.add(f"secret:{upstream_credential_name}", upstream_credential_secret.delete)
+
+ upstream_credential_info = upstream_credential_secret.get_info()
+ checks.append(
+ ExampleCheck(
+ name="upstream credential secret created successfully",
+ passed=(
+ upstream_credential_secret.name == upstream_credential_name
+ and upstream_credential_info.id.startswith("sec_")
+ ),
+ details=(f"name={upstream_credential_secret.name}, id={upstream_credential_info.id}"),
+ )
+ )
+
+ # Use direct secret injection when code inside the devbox legitimately needs
+ # the secret value at runtime. Use agent gateway for upstream credentials
+ # that should never be exposed to the agent.
+ gateway_config = sdk.gateway_config.create(
+ name=unique_name("agent-gateway-config"),
+ endpoint=_EXAMPLE_GATEWAY_ENDPOINT,
+ auth_mechanism={"type": "bearer"},
+ description="Example gateway that keeps upstream credentials off the devbox",
+ )
+ resources_created.append(f"gateway_config:{gateway_config.id}")
+ cleanup.add(f"gateway_config:{gateway_config.id}", gateway_config.delete)
+
+ gateway_info = gateway_config.get_info()
+ checks.append(
+ ExampleCheck(
+ name="gateway config created successfully",
+ passed=(gateway_info.id.startswith("gwc_") and gateway_info.endpoint == _EXAMPLE_GATEWAY_ENDPOINT),
+ details=f"id={gateway_info.id}, endpoint={gateway_info.endpoint}",
)
)
devbox = sdk.devbox.create(
- name=unique_name("secrets-example-devbox"),
+ name=unique_name("agent-gateway-devbox"),
secrets={
- "MY_SECRET_ENV": secret.name,
+ "MAGIC_NUMBER": magic_number_secret.name,
+ },
+ gateways={
+ "MY_API": {
+ "gateway": gateway_config.id,
+ "secret": upstream_credential_secret.name,
+ }
},
launch_parameters={
"resource_size_request": "X_SMALL",
@@ -73,32 +122,54 @@ def recipe(ctx: RecipeContext) -> RecipeOutput:
resources_created.append(f"devbox:{devbox.id}")
cleanup.add(f"devbox:{devbox.id}", devbox.shutdown)
- result = devbox.cmd.exec("echo $MY_SECRET_ENV")
- stdout = result.stdout().strip()
+ devbox_info = devbox.get_info()
+ checks.append(
+ ExampleCheck(
+ name="devbox records gateway wiring",
+ passed=(
+ devbox_info.gateway_specs is not None
+ and devbox_info.gateway_specs.get("MY_API") is not None
+ and devbox_info.gateway_specs["MY_API"].gateway_config_id == gateway_config.id
+ ),
+ details=(
+ f"gateway_config_id={devbox_info.gateway_specs['MY_API'].gateway_config_id}"
+ if devbox_info.gateway_specs is not None and devbox_info.gateway_specs.get("MY_API") is not None
+ else "gateway spec missing"
+ ),
+ )
+ )
+
+ magic_number_result = devbox.cmd.exec("echo $MAGIC_NUMBER")
+ magic_number = magic_number_result.stdout().strip()
checks.append(
ExampleCheck(
- name="devbox can read secret as env var",
- passed=result.exit_code == 0 and stdout == _EXAMPLE_SECRET_VALUE,
- details=f'exit_code={result.exit_code}, stdout="{stdout}"',
+ name="devbox receives plain secret when app needs the value",
+ passed=(magic_number_result.exit_code == 0 and magic_number == _MAGIC_NUMBER_VALUE),
+ details=(f"exit_code={magic_number_result.exit_code}, MAGIC_NUMBER={magic_number}"),
)
)
- updated_info = sdk.secret.update(secret, _UPDATED_SECRET_VALUE).get_info()
+ url_result = devbox.cmd.exec("echo $MY_API_URL")
+ gateway_url = url_result.stdout().strip()
checks.append(
ExampleCheck(
- name="secret updated successfully",
- passed=updated_info.name == secret_name,
- details=f"update_time_ms={updated_info.update_time_ms}",
+ name="devbox receives gateway URL",
+ passed=url_result.exit_code == 0 and gateway_url.startswith("http"),
+ details=f"exit_code={url_result.exit_code}, url={gateway_url}",
)
)
- secrets = sdk.secret.list()
- found = next((s for s in secrets if s.name == secret_name), None)
+ token_result = devbox.cmd.exec("echo $MY_API")
+ gateway_token = token_result.stdout().strip()
checks.append(
ExampleCheck(
- name="secret appears in list",
- passed=found is not None,
- details=f"found name={found.name}" if found else "not found",
+ name="devbox receives gateway token instead of raw secret",
+ passed=(
+ token_result.exit_code == 0
+ and gateway_token.startswith("gws_")
+ and gateway_token != _UPSTREAM_CREDENTIAL_VALUE
+ ),
+ details=(f"exit_code={token_result.exit_code}, token_prefix={gateway_token[:4] or 'missing'}"),
)
)
diff --git a/llms.txt b/llms.txt
index 3785def20..fe07b3bac 100644
--- a/llms.txt
+++ b/llms.txt
@@ -13,7 +13,7 @@
- [Devbox lifecycle example](examples/devbox_from_blueprint_lifecycle.py): Create blueprint, launch devbox, run commands, cleanup
- [Devbox snapshot and resume example](examples/devbox_snapshot_resume.py): Snapshot disk, resume from snapshot, verify state isolation
- [MCP GitHub example](examples/mcp_github_tools.py): MCP Hub integration with Claude Code
-- [Secrets with Devbox example](examples/secrets_with_devbox.py): Create secret, inject into devbox, verify, cleanup
+- [Secrets with Devbox example](examples/secrets_with_devbox.py): Inject a normal secret for app runtime use, protect upstream credentials with agent gateway, verify both behaviors, cleanup
## API Reference