Is Sparkles Giveaway safe?

High risk

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.

iffectivev1.3.1Chrome Web Store
72Risk

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
What actually happens
You did this

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 did this

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.

Fields extracted from trade URL before encryption
  • Steam partner ID
    123456789

    Identifies the Steam account receiving the trade offer. Used by the recipient to initiate the trade.

  • Trade token
    ABCDefgh

    An 8-character credential needed to send a trade offer to a non-public profile. Possession lets anyone send trade offers to that account.

The code that does this

Hardcoded RSA public key and encryption call

Readable version
// 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);
Why you can't catch this in DevTools

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.

Decoded value
partner=123456789;token=ABCDefgh  (plaintext: '123456789;ABCDefgh')
Reproduce it yourself

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.

Requires
  • Node.js 18+
sparkles-encrypt-reproducer.js · js
// 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.');
How to run it
  1. 1Run: node sparkles-encrypt-reproducer.js (requires Node.js 18+).
  2. 2Each run produces a different base64 ciphertext from the same input, confirming OAEP randomness.
  3. 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.

Updated 30 September 2026cnjajifclgonpcbbfiifccjmoihadjgl