Skip to content

Commit d4dd18f

Browse files
authored
improve: added agent gateway to secrets example (#762)
1 parent 97bcd70 commit d4dd18f

4 files changed

Lines changed: 119 additions & 49 deletions

File tree

EXAMPLES.md

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Runnable examples live in [`examples/`](./examples).
1212
- [Devbox Snapshot and Resume](#devbox-snapshot-resume)
1313
- [Devbox Tunnel (HTTP Server Access)](#devbox-tunnel)
1414
- [MCP Hub + Claude Code + GitHub](#mcp-github-tools)
15-
- [Secrets with Devbox (Create, Inject, Verify, Delete)](#secrets-with-devbox)
15+
- [Secrets with Devbox and Agent Gateway](#secrets-with-devbox)
1616

1717
<a id="blueprint-with-build-context"></a>
1818
## Blueprint with Build Context
@@ -168,20 +168,19 @@ uv run pytest -m smoketest tests/smoketests/examples/
168168
**Source:** [`examples/mcp_github_tools.py`](./examples/mcp_github_tools.py)
169169

170170
<a id="secrets-with-devbox"></a>
171-
## Secrets with Devbox (Create, Inject, Verify, Delete)
171+
## Secrets with Devbox and Agent Gateway
172172

173-
**Use case:** Create a secret, inject it into a devbox as an environment variable, verify access, and clean up.
173+
**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.
174174

175-
**Tags:** `secrets`, `devbox`, `environment-variables`, `cleanup`
175+
**Tags:** `secrets`, `devbox`, `agent-gateway`, `credentials`, `environment-variables`, `cleanup`
176176

177177
### Workflow
178-
- Create a secret with a test value
179-
- Create a devbox with the secret mapped to an env var
180-
- Execute a command that reads the secret from the environment
181-
- Verify the value matches
182-
- Update the secret and verify
183-
- List secrets and verify the secret appears
184-
- Shutdown devbox and delete secret
178+
- Create a secret for application data that should be available inside the devbox
179+
- Create a separate secret for an upstream API credential
180+
- Create an agent gateway config for an upstream API
181+
- Launch a devbox with one secret injected directly and the credential wired through agent gateway
182+
- Verify the devbox can read MAGIC_NUMBER while the upstream API credential is replaced with gateway values
183+
- Shutdown the devbox and delete the gateway config and both secrets
185184

186185
### Prerequisites
187186
- `RUNLOOP_API_KEY`

examples/registry.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
},
5656
{
5757
"slug": "secrets-with-devbox",
58-
"title": "Secrets with Devbox (Create, Inject, Verify, Delete)",
58+
"title": "Secrets with Devbox and Agent Gateway",
5959
"file_name": "secrets_with_devbox.py",
6060
"required_env": ["RUNLOOP_API_KEY"],
6161
"run": run_secrets_with_devbox_example,

examples/secrets_with_devbox.py

Lines changed: 107 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
#!/usr/bin/env -S uv run python
22
"""
33
---
4-
title: Secrets with Devbox (Create, Inject, Verify, Delete)
4+
title: Secrets with Devbox and Agent Gateway
55
slug: secrets-with-devbox
6-
use_case: Create a secret, inject it into a devbox as an environment variable, verify access, and clean up.
6+
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.
77
workflow:
8-
- Create a secret with a test value
9-
- Create a devbox with the secret mapped to an env var
10-
- Execute a command that reads the secret from the environment
11-
- Verify the value matches
12-
- Update the secret and verify
13-
- List secrets and verify the secret appears
14-
- Shutdown devbox and delete secret
8+
- Create a secret for application data that should be available inside the devbox
9+
- Create a separate secret for an upstream API credential
10+
- Create an agent gateway config for an upstream API
11+
- Launch a devbox with one secret injected directly and the credential wired through agent gateway
12+
- Verify the devbox can read MAGIC_NUMBER while the upstream API credential is replaced with gateway values
13+
- Shutdown the devbox and delete the gateway config and both secrets
1514
tags:
1615
- secrets
1716
- devbox
17+
- agent-gateway
18+
- credentials
1819
- environment-variables
1920
- cleanup
2021
prerequisites:
@@ -33,37 +34,85 @@
3334

3435
# Note: do NOT hardcode secret values in your code!
3536
# This is example code only; use environment variables instead!
36-
_EXAMPLE_SECRET_VALUE = "my-secret-value"
37-
_UPDATED_SECRET_VALUE = "updated-secret-value"
37+
_EXAMPLE_GATEWAY_ENDPOINT = "https://api.example.com"
38+
_UPSTREAM_CREDENTIAL_VALUE = "example-upstream-api-key"
39+
_MAGIC_NUMBER_VALUE = "42"
3840

3941

4042
def recipe(ctx: RecipeContext) -> RecipeOutput:
41-
"""Create a secret, inject it into a devbox, and verify it is accessible."""
43+
"""Demonstrate direct secret injection for app data and agent gateway protection for upstream credentials."""
4244
cleanup = ctx.cleanup
4345

4446
sdk = RunloopSDK()
4547
resources_created: list[str] = []
4648
checks: list[ExampleCheck] = []
4749

48-
secret_name = unique_name("RUNLOOP_SDK_EXAMPLE").upper().replace("-", "_")
50+
magic_number_name = unique_name("magic-number-secret")
51+
upstream_credential_name = unique_name("agent-gateway-secret")
4952

50-
secret = sdk.secret.create(name=secret_name, value=_EXAMPLE_SECRET_VALUE)
51-
resources_created.append(f"secret:{secret_name}")
52-
cleanup.add(f"secret:{secret_name}", lambda: secret.delete())
53+
magic_number_secret = sdk.secret.create(name=magic_number_name, value=_MAGIC_NUMBER_VALUE)
54+
resources_created.append(f"secret:{magic_number_name}")
55+
cleanup.add(f"secret:{magic_number_name}", magic_number_secret.delete)
5356

54-
secret_info = secret.get_info()
57+
magic_number_info = magic_number_secret.get_info()
5558
checks.append(
5659
ExampleCheck(
57-
name="secret created successfully",
58-
passed=secret.name == secret_name and secret_info.id.startswith("sec_"),
59-
details=f"name={secret.name}, id={secret_info.id}",
60+
name="magic number secret created successfully",
61+
passed=(magic_number_secret.name == magic_number_name and magic_number_info.id.startswith("sec_")),
62+
details=f"name={magic_number_secret.name}, id={magic_number_info.id}",
63+
)
64+
)
65+
66+
upstream_credential_secret = sdk.secret.create(
67+
name=upstream_credential_name,
68+
value=_UPSTREAM_CREDENTIAL_VALUE,
69+
)
70+
resources_created.append(f"secret:{upstream_credential_name}")
71+
cleanup.add(f"secret:{upstream_credential_name}", upstream_credential_secret.delete)
72+
73+
upstream_credential_info = upstream_credential_secret.get_info()
74+
checks.append(
75+
ExampleCheck(
76+
name="upstream credential secret created successfully",
77+
passed=(
78+
upstream_credential_secret.name == upstream_credential_name
79+
and upstream_credential_info.id.startswith("sec_")
80+
),
81+
details=(f"name={upstream_credential_secret.name}, id={upstream_credential_info.id}"),
82+
)
83+
)
84+
85+
# Use direct secret injection when code inside the devbox legitimately needs
86+
# the secret value at runtime. Use agent gateway for upstream credentials
87+
# that should never be exposed to the agent.
88+
gateway_config = sdk.gateway_config.create(
89+
name=unique_name("agent-gateway-config"),
90+
endpoint=_EXAMPLE_GATEWAY_ENDPOINT,
91+
auth_mechanism={"type": "bearer"},
92+
description="Example gateway that keeps upstream credentials off the devbox",
93+
)
94+
resources_created.append(f"gateway_config:{gateway_config.id}")
95+
cleanup.add(f"gateway_config:{gateway_config.id}", gateway_config.delete)
96+
97+
gateway_info = gateway_config.get_info()
98+
checks.append(
99+
ExampleCheck(
100+
name="gateway config created successfully",
101+
passed=(gateway_info.id.startswith("gwc_") and gateway_info.endpoint == _EXAMPLE_GATEWAY_ENDPOINT),
102+
details=f"id={gateway_info.id}, endpoint={gateway_info.endpoint}",
60103
)
61104
)
62105

63106
devbox = sdk.devbox.create(
64-
name=unique_name("secrets-example-devbox"),
107+
name=unique_name("agent-gateway-devbox"),
65108
secrets={
66-
"MY_SECRET_ENV": secret.name,
109+
"MAGIC_NUMBER": magic_number_secret.name,
110+
},
111+
gateways={
112+
"MY_API": {
113+
"gateway": gateway_config.id,
114+
"secret": upstream_credential_secret.name,
115+
}
67116
},
68117
launch_parameters={
69118
"resource_size_request": "X_SMALL",
@@ -73,32 +122,54 @@ def recipe(ctx: RecipeContext) -> RecipeOutput:
73122
resources_created.append(f"devbox:{devbox.id}")
74123
cleanup.add(f"devbox:{devbox.id}", devbox.shutdown)
75124

76-
result = devbox.cmd.exec("echo $MY_SECRET_ENV")
77-
stdout = result.stdout().strip()
125+
devbox_info = devbox.get_info()
126+
checks.append(
127+
ExampleCheck(
128+
name="devbox records gateway wiring",
129+
passed=(
130+
devbox_info.gateway_specs is not None
131+
and devbox_info.gateway_specs.get("MY_API") is not None
132+
and devbox_info.gateway_specs["MY_API"].gateway_config_id == gateway_config.id
133+
),
134+
details=(
135+
f"gateway_config_id={devbox_info.gateway_specs['MY_API'].gateway_config_id}"
136+
if devbox_info.gateway_specs is not None and devbox_info.gateway_specs.get("MY_API") is not None
137+
else "gateway spec missing"
138+
),
139+
)
140+
)
141+
142+
magic_number_result = devbox.cmd.exec("echo $MAGIC_NUMBER")
143+
magic_number = magic_number_result.stdout().strip()
78144
checks.append(
79145
ExampleCheck(
80-
name="devbox can read secret as env var",
81-
passed=result.exit_code == 0 and stdout == _EXAMPLE_SECRET_VALUE,
82-
details=f'exit_code={result.exit_code}, stdout="{stdout}"',
146+
name="devbox receives plain secret when app needs the value",
147+
passed=(magic_number_result.exit_code == 0 and magic_number == _MAGIC_NUMBER_VALUE),
148+
details=(f"exit_code={magic_number_result.exit_code}, MAGIC_NUMBER={magic_number}"),
83149
)
84150
)
85151

86-
updated_info = sdk.secret.update(secret, _UPDATED_SECRET_VALUE).get_info()
152+
url_result = devbox.cmd.exec("echo $MY_API_URL")
153+
gateway_url = url_result.stdout().strip()
87154
checks.append(
88155
ExampleCheck(
89-
name="secret updated successfully",
90-
passed=updated_info.name == secret_name,
91-
details=f"update_time_ms={updated_info.update_time_ms}",
156+
name="devbox receives gateway URL",
157+
passed=url_result.exit_code == 0 and gateway_url.startswith("http"),
158+
details=f"exit_code={url_result.exit_code}, url={gateway_url}",
92159
)
93160
)
94161

95-
secrets = sdk.secret.list()
96-
found = next((s for s in secrets if s.name == secret_name), None)
162+
token_result = devbox.cmd.exec("echo $MY_API")
163+
gateway_token = token_result.stdout().strip()
97164
checks.append(
98165
ExampleCheck(
99-
name="secret appears in list",
100-
passed=found is not None,
101-
details=f"found name={found.name}" if found else "not found",
166+
name="devbox receives gateway token instead of raw secret",
167+
passed=(
168+
token_result.exit_code == 0
169+
and gateway_token.startswith("gws_")
170+
and gateway_token != _UPSTREAM_CREDENTIAL_VALUE
171+
),
172+
details=(f"exit_code={token_result.exit_code}, token_prefix={gateway_token[:4] or 'missing'}"),
102173
)
103174
)
104175

llms.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
- [Devbox lifecycle example](examples/devbox_from_blueprint_lifecycle.py): Create blueprint, launch devbox, run commands, cleanup
1414
- [Devbox snapshot and resume example](examples/devbox_snapshot_resume.py): Snapshot disk, resume from snapshot, verify state isolation
1515
- [MCP GitHub example](examples/mcp_github_tools.py): MCP Hub integration with Claude Code
16-
- [Secrets with Devbox example](examples/secrets_with_devbox.py): Create secret, inject into devbox, verify, cleanup
16+
- [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
1717

1818
## API Reference
1919

0 commit comments

Comments
 (0)