What happened, what was found and why it matters
This investigation did not begin with somebody hunting for fingerprinting code. It began with a physical fault: multipoint Bluetooth headphones stopped handing audio from a Windows PC back to a phone whenever an AliExpress tab was open. Closing the tab fixed it, but muting the tab did not. There was no visible or audible media to explain why [1] [8].
1. A Bluetooth symptom supplied the first clue
The PC behaved as if audio was active while AliExpress was open, even though the user could not hear anything.
2. Ordinary media was ruled out
The original investigator checked audio and video elements, media play calls, Media Session state, media requests and embedded frames. None explained the activity [1].
3. WebAudio was instrumented instead
Wrappers around AudioContext and AudioNode.connect recorded when JavaScript created a processing context and connected nodes to the system audio destination.
4. Two hidden contexts led to two scripts
Stack traces pointed to Alibaba AWSC Collina and Fireye bundles. Both created running audio graphs with no normal media element [1].
5. Source review explained the behaviour
The scripts generated a known waveform, measured its frequency output, set gain to zero and connected the graph to the destination. That can produce a browser clue while remaining silent.
A shopping page could silently run a repeatable audio calculation, turn the returned values into a device clue and combine that clue with other browser measurements without asking for microphone access or showing a normal media player.
WebAudio fingerprinting is closer to a controlled benchmark than a recording. The page supplies the same mathematically generated signal to each browser. The browser processes it, an analyser returns a list of numbers, and small implementation or CPU-maths differences can change those numbers. The list can then be compared directly or shortened to a digest. No microphone is needed because the test signal is generated inside the browser [5] [12] [13].
| Stage | What happens | Why it helps recognition |
|---|---|---|
| Known input | Every browser receives the same sawtooth test signal | Creates a fair comparison |
| Measured output | The analyser returns 1,024 frequency-bin values | Captures processing differences |
| Comparable label | The values are serialized or hashed | Makes later comparisons practical |
| Composite profile | Audio joins Canvas, WebGL, screen and hardware clues | Narrows the matching crowd |
| Later visit | A similar profile appears after cookies change | May support re-identification or a fraud score |
W3C guidance describes fingerprinting as identifying or re-identifying a browser from observable characteristics. It can correlate visits without the clear, resettable control a cookie provides [6]. The same capability can support legitimate fraud and bot detection, but the browser cannot reveal Alibaba’s purpose, retention period or server-side linkage. We will keep those possibilities separate from what our evidence proves.
This walkthrough is for somebody who can open a terminal and install a package but has not investigated browser fingerprinting or reverse-engineered minified JavaScript. We are testing a narrow claim: does a current public AliExpress visit serve Fireye JavaScript containing the complete WebAudio measurement capability, and what happens when we safely reproduce only that mechanism locally?
| Evidence type | What it can establish | What it cannot establish |
|---|---|---|
| Live network capture | Which scripts the public page served during this run | What every visitor receives or what the server later decides |
| Static source review | The WebAudio operations and data handling present in the bundle | That the branch executed in this browser session |
| Local reconstruction | How the mechanism behaves without telemetry | Alibaba’s server-side retention, linkage or purpose |
Chromium 149
An isolated, logged-out browser profile gives us a clean public visit without personal history or account cookies.
mitmweb 12.2.3
The graphical interface for mitmproxy keeps the flow list, selected request, response headers and decoded JavaScript connected on one screen.
curl, wc and SHA-256
These extract the exact observed response into temporary storage, count its bytes and give it a stable integrity identifier.
YARA 4.5.8 and js-beautify 2.0.3
YARA quickly locates a family of relevant strings. The beautifier then turns one huge minified line into source windows a person can inspect.
Visual Studio Code and the local lab
VS Code preserves line numbers and syntax context. The Xposure lab recreates only the audio graph and sends nothing off the page.
The live work is a normal read-only visit to a public page and its public JavaScript. The browser profile, proxy flow, extracted bundles and YARA package stay in temporary storage. We do not log in, submit transactions, bypass controls or retain cookies, identifiers, request payloads or complete third-party bundles in the repository.
# Standard Kali setup. Review the package list before confirming.
sudo apt update
sudo apt install chromium mitmproxy yara curl coreutils python3-jsbeautifier
# Any editor with line numbers works. This walkthrough used VS Code 1.128.| Command part | Plain-English meaning | Why we need it |
|---|---|---|
| sudo apt update | Refresh Kali’s package catalogue | Makes the following package names resolve to current repository versions |
| sudo apt install … | Install the listed tools after showing the package prompt | Provides the browser, proxy, triage and evidence commands used below |
| chromium | Web browser | Creates the isolated public visit |
| mitmproxy | Includes the mitmweb network-capture interface | Keeps requests and responses connected visually |
| yara | Pattern-matching command-line tool | Narrows a large bundle to related WebAudio clues |
| curl and coreutils | Download, byte-count and hash utilities | Extract and identify one exact response |
| python3-jsbeautifier | JavaScript formatter | Restores line breaks and indentation to minified source |
Step 1: open the public target in an isolated browser
Before looking for suspicious code, we need to establish the exact public page and remove our normal cookies, account history and extensions from the observation. Expected result: a working, logged-out AliExpress homepage in a disposable browser profile.
Start with the ordinary site, not the news headline. An isolated Chromium profile is simply a fresh browser data directory created for this investigation. It prevents existing AliExpress cookies or logins from changing what we observe.
profile=$(mktemp -d /tmp/aliexpress-browser.XXXXXX)
chromium --user-data-dir="$profile" --no-first-run https://www.aliexpress.com/| Command part | What it means | Why it is here |
|---|---|---|
| profile=$(mktemp -d …) | Create a uniquely named temporary directory and store its path in profile | Gives Chromium fresh browser storage that can be removed later |
| XXXXXX | Placeholder characters that mktemp replaces safely | Avoids guessing a directory name or colliding with an existing one |
| chromium | Start the Chromium browser | Runs the public visit we will observe |
| --user-data-dir="$profile" | Use the new directory for cookies, cache and settings | Keeps personal browser data out of the capture |
| --no-first-run | Skip Chromium’s welcome workflow | Prevents setup screens obscuring the target |
| https://www.aliexpress.com/ | The public target URL | Starts from the ordinary homepage named in the report |
- Confirm the public target: The address bar shows aliexpress.com over HTTPS. We are examining the normal public homepage, not a copied page or synthetic victim.
- Keep the acquisition logged out: The header still says Sign in / Register. That matters because personal account cookies and order history are outside this investigation.
- Begin with ordinary browsing: The page is functioning as a shopping homepage. Nothing visible tells the visitor that browser-security scripts will inspect WebAudio capabilities.
ObservedThe logged-out public homepage loaded successfully over HTTPS.
Next stepThe page alone cannot tell us which background script matters, so capture its network traffic and narrow the script inventory.
Step 2: capture the JavaScript traffic with mitmweb
The visible page cannot tell us which background file created the reported audio graph. A proxy records the files this browser actually requests, so the script target comes from evidence rather than a copied URL. Expected result: a small set of successful AliExpress JavaScript responses containing the reported Fireye family name.
A browser downloads HTML, images, fonts and JavaScript in the background. mitmweb is the browser interface for mitmproxy: it records those requests and lets us select one response without losing the surrounding network context. Use a disposable profile because the lab-only TLS interception must never be mixed with normal browsing.
mitmweb --listen-host 127.0.0.1 --listen-port 18080 --web-host 127.0.0.1 --web-port 18081 --no-web-open-browser
chromium --proxy-server=http://127.0.0.1:18080 --ignore-certificate-errors --user-data-dir="$profile" https://www.aliexpress.com/| Command part | What it means | Why it is here |
|---|---|---|
| mitmweb | Start mitmproxy’s browser-based interface | Lets us keep the flow list, headers and response body together |
| --listen-host 127.0.0.1 | Accept proxy traffic only from this machine | Avoids exposing the lab proxy to the network |
| --listen-port 18080 | Put the traffic proxy on local port 18080 | Gives Chromium one explicit proxy address |
| --web-host 127.0.0.1 --web-port 18081 | Put the mitmweb interface on a separate local port | Keeps the analyst interface local and distinct from browser traffic |
| --no-web-open-browser | Do not launch another browser automatically | Prevents an unrelated profile entering the capture |
| --proxy-server=http://127.0.0.1:18080 | Send this Chromium profile through mitmweb | Makes its requests visible in the flow list |
| --ignore-certificate-errors | Accept the disposable proxy’s lab certificate | Allows HTTPS inspection only inside this temporary profile |
The exception trusts this local proxy only inside the temporary browser profile. Do not use that browser for email, passwords, administration or ordinary browsing. A longer-lived lab should import the mitmproxy CA into an equally isolated profile instead.
- Reduce hundreds of flows to one script: The search field contains fireyejs. mitmweb translates that into a case-insensitive URL filter and leaves one matching flow.
- Derive the exact script URL: The surviving row names assets.aliexpress-media.com, the AWSC/fireyejs family, version 1.231.67 and fireyejs.js. We do not have to guess a filename.
- Confirm a successful JavaScript response: GET, HTTP 200, 199.9 KB compressed and 50 ms show that this was a real response used by the page, not a failed or blocked request.
ObservedOne current Fireye response was selected from 303 captured browser flows.
Next stepOpen the selected response to confirm its content type and inspect the decoded body before extracting anything.
Step 3: inspect the selected Fireye response
A filename alone is weak evidence. We first need to prove that the selected browser flow succeeded and returned JavaScript, then keep that identity attached to the body. Expected result: HTTP 200, a JavaScript content type and a decoded but difficult-to-read response.
Selecting a flow joins three facts: the page requested this URL, the server returned it successfully, and the response body is JavaScript. That chain is stronger than downloading a URL copied from somebody else’s report.
- Keep the network identity attached: The left pane keeps the selected URL, method, status, compressed size and timing visible while we inspect its response.
- Open the actual response: Response is selected rather than Request. This avoids confusing browser request headers with evidence returned by the JavaScript host.
- Verify status and content type: HTTP/2 200 and content-type application/javascript prove the selected object is a successful JavaScript response.
- The proxy decoded the gzip body: The bottom-right pane labels the body JavaScript [Decoded gzip]. The source is present, but its state machine and one-line layout make a dedicated extraction and beautification necessary.
ObservedThe selected HTTP 200 response is gzip-compressed JavaScript from the exact Fireye version loaded by the page.
Next stepExtract that observed URL into temporary storage and hash it so every later code screenshot is tied to one exact response.
Step 4: extract and fingerprint the evidence file
The live URL can change after our visit. We need one temporary copy plus two identifiers—the decoded byte count and a SHA-256 digest—so every later line number, YARA match and screenshot refers to the same bytes. Expected result: one saved JavaScript file, one byte count and one 64-character digest.
Here “fingerprint” means a file integrity digest, not browser tracking. Think of SHA-256 as a tamper-evident label calculated from every byte. It does not encrypt the file and cannot reconstruct it. If one byte changes, the label should change; matching labels give us strong evidence that two copies contain the same bytes [15].
url='https://assets.aliexpress-media.com/g/AWSC/fireyejs/1.231.67/fireyejs.js'
curl --fail --silent --show-error --compressed "$url" --output /tmp/alibaba-fireye.js
wc -c /tmp/alibaba-fireye.js
sha256sum /tmp/alibaba-fireye.js| Command part | What it means | Why it is here |
|---|---|---|
| url='https://…/fireyejs.js' | Store the exact observed URL in a shell variable | Avoids retyping or silently changing the acquisition target |
| curl | Command-line HTTP client | Downloads the same public response the browser requested [14] |
| --fail | Return an error for HTTP failure responses | Stops a 404 or server error page becoming our “JavaScript evidence” |
| --silent --show-error | Hide the progress meter but keep useful errors | Produces clean evidence without concealing a failed download |
| --compressed | Request compressed transfer and automatically decode it | Saves readable JavaScript bytes rather than gzip-compressed bytes |
| "$url" | Read the stored URL as one quoted value | Prevents shell splitting or accidental target changes |
| --output /tmp/alibaba-fireye.js | Save the response under a temporary filename | Creates the bounded local evidence file used below |
| wc -c | Count bytes; wc means “word count” and -c selects bytes | Explains why the decoded file is 462,779 bytes [16] |
| sha256sum | Calculate the file’s SHA-256 digest | Binds later analysis to this exact byte sequence |
- Download only the observed object: The curl URL is copied from the selected mitmweb flow. --compressed asks curl to decode gzip so the saved bytes match the JavaScript we will analyse.
- Record the decoded size: wc reports 462,779 bytes. mitmweb showed the smaller compressed transfer size; both can be correct because gzip changes the number of bytes sent over the network.
- Bind the rest of the review: The complete SHA-256 identifies this exact decoded file. Later line numbers and screenshots can be checked against the same digest.
ObservedThe current Fireye response was extracted to temporary storage and bound to one byte count and SHA-256.
Next stepThe file has more than 460 KB of obfuscated code. Use a triage signature to locate the WebAudio capability before reading source line by line.
Step 5: triage the bundle with YARA
The evidence file is more than 460 KB and deliberately difficult to read. YARA gives us a transparent shortlist of related WebAudio words and their byte offsets. Expected result: several graph-building terms plus the analyser-read operation, which is enough to justify focused source review—not enough to claim the code executed.
YARA is a pattern-matching tool used in malware analysis and incident response, but it can scan any file. A rule gives names to strings and defines how many must appear before reporting a match. It is acting like a repeatable search checklist here, not labelling AliExpress as malware [11].
We require five of seven clues because one term is ambiguous: AudioContext can power a game or music player, and createGain can simply control volume. Five related terms are more likely to describe one complete path—create a context and signal, create measurement and processing nodes, then read frequency data. Requiring all seven would be brittle if a future minifier hid one name; requiring only one would produce noisy matches. The threshold is an analyst’s triage choice, not proof of maliciousness or execution.
Create a new text file at /tmp/webaudio-fingerprint.yar, paste the rule below and save it. The .yar extension is a convention; YARA reads the rule contents rather than trusting the filename.
rule WebAudio_Fingerprint_Capability
{
meta:
boundary = "String presence is not proof of runtime execution"
strings:
$context = "AudioContext" ascii
$oscillator = "createOscillator" ascii
$analyser = "createAnalyser" ascii
$gain = "createGain" ascii
$processor = "createScriptProcessor" ascii
$frequency_data = "getFloatFrequencyData" ascii
$waveform = "sawtooth" ascii
condition:
5 of them
}| Rule part | Plain-English meaning | Why it matters |
|---|---|---|
| rule WebAudio_Fingerprint_Capability | Give this search checklist a readable name | Makes the result self-explanatory |
| meta | Analyst notes that do not affect matching | Keeps the “presence is not execution” boundary beside the rule |
| strings | The named text clues to search for | Records exactly what the analyst considered relevant |
| ascii | Look for ordinary one-byte text | Matches the method names visible in this JavaScript bundle |
| $context … $waveform | Seven clues covering setup, graph nodes and measurement | Looks for a coherent capability rather than one scary word |
| 5 of them | Match when any five or more named strings are present | Balances tolerance to obfuscation against noisy one-string matches [11] |
yara --print-strings /tmp/webaudio-fingerprint.yar /tmp/alibaba-fireye.js| Command part | What it means | Why it is here |
|---|---|---|
| yara | Run the YARA command-line scanner | Applies our rule without executing the JavaScript |
| --print-strings | Show each matched string and its hexadecimal byte offset | Provides pivots into the large file instead of only a yes/no result |
| /tmp/webaudio-fingerprint.yar | The rule file we just explained | Makes the search criteria reproducible |
| /tmp/alibaba-fireye.js | The SHA-bound evidence file from Step 4 | Keeps the scan limited to one known object |
The VM already had the YARA analysis library but not its command-line frontend. Rather than change the machine globally, we downloaded the signed Kali yara package, unpacked its CLI into temporary storage and ran /tmp/yara. A normal sudo apt install yara installation uses the shorter yara command shown above and runs the same engine.
- Run one explained rule against one file: The command names the rule and the SHA-bound Fireye file explicitly. No directory-wide or live-site scanning occurs.
- Several related WebAudio APIs are present: AudioContext, oscillator, analyser, gain and ScriptProcessor strings appear together. That is enough to justify source review, but not yet a fingerprinting conclusion.
- Find the data-collection operation: getFloatFrequencyData is the analyser read we need to follow. Its hexadecimal offset leads to the routine that handles returned frequency bins.
- Two sawtooth locations deserve comparison: Two sawtooth matches suggest more than one graph-building branch. Beautifying the file will reveal whether their structures agree.
ObservedThe bundle matches a multi-string WebAudio capability rule, including two sawtooth locations and one frequency-data read.
Next stepString presence is not execution. Beautify the exact file and inspect the operations around those matches in context.
Step 6: beautify the code and read the audio graph
YARA proved that related words exist but not how they connect. We need the surrounding operations in execution order to distinguish an ordinary audio feature from a silent measurement path. Expected result: one readable source window that creates a signal, measures it, routes it through zero gain and attaches a processing callback.
Minification removes whitespace and shortens names so a browser downloads less data. Obfuscation goes further by hiding method names and control flow. js-beautify cannot undo the state machine, but it restores line breaks and indentation so related operations fit on one screen.
js-beautify -o /tmp/fireyejs.beautified.js -s 2 /tmp/alibaba-fireye.js
grep -nE 'createOscillator|getFloatFrequencyData|sawtooth' /tmp/fireyejs.beautified.js
code --goto /tmp/fireyejs.beautified.js:3980:1| Command part | What it means | Why it is here |
|---|---|---|
| js-beautify | Format JavaScript without executing it | Turns one minified line into navigable source |
| -o /tmp/fireyejs.beautified.js | Write a separate formatted copy | Preserves the original SHA-bound file unchanged |
| -s 2 | Indent nested code by two spaces | Fits more useful context on screen while keeping structure visible |
| /tmp/alibaba-fireye.js | Read the evidence file from Step 4 | Keeps the transformation tied to the known response |
| grep -nE | Search with line numbers and an extended pattern | Translates YARA’s byte-offset clues into readable locations in the formatted copy |
| term1|term2|term3 | The vertical bars mean “match any of these terms” | Finds graph creation, analyser collection and waveform locations in one pass |
| code --goto file:3980:1 | Open VS Code at line 3980, column 1 | Jumps from the YARA-led review to the first relevant graph window |
The blue background is the analyst’s manual text selection, not a warning or automatic detection from VS Code. It marks the exact obfuscated lines translated below; the red boxes separate node creation from connection and silence.
- Keep the reviewed file and lines visible: VS Code shows fireyejs.beautified.js and line numbers 3980–3985, making this window reproducible against the earlier SHA-256.
- Create the signal and measurement nodes: The blue selection is the analyst’s VS Code highlight. These lines call createOscillator, createAnalyser, createGain and a dynamically assembled createScriptProcessor with 4096, 1, 1 arguments.
- Silence the end, not the measurement: Line 3985 sets gain value 0 and waveform sawtooth, then connects oscillator → analyser → processor → gain → destination. The analyser has already received the signal before it becomes silent.
ObservedOne Fireye branch builds the complete real-time WebAudio graph reported by the original investigator.
Next stepA graph can process audio without fingerprinting. Follow the analyser read and see what the code does with the returned values.
| Code clue | Plain-English job | Why it matters |
|---|---|---|
| createOscillator() | Generate a predictable signal inside the browser | No microphone or sound file is required |
| oscillator.type = "sawtooth" | Choose a waveform rich in multiple frequencies | Gives the analyser a repeatable test pattern |
| createAnalyser() | Measure the signal by frequency | Produces the numeric values followed in Step 7 |
| createScriptProcessor(4096, 1, 1) | Run JavaScript when a 4,096-sample audio block is processed | Provides the callback that collects the measurement |
| createGain(); gain.value = 0 | Set the final output volume to silence | The analyser still sees the signal before this final mute point |
| oscillator → analyser → processor → gain → destination | Connect the nodes into one live processing graph | Explains both the silent measurement and the reported active audio path |
What stays the same
The JavaScript, sawtooth waveform and requested graph shape give browsers the same test.
What can differ
Browser audio code, operating system routines and CPU floating-point behaviour can alter tiny parts of the calculated output.
Why zero gain does not stop measurement
The analyser reads upstream. Turning the final volume to zero prevents audible output but does not erase values already collected.
What this does not do
It does not listen through the microphone. The signal is generated and measured inside the browser [12] [13].
// Plain-English reconstruction of the selected source window
const oscillator = context.createOscillator();
const analyser = context.createAnalyser();
const gain = context.createGain();
const processor = context.createScriptProcessor(4096, 1, 1);
gain.gain.value = 0;
oscillator.type = 'sawtooth';
oscillator.connect(analyser);
analyser.connect(processor);
processor.connect(gain);
gain.connect(context.destination);Step 7: follow the analyser values
Building an audio graph is not automatically fingerprinting; games and visualisers do it legitimately. We now ask whether Fireye reads the calculated output and turns it into something comparable. Expected result: an array sized to the analyser, a frequency-data read and a loop that joins every returned value.
A frequency bin is one slot covering a small part of the frequency range—like one bar in a music visualiser. For this graph, frequencyBinCount gives 1,024 slots. getFloatFrequencyData copies one decibel value into each Float32Array slot [12]. Fireye then loops over those values and joins them with a separator. That collection behaviour moves the evidence from “audio exists” to “the output is being measured and made comparable.”
- Navigate to the YARA-derived location: The selected file remains the SHA-bound beautified Fireye response, now at lines 2434–2435 near the getFloatFrequencyData match.
- Do not over-link the transport capability: sendBeacon appears at line 2420 in the surrounding state machine. Presence proves transport capability, not that this call received or sent the audio-bin string.
- Allocate and fill every frequency bin: The reversed text resolves to frequencyBinCount. The code creates the array and calls getFloatFrequencyData to populate it.
- Turn the measurements into one serial value: The loop continues while the index is below y.length and appends each bin plus a separator. That creates a value suitable for later comparison or inclusion in a larger fingerprint.
ObservedThe current served Fireye source contains the graph builders and a routine that reads and serializes every analyser bin.
Next stepStatic source proves capability, not runtime execution. Recreate only the documented graph locally and observe its result without Alibaba telemetry.
const bins = new Float32Array(analyser.frequencyBinCount);
analyser.getFloatFrequencyData(bins);
let serialised = '';
for (const value of bins) {
serialised += value + separator;
}| Code | Plain-English meaning | Why it matters |
|---|---|---|
| analyser.frequencyBinCount | Ask how many frequency slots the analyser returns | Sets the expected measurement length—1,024 here |
| new Float32Array(…) | Reserve one decimal-number slot for every bin | Creates the container that will hold the result |
| getFloatFrequencyData(bins) | Copy current decibel values into that array | This is the actual measurement operation [12] |
| for (const value of bins) | Visit every returned number | Shows the code uses the complete measurement rather than one display value |
| serialised += value + separator | Append each number and a delimiter to one string | Produces a consistently ordered value that can be compared or combined later |
sendBeacon can transmit data, but seeing it elsewhere in an obfuscated state machine does not prove this audio string reached that call. We would need a runtime data-flow or a matching outbound payload to establish that connection. This walkthrough did not observe either, so it reports transport capability and audio measurement separately.
Our instrumented headless browser did not observe the AudioContext branch execute. The branch may depend on user activation, risk state, browser features or another environment decision. We also did not prove which outbound request, if any, carried the serialized audio values. The current source proves the complete capability; those runtime and transport claims remain unobserved.
Step 8: reproduce only the mechanism locally
Static review shows capability but our live run did not observe the branch execute. A minimal local page lets us test the calculation without executing the wider obfuscated bundle or sending results anywhere. Expected result: five measurements from the same browser, visible cleanup after every run and an honest count of repeated versus different results.
A safe reconstruction lets us answer a narrower question: does this browser return a repeatable result when we run the documented graph? Our page contains no Alibaba code, no Canvas or hardware collection, no microphone request and no network telemetry. It stops the oscillator and closes the audio context after every run.
The lab hashes each 1,024-number result only to give the long array a short comparison label. If two labels match, the underlying serialized arrays matched; if the labels differ, at least one measured value differed. The digest does not make the browser result more unique or stable—it only makes the five-run comparison readable.
- Verify the lab does less than the target code: The top strip states microphone never requested, output gain zero, no demo telemetry and explicit stop plus close cleanup.
- Run the same minimum graph: The left pane lists oscillator, analyser, processor and zero-gain destination in the same order recovered from Fireye.
- Read every result, not only the convenient one: The right pane shows two distinct digests across five runs and a 3/5 dominant group. That demonstrates partial repeatability and real within-browser variation.
ObservedFive local runs returned two digests, with the dominant result appearing three times and no external request made by the demo.
Next stepThe result is a stable clue in some runs, not a unique identity. Put it beside the other browser signals and explain the practical impact.
Repeatable can still be fickle
The same browser returned two hashes across five identical calculations. One appeared twice and the other appeared three times.
A service can treat related values as clues, but a single raw audio digest should not be presented as a unique or permanent device identity. Academic work reached the same conclusion: audio is less diverse alone and more useful when combined with other signals [5].
Step 9: connect the fingerprint to real-world impact
A 64-character digest means little to a shopper. We now place the audio clue beside the other reported signals and ask what a service could do when a later visit has no old cookie. Expected result: a clear distinction between possible correlation, legitimate fraud scoring and the server-side use we did not observe.
A browser fingerprint works like narrowing a crowd. “Uses Chromium” describes millions of people. Add operating system, screen size, GPU renderer, Canvas output, processor count, WebRTC behaviour and an audio result, and the matching group can become much smaller. Audio does not need to identify a person by itself to add useful information.
| Signal | What varies | Why it can help recognition |
|---|---|---|
| WebAudio | Browser processing and broad CPU maths differences | Adds a processing clue |
| Canvas | Fonts, rasterisation and graphics output | Creates a rendering clue |
| WebGL | GPU, driver, renderer and shader behaviour | Narrows hardware/software groups |
| Screen and hardware | Viewport, pixel ratio, memory and processor count | Adds configuration clues |
| Interaction | Mouse, touch, focus, scrolling and automation patterns | Adds behavioural and bot clues |
| Combined profile | The intersection of all observed values | Can remain useful after cookies change |
A shopper clears cookies or opens a private window. The site cannot read the old cookie, but the new visit may still present a similar GPU, screen, Canvas, WebAudio and interaction profile. That profile could help a fraud system recognize a familiar device, or it could help correlate browsing without the transparency of a normal cookie. The browser evidence cannot tell us which server-side use Alibaba chose.
The audio mechanism also had a physical side effect. The gain was zero, but the graph connected to the real-time audio destination and did not cleanly close every source. On the affected Windows and Firefox setup, that kept the computer side of multipoint headphones active and stopped them handing audio back to a phone. An older Mozilla report also found the lingering request could prevent idle sleep [1] [3].
Research involving 2,093 participants found only 95 distinct audio fingerprints, while also showing audio added information to established fingerprinting vectors [5]. Firefox later reduced most WebAudio diversity to a few broad result groups, largely reflecting CPU instruction differences [4]. The honest conclusion is therefore narrower than the headline: the audio test is real, but the composite profile is the more important privacy object.
Step 10: protect users, clean up and retain only the evidence
The reader now understands the capability and its limits. The final step turns that knowledge into user and developer actions, then removes the temporary browser, full third-party source and raw capture. Expected result: only reviewed screenshots, small explained excerpts, hashes and the no-telemetry local demonstration remain.
Use Firefox with Mozilla’s recommended uBlock Origin extension, then add four narrow network filters: one Collina and one Fireye rule for each delivery host observed in our runs [17]. Firefox also reduces WebAudio fingerprinting variation, while the filters stop matching scripts before delivery [4] [19]. The narrow scope avoids blocking either CDN wholesale.
The original Chromium capture used assets.aliexpress-media.com and Fireye 1.231.67. During the approved-video preflight, Firefox received Fireye 1.234.20 from g.alicdn.com instead [9] [20]. The original two filters did not match that newer host. This is direct evidence for re-testing a mitigation rather than assuming a wildcard version alone is enough.
! AliExpress AWSC families across both delivery hosts observed in this investigation
||assets.aliexpress-media.com/g/AWSC/uab/*/collina.js$script,domain=aliexpress.com
||assets.aliexpress-media.com/g/AWSC/fireyejs/*/fireyejs.js$script,domain=aliexpress.com
||g.alicdn.com/AWSC/uab/*/collina.js$script,domain=aliexpress.com
||g.alicdn.com/AWSC/fireyejs/*/fireyejs.js$script,domain=aliexpress.com| Filter part | Plain-English meaning | Why it is narrow |
|---|---|---|
| ||assets.aliexpress-media.com | Anchor two rules to the host captured in the main Chromium investigation | Covers the /g/AWSC path without blocking the whole host |
| ||g.alicdn.com | Anchor two rules to the newer host observed in Firefox video preflight | Covers the current /AWSC path without blocking the whole CDN |
| /g/AWSC/uab/ … collina.js | Match the reported Collina family path | Targets one named script family |
| /g/AWSC/fireyejs/ … fireyejs.js | Match the reported Fireye family path | Targets the second named family |
| * | Allow the version directory to change | Covers a new version only when the surrounding family path still matches |
| $script | Apply only to JavaScript requests | Does not block images, styles or the whole page |
| domain=aliexpress.com | Apply when AliExpress is the requesting site | Avoids a global rule for every site [18] |
Install from Mozilla Add-ons
Use the official uBlock Origin listing by Raymond Hill, not a similarly named copy [17].
Open Dashboard → My filters
Paste all four rules, select Apply changes, then close every existing AliExpress tab.
Open a new AliExpress tab
The new page load is important because blocking a request cannot stop an AudioContext that an old tab already created [1].
Expect a possible trade-off
These bundles appear connected to anti-abuse controls, so login or checkout may show more CAPTCHAs or fail. Temporarily disable the custom rules if they block a legitimate transaction [1].
The next three genuine Firefox views prove the extension state, saved rules and current network result. The uBlock logger shows Fireye 1.234.20 from g.alicdn.com blocked as a script request. Collina did not appear in that Firefox run, so the protected claim for this lane is one observed Fireye block—not two.
- Use the real extension: Firefox identifies uBlock Origin and shows Mozilla’s recommended-extension trophy beside it.
- Check the enabled state: The extension is under Enabled and its switch is on before any protected visit begins.
ObserveduBlock Origin is installed, recommended by Mozilla and enabled in this clean Firefox profile.
Next stepOpen My filters and verify both observed delivery hosts are covered.
- Custom filters are enabled: The checked setting allows the four narrow user rules below to participate in request filtering.
- Cover the original host: The first pair covers the AliExpress Media host captured in the main investigation.
- Cover the current Firefox host: The second pair covers g.alicdn.com, observed serving Fireye 1.234.20 during video preflight.
- Applied, not merely typed: Apply changes is disabled after saving, showing there is no pending unsaved edit.
ObservedFour narrow custom filters are enabled and saved.
Next stepClose old target tabs, open a fresh AliExpress tab and inspect the uBlock logger.
- Filter the logger to Fireye: The logger search leaves the decisive Fireye network event visible without unrelated traffic.
- Read the blocking rule: The red row begins with the g.alicdn.com rule and --, uBlock’s blocked-request result.
- Confirm the current script path: The blocked request is Fireye 1.234.20 from the newer g.alicdn.com delivery host.
ObserveduBlock Origin blocked the current Fireye 1.234.20 script request before delivery in this Firefox profile.
Next stepUse the independent verifier as a repeatable second lane and retain the profile-scoped claim boundary.
For repeatable evidence, our independent Chromium verifier mirrors both family boundaries across both observed delivery hosts. It records only matching script-family events and instruments AudioContext before the page loads. It supports the control result, but it is not presented as proof that uBlock Origin itself was enabled; the Firefox views above provide that evidence.
node content/forensics/verify-aliexpress-script-blocking.mjs- Run the bounded verifier: The local script opens one fresh Chromium session and intercepts only the two exact reported script families.
- Name both controls: Collina and Fireye are configured separately so one successful block cannot hide a missed family.
- Read the observed blocks: Two script requests were blocked and no AudioContext was observed in this protected run.
- Keep the claim profile-scoped: Mitigated means this profile and run. It does not claim AliExpress changed or future paths will match.
ObservedThe fresh protected visit returned HTTP 200 while two matching script requests—Collina and Fireye—were blocked; no AudioContext was observed.
Next stepRecord the scope limits, keep browser-level fingerprinting protection enabled and remove all temporary acquisition material.
| Claim | Result | Boundary |
|---|---|---|
| Current Firefox Fireye request ran after enabling the rule | Prevented | The uBlock logger shows Fireye 1.234.20 blocked before delivery |
| An AudioContext appeared after blocking | Not observed | Zero was recorded during this protected run |
| A future version under the same family paths | Covered by the wildcard | Re-test because this preflight proved the host or path can change |
| All browser fingerprinting on AliExpress | Not established | Other scripts and passive signals remain possible |
| AliExpress changed globally | Not claimed | The control exists only in the user’s protected browser profile |
- Keep Firefox fingerprinting protection enabled as a second layer; script blocking and output normalisation address different parts of the problem [19].
- Site owners should collect only the signals needed for a named security decision, disclose purpose and retention, and run invasive checks at the point of risk rather than silently on a general homepage [6].
- WebAudio users should stop oscillators, disconnect nodes and close one-shot audio contexts immediately after the legitimate work finishes.
Disconnecting output nodes alone can leave a source or context alive.
analyser.disconnect();
processor.disconnect();
gain.disconnect();
// oscillator.stop() missing
// context.close() missingStop the source and close the context as soon as the one-shot operation completes.
oscillator.stop();
oscillator.disconnect();
analyser.disconnect();
processor.disconnect();
gain.disconnect();
await context.close();| Line | Plain-English meaning | What it prevents |
|---|---|---|
| processor.onaudioprocess = null | Remove the JavaScript audio callback | Stops further measurement code running |
| oscillator.stop() | Stop the generated source | Ends the live signal instead of only detaching later nodes |
| node.disconnect() | Remove each connection in the graph | Releases the routing path and object references |
| await context.close() | Shut down the one-shot AudioContext | Returns the browser and system audio resources promptly |
# Close Chromium, mitmweb and VS Code first.
rm -f -- /tmp/alibaba-fireye.js /tmp/fireyejs.beautified.js /tmp/webaudio-fingerprint.yar
if [[ -n "$profile" && "$profile" == /tmp/aliexpress-browser.* ]]; then
rm -rf -- "$profile"
fi| Command part | What it means | Why it is here |
|---|---|---|
| rm -f -- file1 file2 file3 | Remove the three named temporary analysis files; -- ends option parsing | Deletes only the bounded files listed explicitly |
| if [[ … ]]; then | Run the profile deletion only when both safety checks pass | Prevents an empty or unexpected path being removed |
| -n "$profile" | Confirm the variable is not empty | Rejects an unresolved deletion target |
| "$profile" == /tmp/aliexpress-browser.* | Confirm the path matches our temporary-profile prefix | Keeps recursive removal inside the directory family created in Step 1 |
| rm -rf -- "$profile" | Recursively remove that validated disposable browser directory | Deletes cookies, cache and the temporary proxy trust state |
The repository keeps reviewed screenshots, their annotations, non-sensitive response metadata, byte counts, hashes, small source excerpts and the no-telemetry reconstruction. The disposable Chromium profiles, raw mitmproxy flow, complete Fireye and Collina bundles, YARA package, cookies and request payloads are removed.
Do not stop at a scary API name. Start from an observed request, bind the exact evidence, use a detection tool to narrow the search, read the operations in context, reproduce only the necessary mechanism, report negative findings and finish with the practical effect on a real person. That is what turns a security headline into an investigation somebody else can follow.
References
Numbered sources support the practical points in this article. Video timestamps will appear when the related media is published.
- AliExpress webpage keeping multipoint Bluetooth headphones active with WebAudio fingerprintinghttps://blog.laserphile.com/2026/08/aliexpress-webpage-keeping-multipoint.html
The original investigation that identified two audio contexts, the source script families and the Bluetooth side effect.
- fireyejs WebAudio fingerprint — standalone reimplementationhttps://ritter.vg/misc/ff/webaudio-alibaba.html
An independent, no-telemetry reconstruction used to cross-check the deobfuscated Fireye algorithm.
- Audio power request prevents sleep on Windows 11 on AliExpress pageshttps://bugzilla.mozilla.org/show_bug.cgi?id=1863193
The public Firefox defect report, including the observed node graph and oscillator cleanup problem.
- WebAudio fingerprinting on Alibabahttps://ritter.vg/blog-webaudio_alibaba.html
A Firefox engineer’s analysis of WebAudio result diversity after Firefox fingerprinting protections.
- A Study of Feasibility and Diversity of Web Audio Fingerprintshttps://arxiv.org/abs/2107.14201
A 2,093-participant study of WebAudio fingerprint stability, diversity and additive value.
- Mitigating Browser Fingerprinting in Web Specificationshttps://www.w3.org/TR/fingerprinting-guidance/
Current guidance on active fingerprinting, privacy impact, data minimisation and detectability.
- Fingerprinting defenses 2.0https://brave.com/privacy-updates/4-fingerprinting-defenses-2.0/
Brave’s explanation of per-session, per-site randomisation for WebAudio and Canvas outputs.
- AliExpress was silently running audio in your browser to fingerprint and track your devicehttps://www.techspot.com/news/113581-aliexpress-silently-running-audio-browser-fingerprint-track-device.html
The news report that prompted this follow-along investigation.
- Alibaba AWSC Fireye browser-security bundle, version 1.231.67https://assets.aliexpress-media.com/g/AWSC/fireyejs/1.231.67/fireyejs.js
The exact live response acquired, hashed and reviewed during this walkthrough.
- Alibaba AWSC Collina browser-security bundle, version 1.140.0https://assets.aliexpress-media.com/g/AWSC/uab/1.140.0/collina.js
The second reported AWSC bundle served during the same isolated acquisition.
- Writing YARA ruleshttps://yara.readthedocs.io/en/stable/writingrules.html
The official guide to YARA strings, conditions and the “of” operator used in this walkthrough.
- AnalyserNode.getFloatFrequencyData()https://developer.mozilla.org/en-US/docs/Web/API/AnalyserNode/getFloatFrequencyData
Defines how an analyser copies decibel values for frequency bins into a Float32Array.
- Web Audio API concepts and usagehttps://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API
An accessible introduction to audio contexts, sources, processing nodes and routing graphs.
- curl command-line manualhttps://curl.se/docs/manpage.html
The authoritative meanings of the curl flags used to extract the selected response.
- FIPS 180-4 Secure Hash Standardhttps://csrc.nist.gov/pubs/fips/180-4/upd1/final
The standard defining SHA-256 and the other Secure Hash Algorithms.
- wc invocationhttps://www.gnu.org/software/coreutils/manual/html_node/wc-invocation.html
The official explanation of wc and its byte-counting -c option.
- uBlock Origin for Firefoxhttps://addons.mozilla.org/en-US/firefox/addon/ublock-origin/
Mozilla’s recommended listing for the open-source content blocker used for the narrow user-side filters.
- uBlock Origin static filter syntaxhttps://github.com/gorhill/uBlock/wiki/Static-filter-syntax
The maintained syntax reference for hostname anchors, wildcards, resource types and domain restrictions.
- Firefox's protection against fingerprintinghttps://support.mozilla.org/en-US/kb/firefox-protection-against-fingerprinting
Mozilla’s current user guidance for fingerprinting protection and Resist Fingerprinting.
- Alibaba AWSC Fireye browser-security bundle, version 1.234.20https://g.alicdn.com/AWSC/fireyejs/1.234.20/fireyejs.js
The newer delivery host and Fireye version observed in Firefox during companion-video preflight.