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.
Step 3: send it through a link that forgets
When you do have to send a password or a connection string:
- Open EnvShare and paste the connection string, or the host, user and password on separate lines.
- Set one read and a short expiry, from one hour to one day.
- 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.
- 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. - 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-fullboth 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 UNTILis a safety net, not a substitute. - Rotate any shared password the person saw, such as an application user's password that appeared in a
.envfile 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
\passwordrather than typed into SQL VALID UNTILand 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 access | What to give them | How to send it |
|---|---|---|
| A developer debugging production for an afternoon | A read-only role with VALID UNTIL set to tomorrow | A one-read link, with the password given on a call |
| An analyst running reports | A read-only role on a replica | A one-read link |
| A new service | Its own role, stored in your platform's secret store | No human handoff |
| An agency taking over the project | Their own admin role, then a rotation of yours | A one-read link with a password |
| A client giving you access to their database | A dedicated role, not their admin login | A secret request |
When a link is the wrong tool
- 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
- PostgreSQL documentation: connection strings, CREATE ROLE, ALTER ROLE, GRANT
- OWASP, Database Security Cheat Sheet
- AWS, IAM database authentication for MariaDB, MySQL, and PostgreSQL
- HashiCorp, Database secrets engine
- GitHub Docs, Removing sensitive data from a repository
- MDN, URI fragment