Skip to content

Developer Security

How to Share Database Credentials Safely

A connection string is a whole login in one line. How to create a scoped database user, send it through an expiring encrypted link, and revoke it after.

By Updated
First published 5 min read

Database access is one of the most valuable things a team hands out, and one of the most casually shared. A connection string goes into a chat message "just for today" and stays there long after the person who needed it has moved on.

This guide is about doing it properly: giving the right person the least access that works, sending it without leaving copies behind, and taking it back afterwards.

Why a connection string is worse than a password

A PostgreSQL connection URI can carry everything needed to log in, in the form postgresql://user:password@host:5432/dbname (PostgreSQL documentation). Other databases use similar formats. One line holds the host, the account and its password, and if the database accepts connections from the internet, that line works for whoever reads it, wherever they are.

That's what makes the usual shortcuts risky. Chat messages are kept and synced, emails sit in mailboxes and backups, and a DATABASE_URL in a shared document gets copied into places nobody tracks.

Step 1: don't share your login, create one for them

The best credential to share is one that exists only for this person and this job. The OWASP Database Security Cheat Sheet recommends that application accounts have only the permissions they need, that each environment uses separate accounts, and that applications never use built-in accounts such as root, sa or SYS. The same logic applies to people.

In PostgreSQL, a time-limited, read-only role for a contractor looks like this:

CREATE ROLE contractor_alex WITH LOGIN CONNECTION LIMIT 2 VALID UNTIL '2026-11-30';
GRANT CONNECT ON DATABASE app TO contractor_alex;
GRANT USAGE ON SCHEMA public TO contractor_alex;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO contractor_alex;

Then set the password from psql with \password contractor_alex. PostgreSQL's ALTER ROLE documentation warns that a password written into the command itself is sent to the server in cleartext and can end up in command history or server logs, and recommends \password for that reason. Make the password long and random, for example with a password generator.

VALID UNTIL makes the password stop working after a date, though it applies only to password logins (CREATE ROLE). GRANT hands out specific privileges on specific objects (GRANT); note that ON ALL TABLES covers the tables that exist now, not ones created later. If read-only isn't enough, widen access table by table. MySQL, SQL Server and managed databases have equivalents.

Two other ways to make access smaller:

  • A read replica for analysts and debugging, so a mistake can't touch the primary.
  • A staging copy for anyone who doesn't truly need production data.

Step 2: prefer credentials that expire on their own

Some platforms can take the long-lived password out of the handoff entirely.

  • Amazon RDS IAM database authentication replaces the database password with an authentication token that has a lifetime of 15 minutes (AWS documentation). Access is managed through IAM, and there's nothing long-lived to send.
  • HashiCorp Vault's database secrets engine generates credentials on demand, unique to each client, and revokes them when their lease expires (Vault documentation).

If your setup supports something like this, use it, and keep the rest of this guide for the cases where a password still has to travel.

When you do have to send a password or a connection string:

  1. Open EnvShare and paste the connection string, or the host, user and password on separate lines.
  2. Set one read and a short expiry, from one hour to one day.
  3. For production access, add a password and send it another way, such as on a call. The host name can live in the ticket; the password shouldn't.
  4. Send the link. It's encrypted in your browser with AES-256-GCM, the key stays after the # in the link, which browsers don't send to servers (MDN), and the ciphertext is deleted after the read.
  5. Ask the recipient to confirm they can connect, and check My links to see that the link was opened.

On the receiving end, for example when a client needs to give your agency access to their database, send them a secret request instead. Their browser encrypts the credentials to a key only your inbox link can open.

Step 4: lock down the network path

A leaked password matters less if the database can't be reached.

  • Don't expose the database to the internet unless you have to. Use private networking, a VPN or bastion host, or an IP allowlist.
  • Require TLS. OWASP recommends configuring the database to allow only encrypted connections. On the client side, PostgreSQL's sslmode=verify-full both requires TLS and checks that the server's certificate matches the host name you connected to.

Step 5: take access back when the work ends

  • Disable or remove the role, for example with ALTER ROLE contractor_alex NOLOGIN;, or drop it once nothing depends on it. VALID UNTIL is a safety net, not a substitute.
  • Rotate any shared password the person saw, such as an application user's password that appeared in a .env file they received.
  • If a credential reached git, rotate it first and clean up history afterwards; GitHub's guide to removing sensitive data notes that rewriting history can't remove copies in existing clones.

What to put in the handoff

Connection details are easier to use, and easier to clean up, when they arrive with context. A note like this, sent through the one-read link, covers it:

Host:     db.internal.example.com  (VPN required)
Port:     5432
Database: app
User:     contractor_alex  (read-only, expires 30 Nov)
Password: <long random value>
TLS:      sslmode=verify-full
Questions: reply to the person who sent this link

The expiry date in the note tells the recipient when their access ends, and reminds you what to check when it does.

Quick checklist

  • A dedicated role for this person, with only the privileges the job needs
  • A long random password, set with \password rather than typed into SQL
  • VALID UNTIL and a connection limit on the role
  • Network access restricted, and TLS required
  • Sent through a one-read link with a short expiry, with the password for production access given another way
  • A reminder in your calendar to disable the role when the work ends

Who gets what

Who needs accessWhat to give themHow to send it
A developer debugging production for an afternoonA read-only role with VALID UNTIL set to tomorrowA one-read link, with the password given on a call
An analyst running reportsA read-only role on a replicaA one-read link
A new serviceIts own role, stored in your platform's secret storeNo human handoff
An agency taking over the projectTheir own admin role, then a rotation of yoursA one-read link with a password
A client giving you access to their databaseA dedicated role, not their admin loginA secret request
  • Application config belongs in a secrets manager or your deployment platform's environment settings, not in links people open.
  • Ongoing team access is better handled with individual accounts, single sign-on or IAM-based authentication where your database supports it, or a shared vault in a password manager.
  • Environments that require approved tools and audit trails should use those. ProtectedShare records that a link was opened, not who opened it.

Sources