The vault's placeholder injection model, the allowed_hosts egress whitelist, the gateway's Authorization-header substitution, and choosing between session environment variables and the vault.
Overview
This page covers the Managed Agents credential security path: the vault's placeholder injection, the allowed_hosts egress whitelist, the gateway's Authorization-header substitution, and the layering between session environment variables (environment_variables) and the vault. Relative to the production page, which wires credentials up by the shortest path, this page explains exactly where in the chain secrets exist and in what form.
For the vault's basic usage (create, store credentials, attach), see Credentials.
Who should read this
- Your agent calls external APIs at runtime (your own services, third-party SaaS) and needs an API Key / Token;
- You do not want secrets in cleartext in: application code, agent configuration and version history, the sandbox process, the model context, or event-stream logs;
- You are running a security or compliance review and need to explain precisely where secrets live along the chain.
The model: placeholder injection
The vault credential security model is "the secret never enters the sandbox; the gateway substitutes it on use":
| Stage | What happens |
|---|---|
| ① Configure (once) | The credential is stored in the vault: name: MY_API_KEY, value: sk-… (encrypted), allowed_hosts: api.example.com |
| ② Sandbox reads | os.environ["MY_API_KEY"] returns the placeholder BMA_SECRET_PLACEHOLDER_MY_API_KEY |
| ③ Sandbox sends | The request carries Authorization: Bearer BMA_SECRET_PLACEHOLDER_MY_API_KEY to api.example.com |
| ④ Gateway decision | Host hits allowed_hosts: the real value is substituted and forwarded; otherwise the placeholder passes through (no secret leaked) |
Gateway behavior boundaries
| Behavior | Description |
|---|---|
| Which header is substituted | Only Authorization. Placeholders in other headers of the same request (X-Api-Key, Api-Key, …) pass through unsubstituted |
| How the credential to substitute is located | By request host matching allowed_hosts, not by parsing the placeholder variable name — even a nonexistent placeholder variable name sent to a whitelisted host is substituted with that host's credential's real value |
| Multiple credentials on one host | The substitution result is not defined (one is chosen). Recommend one credential per host |
| Requests to non-whitelisted hosts | Not blocked (the network passes as usual); the Authorization header just keeps the placeholder — allowed_hosts governs "who the secret is sent to", not "where the network can go" |
| Interception mode | Transparent MITM: no proxy environment variables inside the sandbox; outbound HTTPS is intercepted at the network layer (the TLS handshake receives the gateway's self-signed certificate) |
| The gateway certificate | The sandbox preinstalls the gateway CA — default TLS validation passes; only environment-specific CA misconfiguration causes handshake failures (see the Step 5 notes) |
| Interception scope | All sessions (sessions without any vault attached still egress through the gateway) |
Two injection paths: a layered choice
Session environment variables environment_variables | Vault credentials | |
|---|---|---|
| How values are passed | Cleartext key-value pairs at session creation; echoed in the creation response | Credentials stored in the vault beforehand; sessions/deployments pass vault_ids |
os.environ in the sandbox | Real value | Placeholder BMA_SECRET_PLACEHOLDER_<name> |
| Where the real value appears | The sandbox process (and the event stream, if the agent prints it) | Only the credential store (encrypted) + the gateway's substitution instant |
| Fits | Non-sensitive config: API endpoints, log levels, flags, tenant IDs | High-sensitivity secrets: API keys, tokens, AccessKeys |
| Deployment support | Not supported (silently ignored) | Supported — vault_ids wired at the deployment level |
Prerequisites
- A Bailian workspace and its ID, one DashScope API Key;
- an agent with the bash tool (bash enabled in the built-in toolkit's
configs[]— see the getting-started page); - an external echo service for verification (this page uses
httpbin.org— its/getendpoint echoes your request headers back, perfect for observing "what did the other side actually receive").
Step 1 · Create the vault
id looks like vlt_01M1KPMXH45N5QQ00PQX8H9T9Q:
Step 2 · Create the credential: allowed_hosts required
Credentials live under the vault at the nested endpoint /vaults/{vault_id}/credentials (there is no top-level /credentials; it 404s).
Structure: allowed_hosts must sit inside auth.networking and is required:
secret_name: the environment variable name;os.environin the sandbox reads the placeholder by this name.secret_value: the real secret. Write-only — GET returns onlysecret_name/type/networkinginauth.networking.allowed_hosts: the egress whitelist, required. Wildcards supported (*.example.com). This is "declare at configuration time, once, which domains the secret may travel to".networking.type: optional (limitedis also accepted); behavior is governed byallowed_hosts.- The response
idis prefixedvcrd_(notcred_).
Step 3 · Attach the vault to a session
vault_ids on the deployment; every session a trigger generates carries them automatically) — see the production page.
Step 4 · Verification one: only a placeholder in the sandbox
Send a message having the agent print the environment variable:
tool_call_output):
printenv returning the real value; the correct check is Step 5's end-to-end request.
Step 5 · Verification two: egress requests substituted by the gateway (the happy path)
Have the agent call the whitelisted echo service with the placeholder as the Bearer Token:
printenv in the sandbox shows the placeholder; the receiving service gets the real value.
The negative path: a non-whitelisted host
The same command with the URL swapped to an echo service outside the whitelist (e.g. postman-echo.com):
Egress TLS validation: passes by default
The gateway performs transparent MITM: no proxy variables are visible in the sandbox, and outbound HTTPS is terminated at the gateway. The sandbox now preinstalls the gateway CA (SSL_CERT_FILE, CURL_CA_BUNDLE, and REQUESTS_CA_BUNDLE all point to it), so:
curlwith default validation → exits 0 normally;- Python
requests(defaultverify=True) → HTTP 200 normally;
SSLError) should you check whether the CA variables above point to the gateway certificate; -k / verify=False are troubleshooting tools, not routine steps. This holds for all sessions (vault-less ones included).
Step 6 · Session environment variables: first choice for non-sensitive config
Config that does not need secret-grade protection goes straight into session creation, cleartext into the sandbox:
printenv API_BASE_URLin the sandbox →https://new.example.com(the real value, directly usable by the agent's bash / python);- the creation response and
GET /sessions/{id}both echo the field verbatim (sibling ofmetadata) — note this makes it visible to anyone with session read access; keep it non-sensitive; - Deployment does not support it: passing
environment_variablestoPOST /deploymentsdoes not error but is silently ignored — the generated sessions have none. Scheduled pipelines must usevault_ids.
$API_BASE_URL, log at $LOG_LEVEL".
Step 7 · Operations and cleanup
- Rotation: the vault API has no dedicated rotation endpoint; updating
secret_valuedirectly is the current method (the update must carry the fullauthvalue). - Cleanup order: delete the credential (child) before the vault (parent) — deleting a vault does not cascade to its credentials (orphan data); the full lifecycle verb matrix is in the production page.
- The Webhook side exposes
vault.created / archived / deletedandvault_credential.created / archived / deletedevents for control-plane monitoring — see Advanced: Webhook Event Notifications.
FAQ
| Symptom | Cause | Fix |
|---|---|---|
Creating a credential returns 409 CREDENTIAL_AUTH_NETWORKING_ERROR "出口网络地址不能为空" | allowed_hosts missing, or not nested inside auth.networking | Write the structure as "networking": {"allowed_hosts": ["api.example.com"]} |
printenv shows BMA_SECRET_PLACEHOLDER_*; suspected "injection failed" | This is the correct behavior of the model | Verify end to end (Step 5): a whitelisted host receiving the real value means injection worked |
| curl exits 60 in the sandbox / requests raises SSLError | Egress gateway MITM; the certificate is not in the sandbox CA store | Add -k to curl, verify=False to requests (current behavior) |
| The receiving service still sees the placeholder | ① The target host is not in the credential's allowed_hosts; ② the placeholder was placed in a header other than Authorization (e.g. X-Api-Key, which the gateway does not substitute); ③ multiple credentials on that host — ambiguous substitution | ① Add the host to the whitelist; ② put the credential in the Authorization header; ③ one credential per host |
Deployment-triggered sessions have no environment_variables | Deployments do not support the parameter (silently ignored) | Use vault_ids on the scheduled path; or create the session application-side first |
| Want to check whether the secret leaked into the event stream | The sandbox prints only placeholders | Within sandbox output and outbound requests (before substitution), searching for the real value should yield 0 hits; BMA_SECRET_PLACEHOLDER shows the placeholders. Note: the response body of the called service (e.g. httpbin echoing Authorization back in the response JSON) enters the event stream as tool output — content the external service returns itself is outside this guarantee; use dummy values with echo endpoints |
Summary
- The placeholder model: secrets live only in the credential store (encrypted); the sandbox, the model context, and platform events see only
BMA_SECRET_PLACEHOLDER_*; the real value appears only in theAuthorizationheader at the egress instant, for a host matchingallowed_hosts. Content echoed back in external service responses (e.g. httpbin) is outside this guarantee. - The gateway substitutes only the
Authorizationheader, matched only by host: other headers pass through unsubstituted; non-whitelisted requests flow as usual but the secret stays home. - One credential per host: multiple credentials on one host make substitution ambiguous.
- TLS: sandbox egress goes through gateway MITM, but the gateway CA is preinstalled and default validation passes; investigate CA variables on certificate errors instead of disabling validation routinely.
- Layered choice: non-sensitive config via session environment variables (cleartext); high-sensitivity secrets and Deployment unattended pipelines via the vault.
Next steps
- Credentials: vault and credential operations in the console.
- Advanced: Productionize a Morning-Briefing Robot:
vault_idswiring on deployments. - Credential API: full parameters of the credential endpoints.