Proxy-Based Secret Management

Credential Injection

The proxy acts as a reverse proxy for configured credential routes. The agent sends plain HTTP to localhost, and the proxy strips the service prefix, injects credentials as HTTP headers, forwards over TLS, and streams responses back. The agent never sees the API key.

Two approaches

Proxy injection (recommended for API keys): the proxy automatically sets environment variables like OPENAI_BASE_URL=http://127.0.0.1:PORT/openai in the child environment. Even if the agent is compromised, it cannot extract credentials from its own environment or memory.

Environment variable injection (simpler but less secure): nono loads secrets from the keystore before the sandbox is applied, injects them as environment variables, then zeroises them from memory after the call to exec(). The sandbox blocks keystore access so the child cannot read additional secrets.

terminal
# Proxy injection — agent never sees the API key
nono run --allow-cwd --network-profile claude-code \
--credential openai -- my-agent
# Environment variable injection — simpler but less secure
nono run --allow-cwd --env-credential openai_api_key -- my-agent
# Multiple credentials
nono run --allow-cwd \
--env-credential openai_api_key,anthropic_api_key -- claude

Built-in Credential Routes

ServiceHeader
openaiAuthorization: Bearer {}
anthropicx-api-key: {}
geminix-goog-api-key: {}

Security Properties

  • Credentials never enter the sandbox
  • Session token isolation via X-Nono-Token header
  • Keystore-backed storage (Keychain/Secret Service)
  • Credential values stored in zeroising memory
  • Proxy strips existing auth headers before injection
  • Invalid tokens receive 407 Proxy Authentication Required

Credential storage

nono reads credentials from your platform keystore. On macOS, use the Keychain. On Linux, use the Secret Service API (gnome-keyring or similar). The target default attribute is required on Linux for the keyring crate to find the entry.

macOS Keychain
# macOS Keychain — store credentials
security add-generic-password -s "nono" -a "openai_api_key" -w
security add-generic-password -s "nono" -a "anthropic_api_key" -w
# Update existing
security add-generic-password -s "nono" -a "openai_api_key" -w "new-value" -U
# Delete
security delete-generic-password -s "nono" -a "openai_api_key"
# List all nono secrets
security dump-keychain | grep -A5 "nono"
Linux Secret Service
# Linux Secret Service — store credentials
echo -n "sk-..." | secret-tool store \
--label="nono: openai_api_key" \
service nono username openai_api_key target default
# Look up
secret-tool lookup service nono username openai_api_key target default
# Delete
secret-tool clear service nono username openai_api_key target default

1Password and Apple Passwords

nono integrates with 1Password via op:// URIs and Apple Passwords (macOS) via apple-password:// URIs. Both work with proxy injection and environment variable injection.

terminal
# 1Password — URI format: op://<vault>/<item>/<field>
nono run --allow . \
--env-credential-map 'op://Development/OpenAI/credential' \
OPENAI_API_KEY -- my-agent
# Apple Passwords (macOS) — URI format: apple-password://<server>/<account>
nono run --allow . \
--env-credential-map \
'apple-password://github.com/alice@example.com' \
GITHUB_PASSWORD -- my-agent

Custom credential routes

Define custom credential routes for any API. Four injection modes are supported: header (default), url_path, query_param, and basic_auth. Use underscores, not hyphens, in credential names as they are used to generate environment variables.

profiles/custom-credentials.json
{
"network": {
"custom_credentials": {
"telegram": {
"upstream": "https://api.telegram.org",
"credential_key": "telegram_bot_token",
"inject_mode": "url_path",
"path_pattern": "/bot{}/",
"path_replacement": "/bot{}/"
},
"google_maps": {
"upstream": "https://maps.googleapis.com",
"credential_key": "google_maps_api_key",
"inject_mode": "query_param",
"query_param_name": "key"
},
"private_api": {
"upstream": "https://api.example.com",
"credential_key": "example_basic_auth",
"inject_mode": "basic_auth"
}
}
}
}
profiles/1password-proxy.json
{
"meta": { "name": "my-agent-secure" },
"network": {
"custom_credentials": {
"openai": {
"upstream": "https://api.openai.com/v1",
"credential_key": "op://Development/OpenAI API Key/credential",
"env_var": "OPENAI_API_KEY",
"inject_header": "Authorization",
"credential_format": "Bearer {}"
}
},
"credentials": ["openai"]
}
}

Profile-based configuration

Both proxy and environment variable credentials can be configured in profiles. You can mix both approaches in a single profile — use proxy injection for LLM API keys and environment variable injection for things like database passwords.

profiles/env-credentials.json
{
"meta": { "name": "my-agent" },
"env_credentials": {
"openai_api_key": "OPENAI_API_KEY",
"anthropic_api_key": "ANTHROPIC_API_KEY",
"custom_token": "MY_CUSTOM_TOKEN"
}
}
profiles/mixed-mode.json
{
"meta": { "name": "mixed-example" },
"env_credentials": {
"op://Infrastructure/Database/password": "DATABASE_PASSWORD"
},
"network": {
"custom_credentials": {
"openai": {
"upstream": "https://api.openai.com/v1",
"credential_key": "op://Development/OpenAI/credential",
"env_var": "OPENAI_API_KEY",
"inject_header": "Authorization",
"credential_format": "Bearer {}"
}
},
"credentials": ["openai"]
}
}

Audit logging

Reverse proxy requests are logged with the service name and status code, but credential values are never logged:

audit log
ALLOW REVERSE openai POST /v1/chat/completions -> 200
ALLOW REVERSE anthropic POST /v1/messages -> 200

See the credential injection docs for full details including headless Linux setups and WSL2 limitations.

Get started with nono

Runtime safety infrastructure that works on macOS, Linux, Windows, and in CI.