How to Securely Manage Secrets and Environment Variables in Agent-Native Projects
Agent-native projects store all runtime secrets in an AES-256-GCM encrypted vault accessed via saveCredential and resolveCredential, while static analysis guard scripts enforce that only deployment-level variables like DATABASE_URL may reside in environment files.
The BuilderIO/agent-native framework eliminates hard-coded secrets by storing sensitive data in an encrypted database vault rather than source files or plain-text environment variables. When you need to securely manage secrets and environment variables in agent-native applications, the platform provides a credential API that encrypts values with user, organization, or workspace scope while enforcing compliance through automated guard scripts.
Understanding the Vault Architecture
Agent-native persists runtime secrets in an encrypted app_secrets table rather than in process.env. The encryption layer in packages/core/src/credentials/index.ts uses AES-256-GCM to seal values at rest, decrypting them only when resolveCredential is invoked. Master encryption keys derived from SECRETS_ENCRYPTION_KEY or BETTER_AUTH_SECRET—defined in packages/core/src/secrets/crypto.ts—protect the vault, and the application will fail to start if these are absent.
This architecture ensures that API keys, OAuth tokens, and service credentials never appear in source control or shell environments. Instead, the framework scopes each secret to a specific user, organization, or workspace, enabling fine-grained access control across multi-tenant deployments.
The Core Credential API
The packages/core/src/credentials/index.ts module exposes three primary functions for secret lifecycle management.
Storing Secrets with saveCredential
To encrypt and persist a secret, invoke saveCredential with a key identifier and the plaintext value. The function automatically encrypts the payload and inserts it into the app_secrets table.
import { saveCredential } from "@agent-native/core/credentials";
await saveCredential("OPENAI_API_KEY", "sk-mySecret", {
// Optional: default scope is the current user
// scope: "org",
// orgId: "12345"
});
The scope parameter determines visibility: omitting it defaults to the authenticated user, while "org" or "workspace" allows shared access across team boundaries.
Retrieving Secrets with resolveCredential
When your action or route requires a secret, call resolveCredential to decrypt the value on demand. The function traverses the scope hierarchy—user, then organization, then workspace—returning the most specific match.
import { resolveCredential } from "@agent-native/core/credentials";
const apiKey = await resolveCredential("OPENAI_API_KEY");
// Returns decrypted plaintext ready for the provider SDK
Scoping and Deletion
To rotate or revoke credentials, use deleteCredential to remove the encrypted row, then re-save with a new value if needed.
import { deleteCredential } from "@agent-native/core/credentials";
await deleteCredential("OPENAI_API_KEY");
// Re-save with rotated value via saveCredential
Deletion is immediate and scope-aware; calling it without specifying a scope removes only the user-scoped entry.
Development vs. Production Patterns
During local development, agent-native loads the workspace root .env into process.env via packages/core/src/vite/client.ts for convenience. This allows rapid iteration without vault setup, but the framework treats these values as temporary placeholders.
In production, the guard script located at scripts/guard-no-env-credentials.mjs performs static analysis to block any read of process.env.<KEY> inside credential-related logic. Only whitelisted deployment variables—such as DATABASE_URL—are permitted. This enforcement guarantees that production builds cannot accidentally leak secrets through environment variables.
Deployment-Level Environment Variables
While runtime secrets belong in the vault, infrastructure configuration remains in environment variables. The following variables are required and must be defined at deploy time:
SECRETS_ENCRYPTION_KEY– Master key for the AES-256-GCM vaultBETTER_AUTH_SECRET– Secondary encryption seed for credential operationsDATABASE_URL– Connection string for the application database
These values are validated in packages/core/src/secrets/crypto.ts. The guard script explicitly allows process.env access for these keys while rejecting all others in sensitive code paths.
Enforcement Through Guard Scripts
The repository ships with scripts/guard-no-env-credentials.mjs, a static analysis tool that runs in CI to prevent credential leakage. It scans for patterns like process.env.OPENAI_API_KEY and errors if found outside of whitelisted configuration files. This automation ensures that developers cannot bypass the vault API, maintaining the security invariant that plaintext secrets never enter source control.
Summary
- Use the vault API – Store all runtime secrets via
saveCredentialand retrieve them withresolveCredentialfrompackages/core/src/credentials/index.ts. - Scope appropriately – Assign secrets to user, organization, or workspace contexts to enforce least-privilege access.
- Protect master keys – Provide
SECRETS_ENCRYPTION_KEYandBETTER_AUTH_SECRETat deployment; the app refuses to start without them. - Reserve
.envfor infrastructure – Only deployment-level variables likeDATABASE_URLmay useprocess.env; runtime secrets trigger guard-script failures. - Automate compliance – Run
scripts/guard-no-env-credentials.mjsin CI to block accidental credential commits.
Frequently Asked Questions
What happens if I try to read a secret from process.env in production?
The static analysis guard script in scripts/guard-no-env-credentials.mjs will fail the build with an error. Agent-native explicitly forbids reading process.env.<SECRET> in credential-related code, allowing only whitelisted variables like DATABASE_URL to be accessed via environment variables.
How do I rotate an existing API key in the vault?
First call deleteCredential("KEY_NAME") to remove the old encrypted entry, then immediately invoke saveCredential("KEY_NAME", "new-value") with the rotated token. The change takes effect immediately for subsequent resolveCredential calls.
Can I share secrets across my entire organization?
Yes. Pass scope: "org" and an orgId when calling saveCredential. This stores the secret at the organization level in the app_secrets table, making it available to all members while still encrypting it at rest with AES-256-GCM.
Is the .env file safe for local development secrets?
The workspace root .env is loaded by packages/core/src/vite/client.ts for convenience during development, but these values are never permitted in production builds. Treat .env as a local-only scratchpad; always migrate secrets to the vault before deploying.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →