Credentials

Agent tasks frequently need to authenticate with external APIs. The credential system provides secure, encrypted storage with split-trust key management and automatic injection at execution time.

Storage Model

Credentials are stored as a Firestore subcollection under each task:

agent_tasks/{task_id}/credentials/{credential_key}

Each credential document contains:

Field
Type
Description

credential_key

string

Credential name, referenced by the agent in api_call tool invocations (e.g., LUNARCRUSH_API_KEY)

service

string

The integration service this credential belongs to (e.g., lunarcrush, slack)

status

string

Connection status (connected)

credentials_encrypted

bytes

KMS-encrypted ciphertext blob

kms_key_version

string

Full KMS key resource name used for encryption

auth_type

string

One of bearer, header, query_param

auth_header_name

string

Custom header name (e.g., Authorization, X-Api-Key)

scopes

array

OAuth scopes, if applicable

token_expires_at

timestamp

Token expiry for OAuth credentials (null for non-expiring keys)

created_at

timestamp

When the credential was stored

updated_at

timestamp

Last update timestamp

The plaintext credential value is never stored in Firestore. Only the KMS-encrypted ciphertext is persisted.

Credential Types

Bearer Token

The credential value is injected as an Authorization: Bearer header on outbound requests.

Use this for APIs that accept standard OAuth2 bearer tokens or API keys in the Authorization header. Most modern APIs (Slack, Notion, Airtable, LunarCrush) use this pattern.

Custom Header (API Key)

The credential value is injected as a custom HTTP header. The auth_header_name field specifies which header to set.

Use this for APIs that expect keys in non-standard headers (e.g., CoinMarketCap uses X-CMC_PRO_API_KEY, NewsAPI uses X-Api-Key, CoinGecko uses x-cg-demo-api-key).

Query Parameter

The credential value is appended as a URL query parameter named api_key.

Use this for APIs that accept keys as URL parameters (e.g., Alpha Vantage, OpenWeather). Note that query parameter auth is less secure than header-based auth because credentials may appear in server access logs on the target API side. Prefer bearer or header when the API supports it.

OAuth (Future)

OAuth credential support is planned for Phase 3. This will enable agents to authenticate with services that require OAuth flows (e.g., Google Sheets, GitHub) using refresh tokens stored alongside the encrypted access token. The executor will handle token refresh automatically when access tokens expire.

KMS Encryption (Split Trust)

Credentials are protected by Google Cloud KMS with a split-trust model that ensures no single service account can both encrypt and decrypt credential material.

Service Account Permissions

Service Account
GCP Project
KMS Permission
Purpose

Bridge SA ([email protected])

inference-platform

cloudkms.cryptoKeyVersions.useToEncrypt

Encrypts credentials during storage via Cloud Function

Executor SA ([email protected])

inference-agents

cloudkms.cryptoKeyVersions.useToDecrypt

Decrypts credentials during task execution

Neither service account has the other's permission. The bridge SA (running in the main platform project) cannot decrypt credentials, and the executor SA (running in the agents project) cannot encrypt or overwrite them. This separation means:

  • A compromised bridge service cannot read existing credentials.

  • A compromised executor cannot inject new credentials.

  • Both services would need to be compromised simultaneously to both write and read credential material.

KMS Key Configuration

KMS Property
Value

Key Ring

agent-keys

Key Name

credentials

Location

us-central1

Full Resource Name

projects/inference-agent/locations/us-central1/keyRings/agent-keys/cryptoKeys/credentials

Protection Level

Software (HSM available for upgrade)

Rotation Period

90 days (automatic)

Encrypted Payload Format

The plaintext credential is a JSON object that is serialized, encrypted, and stored as a binary blob:

The encrypt_credentials() function serializes this JSON, encrypts it via kms.encrypt(), and returns the ciphertext bytes. The decrypt_credentials() function reverses the process, calling kms.decrypt() and deserializing the JSON.

Credential Lifecycle

End-to-End Flow: Web UI to Execution

Creating Credentials

Credentials are created through a Cloud Function (encryptCredential) triggered from the frontend:

  1. User enters the credential value in the web UI.

  2. Frontend calls the Cloud Function with the task ID, credential key, auth type, and plaintext value.

  3. The Cloud Function (running as bridge SA) encrypts the value via Cloud KMS.

  4. The encrypted ciphertext is written to Firestore at agent_tasks/{task_id}/credentials/{key}.

  5. The plaintext value is discarded from memory; it never reaches Firestore.

The frontend never sends the plaintext credential to Firestore directly. The Cloud Function acts as a secure intermediary.

Updating Credentials

Updating a credential follows the same encrypt-and-write flow. The old ciphertext is overwritten with the new encrypted value. There is no version history for credential values.

Deleting Credentials

Credentials are deleted through a Cloud Function (deleteCredential) that removes the document from the subcollection. When a task is deleted, all credentials in its subcollection are cascade-deleted as well.

Rotation

To rotate a credential, update it with the new value. The old ciphertext is overwritten. KMS key rotation (every 90 days) is handled automatically by Cloud KMS and does not require re-encryption of existing credentials -- Cloud KMS retains prior key versions for decryption.

Runtime Injection

At execution time, the agent executor:

  1. Loads all credential documents from the task's subcollection via load_task_credentials().

  2. Decrypts each encrypted blob using Cloud KMS via decrypt_credentials().

  3. Merges the decrypted values with metadata from the credential document (service, auth_type, auth_header_name).

  4. Holds the complete decrypted map in memory for the duration of the execution.

  5. Builds a credential context for the LLM's system prompt listing available credential names (not values).

  6. When the agent invokes api_call with a credential parameter, the executor looks up the credential name, retrieves the decrypted value, and injects it into the outbound HTTP request according to the auth_type.

Credential Context

The LLM never receives raw credential values. It receives a list of available credential names so it knows which credential parameter to pass to api_call:

The agent references these names in tool calls, and the executor handles the actual injection:

Security Model

Credential Isolation

Credentials are per-task, not shared across tasks. Even if the same user creates multiple tasks that access the same API, each task has its own copy of the credential in its own subcollection. This prevents cross-task credential leakage and allows fine-grained revocation.

Memory-Only Decryption

Decrypted credentials exist only in executor memory during a single execution. They are:

  • Never written to disk.

  • Never written to logs (the executor uses redacted logging throughout).

  • Never persisted in any intermediate storage.

  • Garbage collected when the execution completes and the in-memory dict goes out of scope.

Exfiltration Prevention

Outbound request bodies, URLs, and custom headers are scanned for credential values before transmission. The scanner checks for plaintext, base64-encoded, and URL-encoded forms of every decrypted credential. If a credential value is detected in a location where the agent placed it (as opposed to the executor's legitimate injection point), the request is blocked. See Security for full details.

Safety Preamble

The executor prepends a safety preamble to every LLM call that explicitly instructs the model:

  • Never include credential values in request bodies, URLs, or custom headers.

  • Credentials are injected automatically via the auth header.

  • Only make API calls that directly serve the task described in the user message.

Last updated