The problem: agents with a database password
- Many AI agents (an LLM that can call tools) reach a database with a static password stored in an environment variable or secret.
- That is worse for an agent than for a normal service:
- Prompt injection can steer the agent into using whatever access it holds.
- A long-lived secret can leak through logs, traces or tool output.
- A shared credential gives no per-agent attribution in audit logs.
- I've spent years removing static credentials from infrastructure pipelines by replacing stored secrets with platform-issued identities. When agents started needing data access, I wanted to know whether the same pattern holds up when the caller is an LLM that can be talked into things.
The pattern: workload identity instead of a password
- The agent's runtime gets an identity from the platform rather than a secret: for example an AWS IAM role via EKS IRSA or Pod Identity, or an Azure managed identity / workload identity federation.
- It exchanges that identity for short-lived database credentials, for example:
- Amazon RDS IAM database authentication (tokens are valid for 15 minutes)
- Microsoft Entra authentication for Azure Database for PostgreSQL / Azure SQL
- HashiCorp Vault dynamic database secrets with a short TTL
- At the database, the agent gets its own least-privilege role: read-only where possible, scoped to the schemas or tables it needs.
Agent pod ──(service account)──► Platform identity (IAM role / managed identity)
│
▼
Short-lived token (15 min)
│
▼
Database ──► agent_reader role (SELECT on one schema)
The reference build
- Setup: a small Python agent with one tool,
run_query, running as a pod on Amazon EKS. The pod's service account is mapped to an IAM role with EKS Pod Identity. The database is an Amazon RDS for PostgreSQL instance with IAM database authentication enabled. - Wiring: the IAM role is allowed to connect as exactly one database user. That user has
the
rds_iamgrant andSELECTon a single reporting schema, and nothing else. The tool generates a fresh auth token for every new connection. No password exists anywhere in the pod, the image or the environment.
IAM policy: the agent can connect as one database user only.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": "rds-db:connect",
"Resource": "arn:aws:rds-db:us-east-1:111122223333:dbuser:db-ABCDEFGHIJKL/agent_reader"
}]
}
Database role: read-only, one schema.
CREATE USER agent_reader;
GRANT rds_iam TO agent_reader;
GRANT USAGE ON SCHEMA reporting TO agent_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA reporting TO agent_reader;
The agent's connection: a token instead of a password.
import boto3, psycopg2
def connect():
token = boto3.client("rds").generate_db_auth_token(
DBHostname=HOST, Port=5432, DBUsername="agent_reader", Region="us-east-1")
return psycopg2.connect(host=HOST, port=5432, dbname="app", user="agent_reader",
password=token, sslmode="verify-full",
sslrootcert="global-bundle.pem")
Things to watch:
- The token only matters at connect time. A connection opened with a valid token stays open after the token expires. Use short connection lifetimes or recycle the pool, or "15 minutes" isn't really your exposure window.
- IAM authentication requires SSL. Use
verify-fullwith the RDS CA bundle, not justrequire. - The resource ARN uses the DB resource ID (
db-…), not the instance name. Getting this wrong is the most common cause of a silent "PAM authentication failed". - New-connection rate: AWS documents a practical limit on new IAM-authenticated connections per second, so pool connections rather than opening one per query.
What it solves
- No static secret to leak, store or rotate.
- Credentials expire on their own, in minutes rather than months.
- Every connection is attributable to a specific workload identity in the audit logs.
- Revoking access is an identity change, not a password rotation across deployments.
What it doesn't solve
- Prompt injection still works within the token's lifetime. Short-lived is not the same as least privilege, so keep the database role minimal.
- Data can still leave through the agent's answers. Consider read replicas, query allow-lists, or a tool layer that runs parameterised queries instead of raw SQL.
- It identifies the agent, not the user it acts for. Acting on behalf of a specific user (delegation, OAuth 2.0 token exchange, RFC 8693) is the next problem.
- Long-lived connections stretch the window. Short-lived credentials only help if connections are short-lived too (see "Things to watch").
Checklist
- [ ] No database password in environment variables, code or container images
- [ ] Platform-issued workload identity for the agent
- [ ] Short-lived database credentials (minutes)
- [ ] Connection lifetimes capped, so expired tokens don't leave connections open
- [ ] A dedicated least-privilege database role per agent
- [ ] Audit logs tied to the workload identity
- [ ] Guardrails on what the agent can query
Further reading
- AWS: IAM database authentication for MariaDB, MySQL, and PostgreSQL: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html
- AWS: EKS Pod Identity: https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html
- Microsoft: Microsoft Entra authentication with Azure Database for PostgreSQL (Microsoft Learn)
- HashiCorp Vault: Database secrets engine: https://developer.hashicorp.com/vault/docs/secrets/databases
- OWASP Top 10 for LLM Applications (Prompt Injection; Excessive Agency): https://genai.owasp.org
- OWASP Non-Human Identities Top 10
- RFC 8693, OAuth 2.0 Token Exchange: https://datatracker.ietf.org/doc/html/rfc8693