Skip to main content

Advanced: Credential Vault Security and the Egress Gateway

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.
If you only need non-sensitive config (API endpoints, log levels, feature flags), use session environment variables directly — no need for the full page.

The model: placeholder injection

The vault credential security model is "the secret never enters the sandbox; the gateway substitutes it on use":
StageWhat happens
① Configure (once)The credential is stored in the vault: name: MY_API_KEY, value: sk-… (encrypted), allowed_hosts: api.example.com
② Sandbox readsos.environ["MY_API_KEY"] returns the placeholder BMA_SECRET_PLACEHOLDER_MY_API_KEY
③ Sandbox sendsThe request carries Authorization: Bearer BMA_SECRET_PLACEHOLDER_MY_API_KEY to api.example.com
④ Gateway decisionHost hits allowed_hosts: the real value is substituted and forwarded; otherwise the placeholder passes through (no secret leaked)
The real secret exists in exactly two places: the credential store (encrypted at rest) and the instant the gateway substitutes it for a whitelisted host; every platform-controlled observable point in the chain (sandbox process, model context, platform events) sees only the placeholder. The one exception is the response body of the called service — content the callee echoes back (e.g. httpbin returning the Authorization) enters tool output; that is the external service's behavior, outside platform control. Even if the agent is prompt-injected into sending the credential to a hostile domain, the gateway will not substitute — the recipient gets a meaningless placeholder.

Gateway behavior boundaries

BehaviorDescription
Which header is substitutedOnly Authorization. Placeholders in other headers of the same request (X-Api-Key, Api-Key, …) pass through unsubstituted
How the credential to substitute is locatedBy 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 hostThe substitution result is not defined (one is chosen). Recommend one credential per host
Requests to non-whitelisted hostsNot 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 modeTransparent 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 certificateThe sandbox preinstalls the gateway CA — default TLS validation passes; only environment-specific CA misconfiguration causes handshake failures (see the Step 5 notes)
Interception scopeAll sessions (sessions without any vault attached still egress through the gateway)

Two injection paths: a layered choice

Session environment variables environment_variablesVault credentials
How values are passedCleartext key-value pairs at session creation; echoed in the creation responseCredentials stored in the vault beforehand; sessions/deployments pass vault_ids
os.environ in the sandboxReal valuePlaceholder BMA_SECRET_PLACEHOLDER_<name>
Where the real value appearsThe sandbox process (and the event stream, if the agent prints it)Only the credential store (encrypted) + the gateway's substitution instant
FitsNon-sensitive config: API endpoints, log levels, flags, tenant IDsHigh-sensitivity secrets: API keys, tokens, AccessKeys
Deployment supportNot supported (silently ignored)Supported — vault_ids wired at the deployment level
The rule: don't need maximum security → environment variables; need maximum security → the vault. Scheduled/unattended pipelines (Deployment) currently support only the vault.

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 /get endpoint echoes your request headers back, perfect for observing "what did the other side actually receive").
Agreed environment variables:
export AGENTSTUDIO_URL="https://{workspace_id}.cn-beijing.maas.aliyuncs.com/api/v1/agentstudio"
export DASHSCOPE_API_KEY="<your DashScope API Key>"
export AGENT_ID="agent_xxx"          # the agent with bash
export MY_API_KEY="sk-DUMMY_KEY_FOR_SMOKE_TEST"   # a dummy value for the demo

Step 1 · Create the vault

curl -X POST "$AGENTSTUDIO_URL/vaults" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "生产环境密钥库",
    "metadata": { "team": "operations" }
  }'
The response id looks like vlt_01M1KPMXH45N5QQ00PQX8H9T9Q:
export VAULT_ID="vlt_xxx"

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:
curl -X POST "$AGENTSTUDIO_URL/vaults/$VAULT_ID/credentials" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "display_name": "天气服务 API Key",
    "auth": {
      "type": "environment_variable",
      "secret_name": "MY_API_KEY",
      "secret_value": "'"$MY_API_KEY"'",
      "networking": { "allowed_hosts": ["httpbin.org", "*.httpbin.org"] }
    }
  }'
Points:
  • secret_name: the environment variable name; os.environ in the sandbox reads the placeholder by this name.
  • secret_value: the real secret. Write-only — GET returns only secret_name / type / networking in auth.
  • 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 (limited is also accepted); behavior is governed by allowed_hosts.
  • The response id is prefixed vcrd_ (not cred_).
Two common errors:
Omitting allowed_hosts → 409 CREDENTIAL_AUTH_NETWORKING_ERROR "出口网络地址不能为空". The old structure (networking: {type: "unrestricted"} only, top-level allowed_hosts, or no networking at all) is rejected wholesale.
auth.type still supports only environment_variable (secret / static_bearer / mcp_oauth all return 409 CREDENTIAL_AUTH_TYPE_ERROR).
export CRED_ID="vcrd_xxx"

Step 3 · Attach the vault to a session

curl -X POST "$AGENTSTUDIO_URL/sessions" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent": "'"$AGENT_ID"'",
    "title": "密钥注入验证",
    "vault_ids": [ "'"$VAULT_ID"'" ]
  }'
Wiring in a Deployment is identical (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:
curl -X POST "$AGENTSTUDIO_URL/sessions/$SESSION_ID/events" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": [
      { "type": "message", "role": "user",
        "content": [ { "type": "text", "text": "Run exactly this bash command and report the raw output verbatim, nothing else: printenv MY_API_KEY" } ] }
    ]
  }'
Event stream (tool_call_output):
{"stdout":"BMA_SECRET_PLACEHOLDER_MY_API_KEY\n","stderr":"","exit_code":0,"interrupted":false}
This is the expected behavior — the sandbox holds only the placeholder. So "verify the secret was injected" cannot rely on 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:
curl -X POST "$AGENTSTUDIO_URL/sessions/$SESSION_ID/events" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": [
      { "type": "message", "role": "user",
        "content": [ { "type": "text", "text": "Run exactly this bash command and report the raw output verbatim, nothing else: curl -sk --max-time 30 -H \"Authorization: Bearer $MY_API_KEY\" https://httpbin.org/get" } ] }
    ]
  }'
stdout (httpbin echoes the request headers it received):
{
  "headers": {
    "Authorization": "Bearer sk-DUMMY_KEY_FOR_SMOKE_TEST",
    "Host": "httpbin.org",
    ...
  },
  "origin": "39.105.83.17"
}
Substitution happens only at the egress instant: 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):
{
  "headers": {
    "authorization": "Bearer BMA_SECRET_PLACEHOLDER_MY_API_KEY",
    "host": "postman-echo.com",
    ...
  }
}
The recipient holds a meaningless placeholder — the real secret never leaked. The request itself is not blocked (HTTP 200 as usual) — the whitelist's semantics are "who the secret is sent to", not "where the network can go".

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:
  • curl with default validation → exits 0 normally;
  • Python requests (default verify=True) → HTTP 200 normally;
Use default validation as-is; do not disable it preemptively. Only when a specific environment hits certificate errors (curl exit 60, 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:
curl -X POST "$AGENTSTUDIO_URL/sessions" \
  -H "Authorization: Bearer $DASHSCOPE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "agent": "'"$AGENT_ID"'",
    "environment_id": "'"$ENV_ID"'",
    "title": "客户咨询 #1234",
    "environment_variables": {
      "API_BASE_URL": "https://new.example.com",
      "LOG_LEVEL": "info"
    }
  }'
Behavior:
  • printenv API_BASE_URL in 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 of metadata) — note this makes it visible to anyone with session read access; keep it non-sensitive;
  • Deployment does not support it: passing environment_variables to POST /deployments does not error but is silently ignored — the generated sessions have none. Scheduled pipelines must use vault_ids.
Reference variable names directly in the agent's prompt, e.g.: "call the API at $API_BASE_URL, log at $LOG_LEVEL".

Step 7 · Operations and cleanup

  • Rotation: the vault API has no dedicated rotation endpoint; updating secret_value directly is the current method (the update must carry the full auth value).
  • 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 / deleted and vault_credential.created / archived / deleted events for control-plane monitoring — see Advanced: Webhook Event Notifications.

FAQ

SymptomCauseFix
Creating a credential returns 409 CREDENTIAL_AUTH_NETWORKING_ERROR "出口网络地址不能为空"allowed_hosts missing, or not nested inside auth.networkingWrite the structure as "networking": {"allowed_hosts": ["api.example.com"]}
printenv shows BMA_SECRET_PLACEHOLDER_*; suspected "injection failed"This is the correct behavior of the modelVerify end to end (Step 5): a whitelisted host receiving the real value means injection worked
curl exits 60 in the sandbox / requests raises SSLErrorEgress gateway MITM; the certificate is not in the sandbox CA storeAdd -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_variablesDeployments 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 streamThe sandbox prints only placeholdersWithin 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 the Authorization header at the egress instant, for a host matching allowed_hosts. Content echoed back in external service responses (e.g. httpbin) is outside this guarantee.
  • The gateway substitutes only the Authorization header, 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

Agent Context
Agent Governance & Evolution
Store
Application Marketplace