
I Told Amazon I Was a Phone, and It Believed Me
The annoyance
I have Prime Lite. It plays on my phone. It plays on a TV. Open the same title on a laptop and you get a polite wall telling you this device isn't supported.
Fine. That's a subscription tier, not a bug. I have no argument with Amazon charging less for less.
What bugged me was a smaller question. How does the server know I'm on a laptop?
My first guess was wrong
I assumed DRM. Prime Video runs Widevine, Widevine has security levels, and the obvious story is that my laptop fails some device attestation and the license server refuses to hand over a key.
That story is wrong, and it's worth understanding why, because it's the whole point of this post.
Widevine has three levels. L1 does key handling and decryption inside a hardware TEE. L3 does it in software, and every Chrome install on earth ships a provisioned software CDM that can do L3 perfectly legitimately.
So a laptop browser is not "unable to do DRM." It does DRM every day. It just can't do L1.
That left a gap in the story. If my browser has a working CDM, and Prime Lite content is SD anyway, what is actually saying no?
What a device check is made of
Here's the uncomfortable part. When a web app asks "what device is this," there is no privileged channel to ask. There is only what the client volunteers.
Server side, that's the request headers. User-Agent, and the Client Hints family that was supposed to replace it: Sec-CH-UA, Sec-CH-UA-Mobile, Sec-CH-UA-Platform, and friends.
Client side, it's whatever page JavaScript can read off navigator. The UA string, the platform, maxTouchPoints, navigator.userAgentData and its async getHighEntropyValues().
Every one of those is a self-report. The browser is describing itself. Nothing signs it, nothing attests it, nothing on the server can independently confirm it.
Which raises the obvious experiment. If the device gate is reading those fields, and those fields are just strings the client chose to send, what happens if the client chooses differently?
The experiment
I wrote a small Chrome extension to find out. It does three unglamorous things.
A declarativeNetRequest rule rewrites the device-identifying request headers on the way out, before they ever leave the machine. That covers the server side check.
A main world content script overrides the navigator surface that page JavaScript reads, including userAgentData and its high entropy getter, so the client side check sees the same story the server did. Consistency matters here. A page that sees an Android UA string next to a desktop userAgentData object is trivially catchable.
An isolated world script passes the active profile between the two so they never disagree.
That's it. No patched binaries, no intercepted license traffic, no key extraction. The whole thing is a few hundred lines and lives here: github.com/krishnaGauss/PassLite.

The whole control surface. A toggle, two device profiles, and the UA string it reports.
It played
Toggled it on, reloaded, hit play.

A laptop, a browser, and a stream that is not supposed to be there.
The interesting thing is not that it worked. The interesting thing is how ordinary the path was.
The document request went out with phone headers, so the edge served the mobile build and its policy branch instead of the unsupported device response. Page JS read a navigator that agreed, so no client side redirect fired.
Then I pressed play and nothing unusual happened at all. The player ran a completely standard EME flow. Chrome's own genuine Widevine CDM generated the license request. Amazon's license server looked at the device it believed it was talking to, decided that device was allowed, and issued an L3 license through the normal path.
Chrome then decrypted the stream with a key that Amazon had willingly handed over.
laptop chrome amazon
| |
| GET /detail (headers say: android phone) |
|--------------------------------------------->|
| | device policy check
| mobile build + mobile policy | sees allowed device
|<---------------------------------------------|
| |
| page JS reads navigator.* (already spoofed) |
| |
| [play] -> standard EME flow |
| license request from Chrome's REAL L3 CDM |
|--------------------------------------------->|
| | policy for the device
| L3 license, normal path | it THINKS you are
|<---------------------------------------------|
| |
v
CDM decrypts with a legitimately issued key -> SD plays
Why this is not a DRM break
I want to be precise, because the loud version of this story is wrong and the quiet version is more interesting.
Nothing was cracked. The decryption key came from Amazon's license server, issued willingly, through the path it was designed to use. No attestation was forged, because L3 has no hardware attestation to forge. It only needs a provisioned software CDM, and Chrome has one legitimately.
What broke is one layer up. Device eligibility was decided from client-reported, unsigned, trivially editable inputs. The crypto was never the barrier. The barrier was a policy gate that took the client's word for it.
There's a clean fingerprint that proves this. HD and UHD titles still refuse to play. Their policy requires L1, and a laptop browser's software CDM cannot produce an L1 security level no matter what its User-Agent claims. That's a real attestation, and it holds.
Everything that played was SD, around 480p. That asymmetry is the tell. Where the check was cryptographic, it held. Where the check was a string comparison, it didn't.
The actual finding
Stated plainly, for the report:
Root cause. Device eligibility for the restricted tier is enforced against client-reported signals (User-Agent, Client Hints, navigator) that the client fully controls.
Impact. A device class outside the subscription's entitlement receives licensed content through the normal licensing path. This is a licensing compliance exposure, not a content protection failure.
Fix direction. Bind the entitlement decision to something the client cannot self-assert. The license exchange already surfaces the CDM security level and the provisioned device class, and unlike a header, those are attested. Gate on that.
Treat headers and navigator as hints for rendering. Never as an access control boundary. They were designed to help you serve the right layout. They were never designed to carry authorization.
I wrote this up with repro steps, the evidence that it is a policy gap and not a crypto break, and the affected surface, and sent it to Amazon through their vulnerability disclosure channel.
The takeaway
I keep coming back to how boring the exploit is.
There's no clever cryptanalysis here. There's a string, and somebody trusted it. The sophisticated part of the system, the Widevine stack with its hardware TEEs and provisioning and attestation, worked exactly as designed and stopped me cold at HD.
The part that failed was a startsWith on a header, somewhere.
That's usually where it is. The interesting bugs are rarely in the algorithm that everyone reviewed. They're in the assumption nobody wrote down, sitting one layer above it, quietly trusting the client.
If your architecture has a sentence in it that starts with "the client tells us," go and read the rest of that sentence carefully.