Skip to main content
Back to all posts

How to Share Database Credentials with a Contractor Safely

โ€ขSnapSend Team

Last year I watched a friend's startup lose a customer's data because a contractor's read-only database password โ€” sent six months earlier in a Slack DM โ€” ended up in a Slack workspace export that the contractor had downloaded for "documentation purposes." The contractor's laptop was stolen. The data was on it.

The database had MFA on every other access path. The credential in the Slack message bypassed all of it. This is the canonical contractor-credential failure mode, and it's preventable with about ten minutes of process change.

This article walks through how to share database credentials safely, with the specific moves that prevent the laptop-theft failure mode and the mass-rotation pain that often comes with contractor offboarding.

The pattern most teams use, and why it fails

The default contractor onboarding flow:

  1. Lead engineer copies the production-ish DB password from 1Password.
  2. Pastes it into a Slack DM with the contractor.
  3. Says "delete this after you copy it."
  4. Goes back to work.

What's wrong with this:

  • The contractor is sharing the same read_user account that three employees and two other contractors are also using. When this contractor's laptop gets stolen, you have to rotate the password and re-distribute to everyone.
  • The Slack DM persists in indexes, integrations, and notification caches.
  • You have no way to verify whether the contractor actually copied and deleted, or just left the message.
  • You have no audit trail of which contractor had access at which time.

The fix is structural, not behavioral.

The four-step pattern that scales

Step 1: Per-person, scoped database users.

Don't give contractors the shared app_readonly account. Create a contractor-specific user with the minimum required permissions and an explicit naming convention.

-- PostgreSQL example
CREATE USER contractor_jane_q4_2026 WITH PASSWORD 'temp-strong-password-here';
GRANT CONNECT ON DATABASE production_db TO contractor_jane_q4_2026;
GRANT USAGE ON SCHEMA public TO contractor_jane_q4_2026;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO contractor_jane_q4_2026;

-- For tables created after this grant, set default privileges
ALTER DEFAULT PRIVILEGES IN SCHEMA public
    GRANT SELECT ON TABLES TO contractor_jane_q4_2026;

The naming convention contractor_<name>_<engagement_quarter> makes the intent obvious in audit logs and trivially auditable in pg_user.

Step 2: Time-bound the credential.

If your database supports password expiry, use it.

ALTER USER contractor_jane_q4_2026 VALID UNTIL '2026-12-31';

If not, set a calendar reminder for the engagement end date and DROP USER then. Better โ€” automate with a cron job that audits pg_user and drops users with expired naming conventions.

Step 3: Send via a self-destructing channel.

Now that you have a per-contractor credential, you need to transfer it once. The transfer channel needs:

  • End-to-end encrypted
  • One-time read
  • Hard expiry
  • No persistent copy in any chat history

A self-destructing share link satisfies all four. Slack, email, and SMS satisfy zero.

Step 4: Bias toward bastion access where possible.

If your contractors need to run ad-hoc queries, route them through a SQL bastion (Stash, Cloudbeaver, or even a teleport-managed psql session) instead of giving them direct DB credentials at all. The bastion has its own auth, logs every query, and the contractor never holds a production credential.

For pure code work where the contractor needs DATABASE_URL in their .env, the per-user-credential pattern is the right shape.

A real onboarding flow

# 1. Create the contractor-specific user (run on your DB host)
psql -c "CREATE USER contractor_jane_q4_2026 WITH PASSWORD '$(openssl rand -base64 32)' VALID UNTIL '2026-12-31';"
# Capture the password from the output for the next step

# 2. Grant minimum permissions
psql -c "GRANT CONNECT ON DATABASE production_db TO contractor_jane_q4_2026;"
psql -c "GRANT USAGE ON SCHEMA public TO contractor_jane_q4_2026;"
psql -c "GRANT SELECT ON ALL TABLES IN SCHEMA public TO contractor_jane_q4_2026;"

# 3. Construct the connection string
echo "postgresql://contractor_jane_q4_2026:THEPASSWORD@db.example.com:5432/production_db?sslmode=require"

Open snapsend.site, paste the connection string into a Credential Card with:

  • Service: Production DB (read-only) for Jane Q4 2026
  • Username: contractor_jane_q4_2026
  • Password: (the random password you generated)
  • URL: postgresql://...
  • Expiry: 1 hour
  • Optional passphrase lock

Send Jane the share link via Slack and the passphrase via SMS. She opens the card once, copies the fields into her .env, the link self-destructs.

When her engagement ends:

psql -c "DROP USER contractor_jane_q4_2026;"

One line. Other team members and contractors are unaffected. No coordination needed.

What if you have to share a shared credential?

Sometimes you can't avoid sharing a multi-user credential โ€” legacy systems, third-party APIs that only issue one credential per organization, etc. The mitigations:

  • Rotate after every contractor rotation. Yes, this is annoying. Yes, this is the cost of using a shared credential. Build the rotation into your offboarding checklist.
  • Use a vault as the source of truth. The contractor reads the credential from a vault that issues short-lived dynamic credentials (Vault DB Secrets engine, AWS RDS IAM auth). The contractor never holds the underlying credential.
  • Avoid sharing entirely where possible. If the contractor is doing a one-time data migration, run it yourself with their script as input. The contractor never sees production credentials at all.

Where SnapSend fits in

The Step 3 transfer is exactly what SnapSend was built for. The Credential Card mode is the right shape for database connections โ€” service, username, password, and URL as separate, copy-buttoned fields, not a single blob the recipient has to parse.

The encryption key lives in the URL fragment, so the SnapSend backend can't decrypt the credential even if compelled. The link self-destructs on first read. Adding a passphrase lock and sending it via a second channel gives you two-factor verification without making the contractor install anything.

For Step 4 โ€” bastion-routed access โ€” there are dedicated tools (Teleport, StrongDM, Stash). SnapSend isn't trying to replace those. It's the right tool for the moment a credential needs to leave the vault and reach a human, once.

Stop sharing the same password forever

The next time a contractor leaves and you have to rotate app_readonly, treat it as a signal that the architecture is wrong. Per-person credentials with self-destructing transfer is a one-week project that pays itself back forever.

Try SnapSend free at snapsend.site โ€” no account needed.