Is Sparkles Giveaway safe?
Sparkles Giveaway injects a button on YouTube that encrypts Steam trade offer credentials with a hardcoded operator-held RSA key, and fetches remote configuration via a covert DNS-over-HTTPS channel.
On YouTube pages belonging to the Sparkles channel, the extension reads Steam trade offer URLs from the page, extracts the partner ID and trade token, and encrypts them using RSA-OAEP with a public key whose corresponding private key is held by the sparkles.tv operator. Separately, the background service worker retrieves configuration by querying the Cloudflare DNS-over-HTTPS API for CERT records on sparkles.tv, decoding embedded JSON from the DNS response. This DNS channel allows the operator to deliver arbitrary configuration to the extension without a conventional server request.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Steam Trade Tokens Encrypted to Operator-Controlled Key on YouTube
Pasting a Steam trade URL into a YouTube comment on a Sparkles page and clicking "Encrypt" encrypts the partner ID/token with an operator RSA key, replacing the URL.
Only the matching private key recovers it, so the operator can decrypt.
- Severity
- High unwanted
- Type
- Unexpected
- CWE
- CWE-522
- Source
- Dynamic sandbox
You paste a Steam trade offer URL into the YouTube comment box on a Sparkles channel page and click "Encrypt".
The Encrypt button is only visible on YouTube pages where document.querySelector("[href='/@Sparkles']") returns a match.
The extension extracts your trade token and partner ID, encrypts them with a hardcoded public key, and replaces the original URL with the ciphertext.
The RSA public key (JWK) is embedded in the extension source. Only the party holding the corresponding private key can decrypt the result.
- Steam partner ID123456789
Identifies the Steam account receiving the trade offer. Used by the recipient to initiate the trade.
- Trade tokenABCDefgh
An 8-character credential needed to send a trade offer to a non-public profile. Possession lets anyone send trade offers to that account.
Hardcoded RSA public key and encryption call
// a = `${partner};${token}` (e.g. '123456789;ABCDefgh')const importedKey = await crypto.subtle.importKey( 'jwk', { key_ops: ['encrypt'], ext: true, kty: 'RSA', n: 'vNSKsc9WDFvgSXuCO8hTpJdyQUQv2V8-BGOe4bKImzN8I9zer4bqFFB1W35M8sQ4YCdRWQbTgcDrcgW4ix5ylhYZeEXyiihUHe8-sRlrCHqpmJJVdGyUynLz', e: 'AQAB', alg: 'RSA-OAEP-256' }, { name: 'RSA-OAEP', hash: 'SHA-256' }, false, ['encrypt']);const encoded = new TextEncoder().encode(a);const ciphertext = await crypto.subtle.encrypt({ name: 'RSA-OAEP' }, importedKey, encoded);const base64Ciphertext = Buffer.from(ciphertext).toString('base64');commentBox.textContent = commentBox.textContent.replace(tradeUrl, base64Ciphertext);The original trade URL is replaced in the comment box with base64 RSA ciphertext. The ciphertext can only be decrypted by the holder of the matching private key. A PoC confirmed the key is real and functional.
partner=123456789;token=ABCDefgh (plaintext: '123456789;ABCDefgh')Reproduces the RSA encryption step using the hardcoded JWK from the extension. Encrypts a sample partner;token tuple and prints the base64 ciphertext. Demonstrates the key is functional and that only the holder of the private key can decrypt the result.
- Node.js 18+
// Node.js 18+ — uses the built-in WebCrypto API// Usage: node sparkles-encrypt-reproducer.jsimport { webcrypto } from 'node:crypto';const { subtle } = webcrypto;const JWK = { key_ops: ['encrypt'], ext: true, kty: 'RSA', n: 'vNSKsc9WDFvgSXuCO8hTpJdyQUQv2V8-BGOe4bKImzN8I9zer4bqFFB1W35M8sQ4YCdRWQbTgcDrcgW4ix5ylhYZeEXyiihUHe8-sRlrCHqpmJJVdGyUynLz', e: 'AQAB', alg: 'RSA-OAEP-256'};const plaintext = '123456789;ABCDefgh'; // partner;token formatconst key = await subtle.importKey( 'jwk', JWK, { name: 'RSA-OAEP', hash: 'SHA-256' }, false, ['encrypt']);const encoded = new TextEncoder().encode(plaintext);const ciphertext = await subtle.encrypt({ name: 'RSA-OAEP' }, key, encoded);const b64 = Buffer.from(ciphertext).toString('base64');console.log('Input: ', plaintext);console.log('Ciphertext:', b64);console.log('\nEach run produces different ciphertext (OAEP randomness) but all');console.log('are decryptable only with the private key held by the sparkles.tv operator.');- 1Run: node sparkles-encrypt-reproducer.js (requires Node.js 18+).
- 2Each run produces a different base64 ciphertext from the same input, confirming OAEP randomness.
- 3None are decryptable without the private key.
What it can do
Permissions this extension asks for, as declared in version 1.3.0. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to. The current listed version is 1.3.1, which we have not unpacked yet.
Read and change your data on youtube.com
https://youtube.com/watch*
Read and change your data on www.youtube.com
https://www.youtube.com/watch*
Where it sends data
Destinations our analysis observed Sparkles Giveaway contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.
- cloudflare-dns.com
Sparkles Giveaway sends data to cloudflare-dns.com. One other extension we have analysed sends data here.
- sparkles.tv
Sparkles Giveaway sends data to sparkles.tv. No other extension we have analysed sends data here.