11#!/usr/bin/env -S uv run python
22"""
33---
4- title: Secrets with Devbox (Create, Inject, Verify, Delete)
4+ title: Secrets with Devbox via Agent Gateway
55slug: 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: Create a secret, proxy it into a devbox through agent gateway , verify the devbox only gets gateway credentials , and clean up.
77workflow:
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 with a test credential
9+ - Create an agent gateway config for an upstream API
10+ - Launch a devbox with the gateway wired to the secret
11+ - Verify the devbox receives a gateway URL and token instead of the raw secret
12+ - Shutdown the devbox and delete the gateway config and secret
1513tags:
1614 - secrets
1715 - devbox
18- - environment-variables
16+ - agent-gateway
17+ - credentials
1918 - cleanup
2019prerequisites:
2120 - RUNLOOP_API_KEY
3332
3433# Note: do NOT hardcode secret values in your code!
3534# This is example code only; use environment variables instead!
36- _EXAMPLE_SECRET_VALUE = "my-secret-value "
37- _UPDATED_SECRET_VALUE = "updated-secret-value "
35+ _EXAMPLE_GATEWAY_ENDPOINT = "https://api.example.com "
36+ _EXAMPLE_SECRET_VALUE = "example-upstream-api-key "
3837
3938
4039def recipe (ctx : RecipeContext ) -> RecipeOutput :
41- """Create a secret, inject it into a devbox , and verify it is accessible ."""
40+ """Create a secret, proxy it through an agent gateway , and verify the devbox only gets gateway credentials ."""
4241 cleanup = ctx .cleanup
4342
4443 sdk = RunloopSDK ()
4544 resources_created : list [str ] = []
4645 checks : list [ExampleCheck ] = []
4746
48- secret_name = unique_name ("RUNLOOP_SDK_EXAMPLE" ). upper (). replace ( "-" , "_ " )
47+ secret_name = unique_name ("agent-gateway-secret " )
4948
5049 secret = sdk .secret .create (name = secret_name , value = _EXAMPLE_SECRET_VALUE )
5150 resources_created .append (f"secret:{ secret_name } " )
@@ -60,10 +59,33 @@ def recipe(ctx: RecipeContext) -> RecipeOutput:
6059 )
6160 )
6261
62+ # Hide upstream credentials from the devbox by routing requests through an
63+ # agent gateway config. This prevents direct secret exfiltration.
64+ gateway_config = sdk .gateway_config .create (
65+ name = unique_name ("agent-gateway-config" ),
66+ endpoint = _EXAMPLE_GATEWAY_ENDPOINT ,
67+ auth_mechanism = {"type" : "bearer" },
68+ description = "Example gateway that keeps upstream credentials off the devbox" ,
69+ )
70+ resources_created .append (f"gateway_config:{ gateway_config .id } " )
71+ cleanup .add (f"gateway_config:{ gateway_config .id } " , gateway_config .delete )
72+
73+ gateway_info = gateway_config .get_info ()
74+ checks .append (
75+ ExampleCheck (
76+ name = "gateway config created successfully" ,
77+ passed = (gateway_info .id .startswith ("gwc_" ) and gateway_info .endpoint == _EXAMPLE_GATEWAY_ENDPOINT ),
78+ details = f"id={ gateway_info .id } , endpoint={ gateway_info .endpoint } " ,
79+ )
80+ )
81+
6382 devbox = sdk .devbox .create (
64- name = unique_name ("secrets-example-devbox" ),
65- secrets = {
66- "MY_SECRET_ENV" : secret .name ,
83+ name = unique_name ("agent-gateway-devbox" ),
84+ gateways = {
85+ "MY_API" : {
86+ "gateway" : gateway_config .id ,
87+ "secret" : secret .name ,
88+ }
6789 },
6890 launch_parameters = {
6991 "resource_size_request" : "X_SMALL" ,
@@ -73,32 +95,44 @@ def recipe(ctx: RecipeContext) -> RecipeOutput:
7395 resources_created .append (f"devbox:{ devbox .id } " )
7496 cleanup .add (f"devbox:{ devbox .id } " , devbox .shutdown )
7597
76- result = devbox .cmd .exec ("echo $MY_SECRET_ENV" )
77- stdout = result .stdout ().strip ()
98+ devbox_info = devbox .get_info ()
7899 checks .append (
79100 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 } "' ,
101+ name = "devbox records gateway wiring" ,
102+ passed = (
103+ devbox_info .gateway_specs is not None
104+ and devbox_info .gateway_specs .get ("MY_API" ) is not None
105+ and devbox_info .gateway_specs ["MY_API" ].gateway_config_id == gateway_config .id
106+ ),
107+ details = (
108+ f"gateway_config_id={ devbox_info .gateway_specs ['MY_API' ].gateway_config_id } "
109+ if devbox_info .gateway_specs is not None and devbox_info .gateway_specs .get ("MY_API" ) is not None
110+ else "gateway spec missing"
111+ ),
83112 )
84113 )
85114
86- updated_info = sdk .secret .update (secret , _UPDATED_SECRET_VALUE ).get_info ()
115+ url_result = devbox .cmd .exec ("echo $MY_API_URL" )
116+ gateway_url = url_result .stdout ().strip ()
87117 checks .append (
88118 ExampleCheck (
89- name = "secret updated successfully " ,
90- passed = updated_info . name == secret_name ,
91- details = f"update_time_ms= { updated_info . update_time_ms } " ,
119+ name = "devbox receives gateway URL " ,
120+ passed = url_result . exit_code == 0 and gateway_url . startswith ( "http" ) ,
121+ details = f"exit_code= { url_result . exit_code } , url= { gateway_url } " ,
92122 )
93123 )
94124
95- secrets = sdk . secret . list ( )
96- found = next (( s for s in secrets if s . name == secret_name ), None )
125+ token_result = devbox . cmd . exec ( "echo $MY_API" )
126+ gateway_token = token_result . stdout (). strip ( )
97127 checks .append (
98128 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" ,
129+ name = "devbox receives gateway token instead of raw secret" ,
130+ passed = (
131+ token_result .exit_code == 0
132+ and gateway_token .startswith ("gws_" )
133+ and gateway_token != _EXAMPLE_SECRET_VALUE
134+ ),
135+ details = (f"exit_code={ token_result .exit_code } , token_prefix={ gateway_token [:4 ] or 'missing' } " ),
102136 )
103137 )
104138
0 commit comments