Dev.to Security 🔐 Cybersecurity 👁 0 📖 4 min read

5 ways your cloud CLI picks the wrong account without telling you

You type vercel deploy, gh pr merge or kubectl apply. Which account does it run on? Most people would say "the one I'm logged into". That's wrong more often than you'd think, and nothing on screen tells you. Each CLI wo

You type vercel deploy, gh pr merge or kubectl apply. Which account does it run on?

Most people would say "the one I'm logged into". That's wrong more often than you'd think, and nothing on screen
tells you. Each CLI works out the account from flags, environment variables, files in the current folder and files in
your home directory, and each one checks them in a different order. These are five cases I ran into while building
cloudpin, all on current versions.

1. An environment variable silently beats your login

gh auth status says you're logged in as you. But if GH_TOKEN or GITHUB_TOKEN is set, gh uses that token
instead, for every command (gh help environment). The same goes for VERCEL_TOKEN, SUPABASE_ACCESS_TOKEN and
CLOUDFLARE_API_TOKEN over their CLIs' stored logins.

These variables usually get set once (a .env file, a CI secret copied to a laptop, an old shell profile) and then
forgotten. From then on, the login you see and the account your commands run on are different, and nothing on screen
says so.

2. The project folder picks the account

Several CLIs read the target from the project folder, not from who you're logged in as:

  • Vercel: a linked project (.vercel/project.json) sends project-level requests to the team it was linked under. Observed on Vercel CLI 50.35: --team changes account-level requests but not those; VERCEL_ORG_ID does. And the current flag in vercel teams ls shows only your global team, whatever the folder says.
  • Supabase: commands go to the project in supabase/.temp/project-ref, which supabase link wrote, unless --project-ref or SUPABASE_PROJECT_ID says otherwise. Copy a folder, and you copy the link.
  • Cloudflare Wrangler: account_id in wrangler.toml wins over CLOUDFLARE_ACCOUNT_ID. The source even has a TODO to swap them in v5. Setting the variable to "be sure" does nothing if the config file names another account.

So cd ../other-project && deploy can land on another account from the same terminal, with the same login.

3. Config files merge, and the first one wins

KUBECONFIG can list several files. kubectl merges them, and for each value the first file to set it wins
(kubernetes.io, "Organizing cluster access"). Add a file at the front of the list and your prod context may now point
somewhere else, with the same name.

A harder one to spot: kubectl config view --minify shows the context you'd expect but ignores --cluster, and real
requests use it (you can see it in the kubectl -v=6 request URLs). So the command you'd use to check can disagree
with the command that runs.

4. Someone else changed the global state

az account set, gh auth switch and gcloud config configurations activate rewrite a file in your home folder.
Every terminal and every tool on the machine shares it. Switch in one window to check something for a client, and the
deploy you start in the other window an hour later goes to the client.

Some mistakes don't even fail. On gcloud SDK 588, --configuration with a name that doesn't exist is not an
error: you get an empty configuration and exit code 0.

5. Names aren't IDs

  • In my own Azure account, several subscriptions are all called "Azure subscription 1". Only the subscription ID tells them apart.
  • Kubernetes context names are personal: the same cluster is prod on one laptop and acme-prod-admin on another.
  • AWS CLI aliases (~/.aws/cli/alias) can replace a built-in command, so aws s3 ls may not be s3 ls.
  • gcloud puts the resource name after the verb: gcloud compute instances delete describe deletes an instance named "describe".

What AI agents add

Coding agents run these same commands for you, and they don't stop to ask which account is active unless you tell
them to. When I started doing my side projects' Azure infrastructure with an agent, I approved each change myself,
but the approval prompt shows the command, not the account it will run on. So I ended up adding the same line to
almost every request: first check which account you're logged into. It worked, but only when I remembered to say
it, and that's the part that kept bugging me.

What I built

So I built cloudpin. You pin the accounts a project uses in a
.cloudpin.yml by ID, since names repeat. Before any guarded command runs, cloudpin works out which account that
command
would use, with its own flags, environment and folder, and stops it if it's not the pinned one. If it can't
tell, it blocks the command and says why. It works in your terminal (a shell wrapper) and in agent hooks (Claude
Code, Codex, Cursor, Copilot CLI, Antigravity and Gemini CLI).

It covers az, aws, gcloud, vercel, gh, kubectl, helm, supabase and wrangler. cloudpin explain -- <command> shows
what it would decide for any command, and why, without running it.

Works alongside

cloudpin doesn't manage credentials or switch accounts. It sits next to the tools that do:

  • aws-vault and granted hand out short-lived AWS credentials; cloudpin checks they're for the right account.
  • kubectx and kubens switch contexts quickly; cloudpin stops the command when the switch was the wrong one.
  • direnv sets per-folder variables; cloudpin checks which account those variables point at.

It's still early days: cloudpin has only been around for about a week, so it's very much a work in progress and I'll
keep adding to it. Next up are a Claude Code plugin, a GitHub Action, and Homebrew and Scoop installers.

To try it: npm install --global cloudpin, then cloudpin init in a project. If it gets an account wrong for your
setup, please open an issue. That's the feedback I'm most after.

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.