Interim controls for shared admin accounts: vault, checkout, and attribution
For teams stuck with shared admin accounts they cannot remove yet, this shows how to put those credentials behind a vault, require explicit checkout, and recover per-user attribution in logs. You will finish with a working interim control set that reduces blast radius and gives you an audit trail without changing the target system.
TL;DR — If a shared admin account must stay for now, stop passing the password around. Put the secret in a vault, require time-bounded checkout to a local environment variable or temporary file, and force every use through a wrapper that logs who checked it out, when, and for which system.\n> Reading time: ~5 min\n\n## Goal\nWhen you are done, the shared admin credential exists only in your team vault, engineers retrieve it through an explicit checkout command with a short TTL, and every checkout is attributable to an individual user in your logs or ticket trail even though the target system still sees one shared username.\n\n## Prerequisites\n- Access to your team secret manager or password vault with audit logs enabled\n- Permission to create or edit one secret item for the shared admin account\n- A shell on your workstation:
bashorzsh\n-jq >= 1.6— check withjq --version\n-openssl >= 3.0orgpg >= 2.2if you need to write a temporary file securely — check withopenssl versionorgpg --version\n-curl >= 8.0— check withcurl --version\n- A team identifier for attribution, usually your SSO email and the ticket/change ID you are working under\n- The exact shared admin username, the target system name, and the login URL or host\n\n## Steps\n\n### Step 1: Put the shared admin credential in one vault record\nCreate one canonical secret entry in your vault named exactlyshared-admin/<system-name>/prod. Store these fields exactly:username,password,url,owner_team,rotation_contact, andbreakglass=false.\n\nIn your vault provider's dashboard, create the item at the equivalent of:\nbash\nVault UI -> New secret/item -> Name: shared-admin/<system-name>/prod\nFields:\n username=<shared-admin-username>\n password=<current-password>\n url=https://<target-login-url>\n owner_team=<team-name>\n rotation_contact=<email-or-group>\n breakglass=false\n\n\nIf your vault supports labels/tags, add exactly these tags:\nbash\nshared-admin\nprod\nmanual-login\nrequires-checkout\n\n\nWhat you should see when this succeeds: one secret item exists, and your vault audit log shows acreate secretorupdate itemevent under your own user identity.\n\n### Step 2: Remove the password from docs, chat, and local dotfiles\nSearch your repo and your shell history for the shared username or known login URL, then delete any copied password references. Run these commands locally from the repo root and your shell history directory.\n\nbash\ngit grep -nE 'shared-admin|admin@|https://<target-login-url>' || true\nstrings ~/.zsh_history ~/.bash_history 2>/dev/null | grep -nE 'shared-admin|admin@|https://<target-login-url>' || true\n\n\nIf you find plaintext references in tracked files, replace them with the vault item name exactly:\nbash\nshared-admin/<system-name>/prod\n\n\nWhat you should see when this succeeds:git grepno longer returns plaintext credentials, and any remaining references point only to the vault item name.\n\n> ⚠️ If you paste the password into shell commands, it may land in shell history, terminal scrollback, process lists, and screen recordings. Use environment variables or a temporary file with0600permissions instead.\n\n### Step 3: Create a local checkout wrapper that records attribution\nCreate a wrapper script that forces a reason, ticket ID, and TTL before retrieving the secret. Replace thevault_read_commandline with your provider's CLI read command that prints JSON containingusernameandpassword.\n\n```bash\nmkdir -p ~/.local/bin ~/.local/state/shared-admin\ncat > ~/.local/bin/shared-admin-checkout <<'EOF'\n#!/usr/bin/env bash\nset -euo pipefail\n\nif [ "$#" -ne 4 ]; then\n echo "usage: shared-admin-checkout <system> <ticket> <reason> <ttl-minutes>" >&2\n exit 64\nfi\n\nsystem="$1"\nticket="$2"\nreason="$3"\nttl="$4"\nrequestor="${USER:-unknown}@$(hostname -s)"\nitem="shared-admin/${system}/prod"\nstate_dir="$HOME/.local/state/shared-admin"\nlease_file="$state_dir/${system}.lease"\nsecret_file="$state_dir/${system}.secret"\nexpires_epoch=$(( $(date +%s) + ttl * 60 ))\n\nmkdir -p "$state_dir"\numask 077\n\n# Replace this command with your vault CLI invocation. It must output JSON.\nvault_read_command='printf "{"username":"shared-admin","password":"REPLACE_ME"}"'\nsecret_json=$(eval "$vault_read_command")\nusername=$(printf '%s' "$secret_json" | jq -r '.username')\npassword=$(printf '%s' "$secret_json" | jq -r '.password')\n\nif [ -z "$username" ] || [ -z "$password" ] || [ "$password" = "null" ]; then\n echo "secret read failed for $item" >&2\n exit 65\nfi\n\nprintf '{"ts":"%s","requestor":"%s","system":"%s","ticket":"%s","reason":"%s","expires_epoch":%s}\n'
"$(date -Iseconds)" "$requestor" "$system" "$ticket" "$reason" "$expires_epoch" \"$state_dir/checkout.log"\n\nprintf 'SHARED_ADMIN_USERNAME=%q\nSHARED_ADMIN_PASSWORD=%q\nSHARED_ADMIN_SYSTEM=%q\nSHARED_ADMIN_TICKET=%q\nSHARED_ADMIN_EXPIRES_EPOCH=%q\n'
"$username" "$password" "$system" "$ticket" "$expires_epoch" > "$secret_file"\ncp "$secret_file" "$lease_file"\n\necho "checked out $item until $(date -d @${expires_epoch} -Iseconds 2>/dev/null || date -r ${expires_epoch} -Iseconds)"\necho "run: source $secret_file"\nEOF\nchmod 700/.local/bin/shared-admin-checkout\n/.local/bin/shared-admin-checkout billing-admin INC-4821 "reconcile failed invoices" 15\nsource ~/.local/state/shared-admin/billing-admin.secret\nprintf '%s\n' "$SHARED_ADMIN_USERNAME" "$SHARED_ADMIN_SYSTEM" "$SHARED_ADMIN_TICKET"\n\n\nWhat you should see when this succeeds: `~/.local/bin/shared-admin-checkout` exists and is executable.\n\n### Step 4: Check out the credential for one task with a short TTL\nRun the wrapper with the real system name, your ticket, a reason, and a short TTL such as 15 minutes.\n\nbash\n\n\nExpected output shape:\nbash\nchecked out shared-admin/billing-admin/prod until 2026-10-30T14:25:12+00:00\nrun: source /home/alice/.local/state/shared-admin/billing-admin.secret\nshared-admin\nbilling-admin\nINC-4821\n\n\nWhat you should see when this succeeds: the checkout command prints an expiry time, and the sourced environment variables are populated.\n\n### Step 5: Use the shared account without exposing the password on the command line\nIf the target is a web UI, paste from the environment variable into the login form and do not save in browser password managers. If the target is an HTTP endpoint, use a header or stdin-fed auth mechanism rather than embedding the password in the process list. Example with basic auth to verify login only:\n\nbash\ncurl -sS -u "$SHARED_ADMIN_USERNAME:$SHARED_ADMIN_PASSWORD" -I "https://<target-login-url>/"\n\n\nExpected output shape:\nbash\nHTTP/2 200\ncontent-type: text/html; charset=utf-8\ncache-control: no-store\n\n\nWhat you should see when this succeeds: the target accepts the shared credential, and your shell history contains only variable names, not the password.\n\n### Step 6: Record the attribution next to the operational action\nPut the same ticket ID and your identity into the system you are changing: ticket comment, deployment note, database session comment, or change log. Use this exact one-line template.\n\nbash\nUsed shared-admin/<system-name>/prod via checked-out vault secret; actor=<your-email>; ticket=<ticket-id>; reason=<one-line reason>; start=$(date -Iseconds)\n\n\nIf you are using Postgres for the admin action, set the application name before running changes so DB logs carry your identity even though the DB user is shared:\n\nbash\nexport PGAPPNAME="shared-admin actor=<your-email> ticket=<ticket-id>"\n\n\nWhat you should see when this succeeds: your ticket or system log contains a line tying the shared account use to a named human and a work item.\n\n### Step 7: End the checkout and delete local material\nWhen the task is done, unset the environment variables and securely remove the local file.\n\nbash\nunset SHARED_ADMIN_USERNAME SHARED_ADMIN_PASSWORD SHARED_ADMIN_SYSTEM SHARED_ADMIN_TICKET SHARED_ADMIN_EXPIRES_EPOCH\nshred -u ~/.local/state/shared-admin/billing-admin.secret 2>/dev/null || rm -f ~/.local/state/shared-admin/billing-admin.secret\n\n\nWhat you should see when this succeeds: `env | grep '^SHARED_ADMIN_'` returns nothing, and the secret file no longer exists.\n\n## Verify it works\nRun these checks end to end.\n\nbash\nls -l ~/.local/bin/shared-admin-checkout/.local/state/shared-admin\n/.local/bin/shared-admin-checkout billing-admin INC-4821 "verification" 5\ntail -n 1 ~/.local/state/shared-admin/checkout.log | jq .\nsource ~/.local/state/shared-admin/billing-admin.secret\n[ -n "$SHARED_ADMIN_PASSWORD" ] && echo ok\nenv | grep '^SHARED_ADMIN_'\n\n\nExpected results:\n- `shared-admin-checkout` is mode `-rwx------` or similar executable by you only\n- `checkout.log` contains one JSON line with `requestor`, `system`, `ticket`, and `expires_epoch`\n- `echo ok` prints `ok`\n- `env | grep '^SHARED_ADMIN_'` shows the five exported variables\n\nIf your vault has audit logs, also verify one read event exists under your own identity for the item `shared-admin/<system-name>/prod` at the same timestamp as the local checkout log.\n\ndiagram\n[Engineer] ---- 1. checkout cmd ----> [Vault]\n | |\n | 4. local log + env vars | 2. audit log read\n v v\n[Workstation] ---- 3. login/use ----> [Target System]\n```\n\n## Common pitfalls\n\n### Password still appears in shell history\nMistake: running commands likecurl -u shared-admin:actualpassword ...or pasting the password into a CLI flag.\n\nSymptom:strings ~/.zsh_history ~/.bash_history | grep shared-adminshows the password or full basic-auth string.\n\nFix: rerun using-u "$SHARED_ADMIN_USERNAME:$SHARED_ADMIN_PASSWORD"or a prompt/stdin mechanism, then delete the affected history entry and rotate the password if it was committed or shared.\n\n### Checkout file is world-readable\nMistake: creating the secret file before settingumask 077, or storing it under/tmp.\n\nSymptom:ls -l ~/.local/state/shared-admin/*.secretshows permissions broader than-rw-------.\n\nFix: runchmod 600 ~/.local/state/shared-admin/*.secretand move the file under your home directory; then recreate it withumask 077in the wrapper.\n\n### No attribution in the target system logs\nMistake: using the shared account directly in the UI or DB without also writing your identity into a ticket, session variable, or application name.\n\nSymptom: audit logs show onlyshared-adminwith no way to tell who acted.\n\nFix: add the exact attribution line to the ticket and, where supported, set a session marker likePGAPPNAME="shared-admin actor=<your-email> ticket=<ticket-id>"before connecting.\n\n### Vault audit log shows reads but no reason or ticket\nMistake: relying on the vault's raw read event alone. Most vaults log who read a secret, not why.\n\nSymptom: during review you can prove identity but not business justification.\n\nFix: require the wrapper arguments<ticket>and<reason>and keep the JSON checkout log; do not permit direct vault reads outside break-glass.\n\n### Secret file survives after TTL expires\nMistake: assuming TTL deletes local files automatically. Your wrapper recorded an expiry; it did not enforce cleanup.\n\nSymptom:date +%sis greater thanSHARED_ADMIN_EXPIRES_EPOCH, but the file still exists and can still be sourced.\n\nFix: delete the file at task end and add a shell guard before use, for example:test $(date +%s) -lt "$SHARED_ADMIN_EXPIRES_EPOCH" || { echo expired; exit 1; }.\n\n### Browser saves the shared password\nMistake: logging into a web admin UI with password-save enabled.\n\nSymptom: the browser offers autofill for the shared admin account on later visits.\n\nFix: decline the save prompt, remove the saved credential from the browser password store, and prefer a dedicated browser profile for admin work.
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI