Build1 publisher3 min readPublished
A leftover localhost callback lets Cognito hand OAuth tokens to whatever binds port 3000
Cognito validates the redirect target by matching it against the app client's allowed callback list, so a development entry nobody removed is a valid destination for an authorization code, or for the tokens themselves where implicit grant is still on.
The Engineer · Build desk

What happened
- A dev.to analysis argues that the http://localhost:3000/callback entry teams add during development stays in the Cognito app client's allowed list and becomes a valid destination for OAuth tokens.
- Where implicit grant is still enabled, the access and ID tokens arrive in the URL fragment at whatever is listening on port 3000, so the attacker never has to exchange a code.
- Cloud security scanners confirm that a callback URL is configured, and none evaluate whether one of the entries would let an attacker receive the redirect.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint Telling a production endpoint from a staging one takes someone who knows the estate, so this part of the review stays manual, per app client, in every account.
- exposure One interception on a client with revocation disabled and a 365-day refresh token gives an attacker a session that outlasts the incident response which would normally end it.
- decision Teams still running implicit grant have to decide whether the fragment response is worth keeping, since it removes the attacker's need to hold anything but a listener.
Collecting a redirect on port 3000 of someone else's machine costs the attacker a foothold first. The post's own list of exploitable environments says so: compromised workstations, shared development servers, CI runners with exposed ports [8]. Teams that also added 3001 and 8080 gave the attacker more listeners to pick from [9]. Two of the other patterns it flags need nothing at all on the victim's device. A dangling Route 53 CNAME or a decommissioned S3 bucket lets an attacker stand up attacker.example.com and receive the redirect under a wildcard match [10]. An http:// entry sitting beside the https:// one lets someone on the same coffee shop wifi take the redirect in transit [11]. Cleaning an allowed list in order of who can reach it puts those two ahead of the laptop entry.
Cognito's part in this is a comparison. At step 4 of the five-step flow the post lays out [3], it checks the supplied redirect_uri against the app client's allowed callback URLs, and if http://localhost:3000/callback is on the list, the redirect is valid [4]. The authorize link is otherwise ordinary: client_id, response_type=code, scope=openid+email+profile [5]. The user authenticates on the real login page of the real identity provider [6].
For the code-flow version to end in a hijacked session, the attacker has to complete the token exchange. On a public client that needs no secret, and the post notes that SPAs and mobile apps do not use client secrets [18]. Implicit grant removes the step. The access token and ID token land in the fragment of the localhost URL, where the listener reads them [7].
What happens after the theft depends on two app client settings the post supplies as an example. With EnableTokenRevocation = false, the legitimate user cannot invalidate the stolen tokens, and the attacker's session runs until they expire [15]. With AccessTokenValidity at 24 hours and RefreshTokenValidity at 365 days [16], one interception yields 365 successive access tokens before the refresh token runs out [17].
The post argues that no scanner asks whether the callback list contains an attacker-reachable entry, because the question is not a boolean [14]. Three of the four dangerous patterns are properties of the string, and a rule that reads the entries can flag a loopback host, an http scheme or a wildcard by pattern, which is more than confirming a URL is present [13]. The staging case is the one that needs someone who knows the estate, since nothing in an HTTPS URL says which environment has the thinner logging and the softer database [12].
This is one dev.to analysis, labelled human-authored with AI used for formatting and proofreading, and it argues from configuration without citing a breach [20]. Its author wrote that the leftover entry "is now a token exfiltration endpoint" [2], and puts the remediation at thirty seconds [19].
What to watch
- Whether PKCE is enabled on the app client changes the code-flow half of this; the post's five-step walkthrough stops at the app exchanging the code for tokens.
- Whether any cloud posture scanner ships a rule that reads the allowed callback entries and flags loopback hosts, http schemes and wildcards by pattern.
- Whether the 24-hour access token and 365-day refresh token in the post match what teams run, since it presents them as an example configuration.