AWS Is Right: You Really Don't Need to Create That IAM Key
AWS has told us for years not to create long-lived access keys, and most of us kept creating them anyway. In 2026 that habit stopped being affordable. Here is the principle, and one worked example: a CLI for your own cloud app that signs in without a key.
The problem
Open the credentials file on a working developer's laptop and count the keys. One for the production account, one for staging, one for the personal sandbox, a few for clients, and one that nobody remembers creating. The same keys, or copies of them, sit in CI secrets, in the environment of every agent that was ever asked to deploy something, and in shell history. Each one is valid everywhere it has been copied, until someone acts.
We did this too. For years it was how work got done: create the key, paste it into ~/.aws/credentials, move on. AWS's own documentation says what that file is, which is plaintext. We knew that and shrugged, because the alternative looked like friction and the risk looked remote.
AWS has been saying this for years
The advice is not new and it is not subtle. AWS's IAM security guidance says human users should federate through an identity provider and use temporary credentials, with IAM Identity Center as the recommended way to manage that; workloads should use roles; and long-term access keys belong only to the use cases that cannot do either. The access-keys page opens with an "Important" notice telling you to use temporary credentials such as IAM roles instead of creating long-term credentials, and to review the alternatives before you create one.
The console tries to talk you out of it in person. According to the IAM User Guide, choosing "Create access key" first lands you on a page of alternatives. You pick your use case to see options that avoid a long-term key, and only if you decide you still need one do you choose "Other" and then "Next". The alternatives it points to are aws login for local development with console credentials, CloudShell for a browser-based CLI, Identity Center for people, and roles for anything running on AWS compute.
Most of us clicked "Other". The reasons were ordinary. A key worked with every tool, including the ones that had never heard of Identity Center; AWS itself lists third-party clients that do not support it as a case where a long-term key is still needed. A key never asked you to sign in again. And the harm was abstract: a leaked key was something that happened to other people, and rotation was a task for a quieter week.
Why 2026 is different
"Black October" is an informal label, not an official one, and it names a trend more than a list of incidents: through 2026, attackers with AI assistance have been finding and using weaknesses at a rate that appeared to go nearly vertical in October. The label is contested. Forkast and SecurityToday argue it describes a disclosure wave rather than an attack wave, and that many incidents began months earlier. We found no rigorous before-and-after figure for time to exploitation.
Two incidents still carry the point. On October 21, a malicious LiteLLM release sat on PyPI and Docker Hub for about two hours, according to LiteLLM's own disclosure. Wiz's analysis found the payload went after AWS credentials in ~/.aws, environment variables and instance metadata, GCP and Azure credentials, AI provider keys, SSH and GPG keys, browser credentials and shell history. LiteLLM told users to rotate everything the host could reach. That advice rests on exposure, not confirmed theft, and what any victim lost has not been established.
The same day, Microsoft reported that Storm-0940 used an agentic AI framework after seizing identities belonging to ConnectWise cloud applications and attempted sign-ins to more than 2,250 tenants; in several cases, "the time from initial targeting to privileged access was less than 3 hours". Microsoft also says most attempts failed.
The lesson is about odds. Every kind of attack is more likely now, and the LiteLLM payload is a map of exactly where developers keep keys. A key sitting on a laptop was always a bet. It is a much worse bet than it was.
The principle
Almost never create a long-lived key, and almost never design a system that requires one. The second half is the one architects control. A system that needs a static key depends on people to rotate it, keep it scoped to one job, and notice when it has been copied somewhere it should not be. That is stronger than the vendors say: AWS prefers temporary credentials "where possible" and lists residual cases; Google calls service account keys an exception rather than the norm; GitHub documents OIDC tokens valid for one job.
One caveat: some third-party APIs only issue static keys. The model-provider keys LiteLLM held are of that kind. That is a constraint to contain, with one key per tool, spend limits and a single storage place, not a pattern to copy into your own design.
Two things to check. First, list the keys on your own laptop: ~/.aws/credentials, the environment, the shell history. Second, list every long-lived key your product asks a person to create. Each one is a design decision, and each can be designed out.
One way to do it
For AWS itself, the answer exists. aws login, shipped in November 2025, signs you in through the browser and gives the CLI temporary credentials refreshed about every 15 minutes. Identity Center does the same for an organisation.
For your own cloud app's CLI, the same idea is what we built for Papyrus and Cyclotron and open-sourced as cli-login: browser sign-in with PKCE, so the person creates no key and an intercepted code is useless without the verifier the CLI never sent; short-lived tokens, with only the refresh token stored, in the OS secret store (Keychain, Secret Service, kernel keyring or a memory-only agent, Credential Manager) and never in a file; and remote approval for machines with no browser.
That last part is the one we needed most. An AI agent on a cloud machine needed to act as me against our Cyclotron lab. It ran cyclotron login --remote --as with my address and printed a link and an eight-character code. I opened the link on my phone. The page showed which machine was asking, which CLI version it ran, the code, and what the session would be allowed to do. I compared the code, ticked the box saying it matched, tapped Approve, and signed in the way I always sign in to the lab. On the cloud machine, the CLI printed the identity it had received: mine. Nothing was pasted into the agent's terminal, and the agent never held a key.
Cognito, which our products use, does not offer the RFC 8628 device flow as far as we have found, and aws login --remote and gcloud auth login --no-launch-browser both have the person paste an authorization code back into the terminal. So we added a small broker, a Lambda function and a DynamoDB table, and split the secret:
sequenceDiagram
participant CLI as CLI on the cloud machine
participant Broker as Broker (Lambda and DynamoDB)
participant Phone as Your phone's browser
participant Pages as Product approval pages
participant IdP as Identity provider (Cognito)
Note over CLI: Keeps the PKCE verifier and the poll secret in memory
CLI->>Broker: Start: hashes of verifier and poll secret, nonce, machine label
Broker-->>CLI: Transaction id, matching code, approval link
Note over CLI: Prints the link and the code, then polls
Phone->>Pages: Open the approval link
Pages->>Broker: Describe the transaction
Broker-->>Pages: Requesting machine, matching code, target
Phone->>Pages: Code matches, Approve
Pages->>Broker: Bind once to this browser
Pages-->>Phone: Redirect to sign-in with the PKCE challenge
Phone->>IdP: Sign in as usual
IdP-->>Pages: Authorization code
Pages->>Broker: Check browser nonce, hand over the code
CLI->>Broker: Poll with transaction id and poll secret
Broker-->>CLI: The code, exactly once
CLI->>IdP: Exchange the code with the verifier
IdP-->>CLI: ID, access and refresh tokens
Note over CLI: Checks nonce, issuer, audience and approver, stores the refresh token in the OS secret store
- The CLI makes a PKCE verifier and a poll secret, keeps both in memory, and sends the broker only their hashes, a nonce and a label naming the machine.
- The broker answers with a transaction id, a matching code and an approval link on the product's own domain. It holds the two hashes and the code, never a token.
- The CLI prints the link and the code, then polls the broker every few seconds with the transaction id and the poll secret.
- You open the link. The product's approval page asks the broker to describe the transaction and shows you the requesting machine, its CLI version, the code, and what the session will be able to do.
- You compare the code, tick "matches" and tap Approve. The page binds the transaction once, so nobody else can approve it, and redirects you to the normal sign-in with the CLI's PKCE challenge attached.
- You sign in as usual. The identity provider returns an authorization code to the product's callback page, which checks its own browser nonce and hands the code to the broker.
- The broker releases the code to the next poll that carries the right poll secret, exactly once, through a conditional delete. A wrong secret looks the same as an expired transaction.
- The CLI exchanges the code with its verifier, checks the nonce, redirect, issuer, audience and, with
--as, the approver, and stores only the refresh token in the OS secret store. If anything is wrong it stores nothing.
Transactions last ten minutes and are rate-limited. Before each request, the library refreshes an access token that expires within 60 seconds, under a per-session lock so two processes cannot lose a rotated refresh token. Lifetimes are the provider's: our lab issues 60-minute access tokens and 30-day refresh tokens, then asks for approval again.
What this doesn't fix
Tokens can still leak. LiteLLM says its release pipeline was reached with GitHub CLI OAuth tokens that had leaked through logs, the same family of credential this design produces. The difference is the damage when one does: these expire, can be revoked, and sit in a secret store rather than in files and environment variables. Don't log response bodies that carry authorization.
Whoever approves first owns the session. An attacker who starts a transaction and persuades you to approve it gets a session as you. The approval page shows who is asking and a code to compare, and --as refuses the wrong approver. That narrows the phishing risk. It does not prevent it, and the protocol document says so.
It is Cognito-only today, the broker is AWS-only, and the only client is Python. Not yet tested: a real 24-hour renewal run, which so far rests on simulated-clock specs; the approval flow in real Chrome, Safari and Firefox; and the macOS Keychain store, which CI exercises only with fakes.
Try it
pip install cli-login
The code and the protocol document are at github.com/DataParade-io/cli-login. The protocol is written to be implemented in other languages. Read it as a security reader would, and tell us where the design is wrong.
Ready to make your diagrams meaningful?
Connect your repositories, extract verified dataflow diagrams automatically, and conduct continuous risk reviews.

