Is Designer Tools safe?
Designer Tools is medium risk. Opening Designer Tools reads the page URL/domain, pairs them with your account ID and name, sends the bundle to designer.tools/api/usage within 10s, AES-encrypted with a key hardcoded into every install, letting anyone with it decrypt data.
Who publishes itdesigner.tools.extension - no other listings under this identity
designer.tools.extension - no other listings under this identity
What this publisher told the store about itself, and the other listings that told it the same thing.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
Designer Tools sends active-tab URL and account name to developer server
Opening Designer Tools reads the page URL/domain, pairs them with your account ID and name, sends the bundle to designer.tools/api/usage within 10s, AES-encrypted with a key hardcoded into every install, letting anyone with it decrypt data.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-359
- Source
- Dynamic sandbox
You click the Designer Tools icon on any browser tab.
This fires chrome.action.onClicked; the extension has no persistent content scripts, so no tracking occurs unless you interact with the toolbar button.
The extension reads the tab's URL and domain, attaches your account ID and display name, and sends the bundle to designer.tools within 10 seconds.
The payload is AES-CBC encrypted before transmission using a key that is identical across every installation of the extension.
- Full page URLhttps://en.wikipedia.org/wiki/Main_Page
The complete address of the tab you had open when you clicked the extension icon, including any path, query string, or fragment.
- Page domainen.wikipedia.org
The hostname of the site you were visiting.
- Account IDusr_a1b2c3d4e5f6
Your unique identifier on the designer.tools platform, which ties the usage record back to your account.
- Account display nameAlice Smith
Your display name as stored by designer.tools, included in every usage event.
- Extension version5.6.2
The installed extension version, used by the server to correlate events across upgrades.
- Event nameOpen extension
The UI action that triggered the record (e.g. 'Open extension', 'tool change').
Every 10 seconds
The service worker flushes the g0[] event queue to designer.tools/api/usage every 10 seconds as long as the browser is running, sending all accumulated events in a single encrypted POST.
The POST body is AES-256-CBC encrypted using the SimpleCrypto library with a key hardcoded in service_worker.js. The wire format is URL-safe base64 (slashes replaced with underscores, plus signs replaced with hyphens). The key is identical in every installation, so any copy of the extension source is sufficient to decrypt any batch ever posted to designer.tools/api/usage.
[ { "event": "Open extension", "options": { "url": "https://en.wikipedia.org/wiki/Main_Page", "domain": "en.wikipedia.org" }, "user": { "id": "usr_example_123", "name": "TestUser", "version": "5.6.2" } }]Hardcoded encryption key and fetch pipeline (service_worker.js)
Semantic reading of the key and helpers
const HARDCODED_AES_KEY = "n2owB17LRKJyxfFze6zcL3lxopjhlD1wxzPD3g8dyUZA3GXY8wDYSBFz";const crypto = new SimpleCrypto(HARDCODED_AES_KEY);const encryptForWire = payload => crypto.encrypt(payload).replace(/\//g, "_").replace(/\+/g, "-");// decryptFromWire available too — the key is symmetricSemantic reading of the batch flush
const USAGE_ENDPOINT = "https://designer.tools/api/usage";const eventQueue = [];async function flushQueue() { if (eventQueue.length === 0) return; const batch = [...eventQueue]; eventQueue.length = 0; const response = await fetch(USAGE_ENDPOINT, { method: "POST", headers: { "Content-Type": "text/plain" }, body: encryptForWire(batch) // AES-CBC with hardcoded key });}setInterval(flushQueue, 10_000); // flush every 10 secondsSemantic reading of the URL capture
async function trackEvent(eventName, options = {}) { options.url = window.location.href; // full URL of active tab options.domain = window.location.hostname; // domain of active tab serviceWorker.sendMessage({ message: "track", options: { event: eventName, options } });}Demonstrates that the hardcoded AES key embedded in service_worker.js can encrypt and decrypt the exact payload format posted to designer.tools/api/usage. Performing a round-trip on a sample payload proves the key is functional and that any intercepted POST body can be read with it.
- Node.js 18+
/** * Reproducer: Designer Tools extension — hardcoded AES key * Extension: jiiidpmjdakhbgkbdchmhmnfbdebfnhp v5.6.2 * Key source: service_worker.js:3810 */const crypto = require('crypto');const HARDCODED_KEY = 'n2owB17LRKJyxfFze6zcL3lxopjhlD1wxzPD3g8dyUZA3GXY8wDYSBFz';function sha3_512(str) { return crypto.createHash('sha3-512').update(str).digest('hex');}function pbkdf2Sync(password, salt, iterations, keylen) { return crypto.pbkdf2Sync(Buffer.from(password, 'hex'), Buffer.from(salt, 'hex'), iterations, keylen, 'sha1');}function simpleCryptoDecrypt(ciphertext, secretKey) { const normalized = ciphertext.replace(/_/g, '/').replace(/-/g, '+'); const salt = normalized.substring(0, 32); const iv = normalized.substring(32, 64); const payload = normalized.substring(64, normalized.length - 64); const secretHash = sha3_512(secretKey); const derivedKey = pbkdf2Sync(secretHash, salt, 100, 32); const decipher = crypto.createDecipheriv('aes-256-cbc', derivedKey, Buffer.from(iv, 'hex')); let decrypted = decipher.update(payload, 'base64', 'utf8'); decrypted += decipher.final('utf8'); return decrypted;}// To decrypt a real captured POST body, replace CAPTURED_BODY below:const CAPTURED_BODY = process.argv[2] || '826b1557dfc13da3c0d4e733b5d51ea70cf265c8a8ac0662e181c405c255537bLusXm8iW80JVEWrcIslnf9sKlKYA74PcU2Z5mnduF-YI0swOAEwBbDC1wWGNbtK1Qw8X5n2YDBkL4FFLARPCbZNw1o8iZ2-4pQN5kq4IrXIEVUqZh6JsuNsho-I8uEoBSSwdfCBGbvLTgkxLOQ9QRiB-E1-OeG4zzqWoMXdBUMTEjRUhaPLZhYXk5zdlwdeazM_YneNOU9WHwKkAy5Ef0smpni6I8g6AefJD2mrfEgr6AtDah4C2HUfD6JiMHt4r3730a0640fd00125d4b933578bbc5b1bb1224c54d08d2bbe056178fd0451c6c9';try { const plaintext = simpleCryptoDecrypt(CAPTURED_BODY, HARDCODED_KEY); console.log('Decrypted payload:'); console.log(JSON.stringify(JSON.parse(plaintext), null, 2));} catch (e) { console.error('Decryption failed:', e.message); process.exit(1);}- 1Intercept a POST to designer.tools/api/usage (mitmproxy/DevTools) while clicking the icon.
- 2Copy the response body.
- 3Run: node designer-tools-usage-decrypt.js '<body>'.
- 4JSON shows every event, URL, domain, user ID, account name.
- designer.tools
Developer's own server. Receives AES-encrypted batches with tab URL, domain, account ID, and display name per toolbar interaction. Only host in host_permissions.
What it can do
Permissions this extension asks for, as declared in version 5.6.2. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.
Read and change your data on designer.tools
https://designer.tools/
Run its own code inside the pages you visit
scripting
Act on the current tab, but only after you click the extension
activeTab
Store data in your browser
storage
See every page you navigate to, as you navigate to it
webNavigation