Is PocketTube: Youtube Subscription Manager safe?
PocketTube is medium risk. PocketTube sends the billing email and Paddle license tokens to a developer server, p.yousub.info, when a companion extension asks for paid status and the cached record isn't active, as a plain unencrypted form post.
Who publishes itDmytro Nabok - 1 other listing from the same operator, none carrying a finding
Dmytro Nabok - 1 other listing from the same operator, none carrying a finding
What this publisher told the store about itself, and the other listings that told it the same thing.
Same store account
1 other listing published from this account, 60k+ users between them, none of them carrying a finding.
Shared hosts - 5 hostnames
Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
PocketTube Sends Your Paddle Billing Email and License Tokens to Its Own Server
PocketTube sends the billing email and Paddle license tokens to a developer server, p.yousub.info, when a companion extension asks for paid status and the cached record isn't active, as a plain unencrypted form post.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-522
- Source
- Dynamic sandbox
One of the developer's own companion PocketTube extensions, already installed in the browser, sends this extension a message asking for the user's paid subscription status.
The message is only accepted from four specific extension IDs belonging to the same publisher, not from arbitrary websites or extensions.
PocketTube reads its locally cached Paddle subscription record, and if it doesn't already show active status, posts the billing email and license tokens to its own server.
The request carries the email address, every stored license token, a browser-type flag, and a partial-data flag.
- Your billing emailjordan.lee@gmail.com (illustrative)
The email address tied to the Paddle purchase, sent in the clear as a form field.
- Your license tokensPDL-8f3a1c-9e21b4 (illustrative)
Every subscription token stored for the account, sent as repeated t[] form fields; the server uses these to recognize the account.
- Browser typef
A short code identifying which browser is making the request.
- Partial-data flagy
A fixed flag telling the server this is a partial re-check rather than a first registration.
200 OK. The JSON response is merged back into the extension's locally cached subscription record.
email=<planted marker email>&t[]=<planted marker token>&type=f&partial=yThe sender check and the paddle/email POST, as shipped vs. formatted for readability
Sender-identity gate on the external message listener
build/background.jscase 114: if ("bplnofkhjdphoihfkfcddikgmecfehdd" === a.id || "playlist@yousub.info" === a.id || "ehbcnddflnbfekidfnieclbpknhkelkj" === a.id || "noeichdeafhgfdbnhbldnheclppjknhm" === a.id) { e.next = 118; break } if (!ku()) { e.next = 117; break } return e.abrupt("return");case 117: return e.abrupt("return", !0);case 118:The POST to p.yousub.info/paddle/email
build/background.jsfunction wl() { return wl = o(s().mark((function e(t) { return s().wrap((function(e) { for (;;) switch (e.prev = e.next) { case 0: return e.abrupt("return", new Promise(function() { var e = o(s().mark((function e(a) { var n, i, o, r, u, l, d, c, m; return s().wrap((function(e) { for (;;) switch (e.prev = e.next) { case 0: n = new AbortController, i = setTimeout((function() { return n.abort() }), 15e3), (o = new FormData).append("email", t.email), r = hl(t.tokens); try { for (r.s(); !(u = r.n()).done;) l = u.value, o.append("t[]", l) } catch (e) { r.e(e) } finally { r.f() } return o.append("type", Ml()), o.append("partial", "y"), e.next = 10, fetch("https://p.yousub.info/paddle/email", { body: o, method: "POST", mode: "cors", signal: n.signal }); case 10: if (d = e.sent, clearTimeout(i), 200 === d.status) { e.next = 15; break } return a({ data: Object.assign({}, t) }), e.abrupt("return"); case 15: return e.next = 17, d.json(); case 17: return c = e.sent, e.next = 20, Tl(); case 20: if (m = e.sent, 0 !== Object.keys(c || {}).length) { e.next = 27; break } if (chrome.storage.local.set({ api_counter: ++m }), !(m > 30)) { e.next = 27; break } return chrome.storage.local.set({ api_counter: 0 }), a({ data: { error: "race-limit" } }), e.abrupt("return"); case 27: a({ data: Object.assign({}, t, c) }); case 28: case "end": return e.stop() } }), e) }))); return function(t) { return e.apply(this, arguments) } }())); case 1: case "end": return e.stop() } }), e) }))), wl.apply(this, arguments)}- p.yousub.info
Subscription-verification backend run by PocketTube's own developer (yousub.info), not an independent third party, but the email and license tokens still leave the device.