GUIDE · HOW-TO 8 min ·

Keep AWS credentials out of CursorWhat's exposed today. How to fix it.

The default AWS CLI setup leaves long-lived secret keys in a plaintext file and in your shell environment. Cursor reads both. Here is how to migrate off that model before the next agent state-file disclosure hits.

TL;DR· the answer, in twenty seconds

What: ~/.aws/credentials stores long-lived AWS access keys in plaintext. Any process running as you can read the file. Cursor and the tools it spawns inherit your shell environment, which may carry AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY directly.

Fix: Migrate to AWS SSO (aws sso login) or aws-vault. After migration, env | grep -i aws should return nothing in your base shell, and cat ~/.aws/credentials should contain no aws_secret_access_key= lines.

Lesson: An AI agent that inherits your shell inherits your blast radius. The fix is not to restrict the agent. The fix is to stop leaving long-lived credentials in ambient environment state.

The default AWS CLI setup is a 2014 design. It writes your access key ID and secret access key into ~/.aws/credentials as plaintext, and it expects you to export AWS_ACCESS_KEY_ID=... in your shell when you need to override a profile. That worked fine when the only thing running as your user was a terminal.

Cursor runs as your user. So does every subprocess Cursor spawns: shell commands, MCP servers, build tools. Any process in that tree that inherits your shell environment gets your AWS variables. Any process that can read your home directory can open ~/.aws/credentials directly.

In late January 2026, Knostic disclosed that Cursor's .cursor/ directory captures state from the agent session, including environment data. That disclosure covered a different credential class, but the mechanism is the same. An agent that writes session state to disk, in a directory inside your project, can capture whatever was in the environment when it ran. AWS env vars qualify.

Long-lived credentials sitting in ambient state are the problem. Every process you run inherits them by default.

What to know in 60 seconds

  • ~/.aws/credentials is a plaintext file. No encryption. Any process running as you can read it, including any tool Cursor launches.
  • Shell exports like export AWS_ACCESS_KEY_ID=... in ~/.zshrc or ~/.bashrc mean every terminal and subprocess inherits that value for the lifetime of your session.
  • Cursor's process tree inherits the environment of the shell that launched it. If you start Cursor from a terminal where AWS_* vars are set, Cursor has them.
  • AWS SSO and aws-vault both solve this by keeping the secret in the OS keychain and issuing short-lived credentials only to the specific command that needs them.
  • After the migration: env | grep -i aws should be empty in your base shell. cat ~/.aws/credentials should contain no aws_secret_access_key= lines.

What is actually exposed today

Open a terminal and run:

cat ~/.aws/credentials

If you see lines like:

[default]
aws_access_key_id = AKIA...
aws_secret_access_key = wJalrXUtnFEMI...

those keys exist on disk in plaintext. If your key ID starts with AKIA, it is a long-lived IAM user key with no automatic expiry. It stays valid until you revoke it. Any process running as you can read it.

Now check your shell environment:

env | grep -i aws

If you see AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY here, those values travel into every subprocess your shell spawns: Cursor, every MCP server Cursor connects to, every shell command Cursor runs on your behalf.

The key in ~/.aws/credentials and the key in your shell environment are often the same key. Two surfaces. A read on either one is enough to take your account.

Three migration paths

Pick one. You only need one to close the immediate gap. aws-vault and SSO can also run together if you want both.

AWS SSO with aws sso login

AWS IAM Identity Center (formerly AWS SSO) gives you short-lived credentials that expire after a configurable window, typically 8 hours. No long-lived secret sits in ~/.aws/credentials.

Configure a profile in ~/.aws/config:

[profile my-work]
sso_session = my-org
sso_account_id = 123456789012
sso_role_name = DeveloperAccess
region = us-east-1

[sso-session my-org]
sso_start_url = https://my-org.awsapps.com/start
sso_region = us-east-1
sso_registration_scopes = sso:account:access

Log in:

aws sso login --profile my-work

This opens a browser, authenticates against your IdP, and writes a short-lived session token to ~/.aws/sso/cache/. The token is not your secret key. It expires. When it does, aws sso login refreshes it.

After setup, run cat ~/.aws/credentials. If the file is empty or only contains SSO profile references without aws_secret_access_key= lines, you are clear. Remove any long-lived key lines and revoke the corresponding IAM user keys from the AWS console.

aws-vault

aws-vault stores your AWS credentials in the OS keychain (macOS Keychain or GNOME Keyring on Linux). When you need credentials, it issues short-lived STS tokens scoped to the session.

Install:

brew install aws-vault

Add your credentials to the vault instead of to ~/.aws/credentials:

aws-vault add my-work

It prompts for your access key ID and secret, then stores them in the keychain. The plaintext file gets nothing.

Run commands with credentials:

aws-vault exec my-work -- aws s3 ls

Or start a shell with credentials available for the duration:

aws-vault exec my-work -- $SHELL

Once inside that shell, AWS_ACCESS_KEY_ID and the other injected vars exist in the environment. When the shell exits, they vanish. Launch Cursor from inside this shell and Cursor gets credentials. Launch Cursor from your base shell and it gets nothing. You pick which processes get access, per invocation.

After adding to vault, delete the keys from ~/.aws/credentials and revoke the long-lived IAM keys. The vault entry is not a file Cursor can read. It requires the keychain unlock flow.

granted (commonfate)

granted is a CLI from Common Fate that wraps AWS SSO and adds a one-command browser-console shortcut. Some teams already use it as their daily driver.

brew install common-fate/granted/granted

Configure it to use your SSO setup:

granted sso populate --sso-start-url https://my-org.awsapps.com/start --sso-region us-east-1

Assume a role:

assume my-work

This exports STS credentials into your current shell for that profile, similar to aws-vault exec. The actual secret stays in the OS keychain. Only shells that ran assume (and their children) carry credentials. Your base shell gets nothing.

granted works well if your team already uses IAM Identity Center and wants the browser-console shortcut as a bonus. If you are starting from scratch, aws-vault is simpler to set up because it does not require SSO infrastructure.

Verify after migration

After setup, run these three checks:

# Should return nothing
env | grep -i aws

# Should contain no aws_secret_access_key= lines
grep -i secret ~/.aws/credentials 2>/dev/null && echo "FOUND: rotate these" || echo "clean"

# Confirm your IAM user long-lived keys are deactivated in AWS console
aws iam list-access-keys --query 'AccessKeyMetadata[?Status==`Active`]'

If the last command shows any active keys with a CreateDate more than 24 hours ago, those keys still exist. Rotate them. Deactivating is not enough. An attacker who already has the secret can continue using it until you delete the key entirely or revoke it.

For Cursor specifically: close Cursor, open a fresh terminal (no aws-vault exec shell), confirm env | grep -i aws is empty, then launch Cursor from that terminal. In Cursor's terminal pane, run env | grep -i aws again. It should be empty. If it is not, something in your shell startup files (~/.zshrc, ~/.zprofile, or ~/.bashrc) is exporting AWS vars unconditionally. Find and remove those lines.

What gets missed in the standard advice

The usual guidance stops at "use SSO" or "use aws-vault" and does not cover the launch-time inheritance issue.

aws-vault exec is a scoped shell, not a global setting

If you start a session with aws-vault exec my-work -- zsh and then type cursor . in that shell, Cursor inherits the injected credentials. That is intentional behavior. It becomes a problem when you treat the injected shell as your daily driver and forget that every agent you open from it has full AWS access.

Your base shell should have no AWS credentials. Use aws-vault exec my-work -- <specific command> for commands that need AWS. Don't start a long-running aws-vault session and then open your editor from it.

SSO token cache is on disk, unencrypted

~/.aws/sso/cache/ holds JSON files with access tokens. They expire, which limits blast radius. They are not encrypted at rest the way the OS keychain is. A process that can list your home directory can read them. If you suspect a session token was read, revoke sessions from the IAM Identity Center console. SSO tokens refresh on demand, so a revoke-and-relogin costs about 30 seconds.

MCP servers run as you, with your environment

Cursor's MCP integrations are separate processes, often Node.js or Python runtimes, started by Cursor. OX Security estimated around 7,000 MCP servers in the wild by early 2026, with roughly 150 million total downloads and no signature requirement on packages. A compromised MCP server runs with your full file system access and inherits your shell environment. Use SSO or aws-vault correctly and it finds no AWS secret. Leave AWS_SECRET_ACCESS_KEY in your shell and it reads it directly.

Checklist to paste into your team runbook

## AWS credential hygiene for AI agent environments

- [ ] No aws_secret_access_key= lines in ~/.aws/credentials
      (check: grep -i secret ~/.aws/credentials)
- [ ] AWS SSO or aws-vault in use for all profiles
- [ ] Long-lived IAM user keys deactivated and deleted in AWS console
- [ ] Base shell has no AWS_* vars: env | grep -i aws returns empty
- [ ] Cursor launched from a clean shell (not from inside aws-vault exec)
- [ ] MCP servers do not receive AWS env vars by default
- [ ] ~/.aws/sso/cache/ understood: tokens expire, not encrypted at rest
- [ ] Team runbook documents which profiles require aws-vault exec wrapping
- [ ] CI/CD uses OIDC or IAM role assumption, not long-lived keys in env vars

Run this on new team member setup and whenever you add a new AWS profile.

What this means for your stack

Long-lived credentials in ambient environment state give any process running as you a permanent foothold. A coding agent compounds that: it writes session state to disk and connects MCP servers, all inheriting the same foothold. Migrating to SSO or aws-vault closes the main surface. The secret stops living in a file Cursor can read or an environment it inherits.

If you want a runtime model where this is enforced rather than relying on shell hygiene: a local credential broker holds secrets in an encrypted vault, injects them into specific child processes at exec time, and keeps an audit log of every grant. hasp is one working implementation. curl -fsSL https://gethasp.com/install.sh | sh, hasp setup, connect a project, and the agent gets a reference scoped to the session rather than a key sitting in the environment. Source-available (FCL-1.0), local-first, macOS and Linux, no account.

An AI agent that runs as you has your blast radius by default. Shrinking it starts with the credential store, not with the agent's configuration.

Sources· cited above, in one place

NEXT STEP~90 seconds

Stop handing the agent your real keys.

hasp keeps secrets in one local encrypted vault, brokers them into the child process at exec, and never lets the agent read the value.

  • Local, encrypted vault — no account, no cloud, no telemetry by default.
  • Brokered run — agent gets a reference, the child process gets the value.
  • Pre-commit + pre-push hooks catch managed values before they ship.
  • Append-only HMAC audit log answers "did the agent touch the prod token?" in seconds.
→ okvault unlocked · binding ./api
→ okgrant once · pid 88421
→ okagent never read

macOS & Linux. Source-available (FCL-1.0, converts to Apache 2.0). No account.

Browse all clusters· eight threads, one index