Skip to content

Commit 51d9867

Browse files
committed
fixed sub-org specific bundles leaking to other sub-org sessions
1 parent 1fb0969 commit 51d9867

10 files changed

+85
-35
lines changed

export-and-sign/dist/bundle.3f29bf7d8e30fb1dd823.js

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

export-and-sign/dist/bundle.8ccdeed719db48416e34.js.LICENSE.txt renamed to export-and-sign/dist/bundle.3f29bf7d8e30fb1dd823.js.LICENSE.txt

File renamed without changes.

export-and-sign/dist/bundle.3f29bf7d8e30fb1dd823.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

export-and-sign/dist/bundle.8ccdeed719db48416e34.js

Lines changed: 0 additions & 3 deletions
This file was deleted.

export-and-sign/dist/bundle.8ccdeed719db48416e34.js.map

Lines changed: 0 additions & 1 deletion
This file was deleted.

export-and-sign/dist/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<!doctype html><html class="no-js"><head><link rel="icon" type="image/svg+xml" href="./favicon.svg"/><meta charset="utf-8"/><title>Turnkey Export</title><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="turnkey-signer-environment" content="__TURNKEY_SIGNER_ENVIRONMENT__"/><meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; base-uri 'self'; object-src 'none'; form-action 'none'"><link href="/styles.e084a69a94c0575bc6ba.css" rel="stylesheet" integrity="sha384-uIrxQTbBoDAwjgotQ+GUHgbxFM2iajB5QKNa4WuL9wn/Ou+2383e3dM2FCWOAq9m" crossorigin="anonymous"></head><body><h2>Export Key Material</h2><p><em>This public key will be sent along with a private key ID or wallet ID inside of a new <code>EXPORT_PRIVATE_KEY</code> or <code>EXPORT_WALLET</code> activity</em></p><form><label>Embedded key</label> <input name="embedded-key" id="embedded-key" disabled="disabled"/> <button id="reset">Reset Key</button></form><br/><br/><br/><h2>Inject Key Export Bundle</h2><p><em>The export bundle comes from the parent page and is composed of a public key and an encrypted payload. The payload is encrypted to this document's embedded key (stored in local storage and displayed above). The scheme relies on <a target="_blank" href="https://datatracker.ietf.org/doc/rfc9180/">HPKE (RFC 9180)</a></em>.</p><form><label>Bundle</label> <input name="key-export-bundle" id="key-export-bundle"/> <button id="inject-key">Inject Bundle</button><br/><label>Key Format</label> <select id="key-export-format" name="key-export-format"><option value="HEXADECIMAL">Hexadecimal (Default)</option><option value="SOLANA">Solana</option></select><br/><label>Organization Id</label> <input name="key-organization-id" id="key-organization-id"/></form><br/><br/><h2>Inject Wallet Export Bundle</h2><p><em>The export bundle comes from the parent page and is composed of a public key and an encrypted payload. The payload is encrypted to this document's embedded key (stored in local storage and displayed above). The scheme relies on <a target="_blank" href="https://datatracker.ietf.org/doc/rfc9180/">HPKE (RFC 9180)</a></em>.</p><form><label>Bundle</label> <input name="wallet-export-bundle" id="wallet-export-bundle"/> <button id="inject-wallet">Inject Bundle</button><br/><label>Organization Id</label> <input name="wallet-organization-id" id="wallet-organization-id"/></form><br/><br/><h2>Sign Transaction</h2><p><em>Input a serialized transaction to sign.</em></p><form><label>Transaction</label> <input name="transaction-to-sign" id="transaction-to-sign"/> <button id="sign-transaction">Sign</button></form><br/><br/><h2>Sign Message</h2><p><em>Input a serialized message to sign.</em></p><form><label>Message</label> <input name="message-to-sign" id="message-to-sign"/> <button id="sign-message">Sign</button></form><br/><br/><h2>Message log</h2><p><em>Below we display a log of the messages sent / received. The forms above send messages, and the code communicates results by sending events via the <code>postMessage</code> API.</em></p><div id="message-log"></div><div id="key-div"></div><script defer="defer" src="/bundle.921b01a774677f8e2da8.js" integrity="sha384-P/yUGeA+YjATjB94JS/FcpAKrqBRW/oFjpTPQJAEZMy2zDCV+2mfOqsTbuxZkCcy" crossorigin="anonymous"></script><script defer="defer" src="/bundle.8ccdeed719db48416e34.js" integrity="sha384-54fp27cW7hyp60JZSDbPoHZ8CRF09rdlr5J5iKQYIUFKkM6ggf68Br6KBtpJ9F6O" crossorigin="anonymous"></script></body></html>
1+
<!doctype html><html class="no-js"><head><link rel="icon" type="image/svg+xml" href="./favicon.svg"/><meta charset="utf-8"/><title>Turnkey Export</title><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="turnkey-signer-environment" content="__TURNKEY_SIGNER_ENVIRONMENT__"/><meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; base-uri 'self'; object-src 'none'; form-action 'none'"><link href="/styles.e084a69a94c0575bc6ba.css" rel="stylesheet" integrity="sha384-uIrxQTbBoDAwjgotQ+GUHgbxFM2iajB5QKNa4WuL9wn/Ou+2383e3dM2FCWOAq9m" crossorigin="anonymous"></head><body><h2>Export Key Material</h2><p><em>This public key will be sent along with a private key ID or wallet ID inside of a new <code>EXPORT_PRIVATE_KEY</code> or <code>EXPORT_WALLET</code> activity</em></p><form><label>Embedded key</label> <input name="embedded-key" id="embedded-key" disabled="disabled"/> <button id="reset">Reset Key</button></form><br/><br/><br/><h2>Inject Key Export Bundle</h2><p><em>The export bundle comes from the parent page and is composed of a public key and an encrypted payload. The payload is encrypted to this document's embedded key (stored in local storage and displayed above). The scheme relies on <a target="_blank" href="https://datatracker.ietf.org/doc/rfc9180/">HPKE (RFC 9180)</a></em>.</p><form><label>Bundle</label> <input name="key-export-bundle" id="key-export-bundle"/> <button id="inject-key">Inject Bundle</button><br/><label>Key Format</label> <select id="key-export-format" name="key-export-format"><option value="HEXADECIMAL">Hexadecimal (Default)</option><option value="SOLANA">Solana</option></select><br/><label>Organization Id</label> <input name="key-organization-id" id="key-organization-id"/></form><br/><br/><h2>Inject Wallet Export Bundle</h2><p><em>The export bundle comes from the parent page and is composed of a public key and an encrypted payload. The payload is encrypted to this document's embedded key (stored in local storage and displayed above). The scheme relies on <a target="_blank" href="https://datatracker.ietf.org/doc/rfc9180/">HPKE (RFC 9180)</a></em>.</p><form><label>Bundle</label> <input name="wallet-export-bundle" id="wallet-export-bundle"/> <button id="inject-wallet">Inject Bundle</button><br/><label>Organization Id</label> <input name="wallet-organization-id" id="wallet-organization-id"/></form><br/><br/><h2>Sign Transaction</h2><p><em>Input a serialized transaction to sign.</em></p><form><label>Transaction</label> <input name="transaction-to-sign" id="transaction-to-sign"/> <button id="sign-transaction">Sign</button></form><br/><br/><h2>Sign Message</h2><p><em>Input a serialized message to sign.</em></p><form><label>Message</label> <input name="message-to-sign" id="message-to-sign"/> <button id="sign-message">Sign</button></form><br/><br/><h2>Message log</h2><p><em>Below we display a log of the messages sent / received. The forms above send messages, and the code communicates results by sending events via the <code>postMessage</code> API.</em></p><div id="message-log"></div><div id="key-div"></div><script defer="defer" src="/bundle.921b01a774677f8e2da8.js" integrity="sha384-P/yUGeA+YjATjB94JS/FcpAKrqBRW/oFjpTPQJAEZMy2zDCV+2mfOqsTbuxZkCcy" crossorigin="anonymous"></script><script defer="defer" src="/bundle.3f29bf7d8e30fb1dd823.js" integrity="sha384-2FahO+4Ckv/SX/ymp4E9OL0qChHU6KW6jUewg//yvqWnWvCOMJVHD1HLeBZUAB0G" crossorigin="anonymous"></script></body></html>

export-and-sign/index.test.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,7 +1018,7 @@ describe("Encryption Escrow", () => {
10181018
keyFormat: "SOLANA",
10191019
});
10201020

1021-
TKHQ.removeEncryptedBundle("addr1");
1021+
TKHQ.removeEncryptedBundle("addr1", "org1");
10221022

10231023
const bundles = TKHQ.getEncryptedBundles();
10241024
expect(bundles["addr1"]).toBeUndefined();
@@ -1033,7 +1033,7 @@ describe("Encryption Escrow", () => {
10331033
keyFormat: "SOLANA",
10341034
});
10351035

1036-
TKHQ.removeEncryptedBundle("addr1");
1036+
TKHQ.removeEncryptedBundle("addr1", "org1");
10371037
expect(TKHQ.getEncryptedBundles()).toBeNull();
10381038
});
10391039

@@ -1051,7 +1051,7 @@ describe("Encryption Escrow", () => {
10511051
keyFormat: "SOLANA",
10521052
});
10531053

1054-
TKHQ.clearAllEncryptedBundles();
1054+
TKHQ.clearAllEncryptedBundles("org1");
10551055
expect(TKHQ.getEncryptedBundles()).toBeNull();
10561056
});
10571057
});
@@ -1414,7 +1414,7 @@ describe("Encryption Escrow", () => {
14141414
keyFormat: "SOLANA",
14151415
});
14161416

1417-
onGetStoredWalletAddresses(requestId);
1417+
onGetStoredWalletAddresses(requestId, "org1");
14181418

14191419
expect(sendMessageSpy).toHaveBeenCalledWith(
14201420
"STORED_WALLET_ADDRESSES",
@@ -1424,7 +1424,7 @@ describe("Encryption Escrow", () => {
14241424
});
14251425

14261426
it("returns empty array when no bundles stored", () => {
1427-
onGetStoredWalletAddresses(requestId);
1427+
onGetStoredWalletAddresses(requestId, "org1");
14281428

14291429
expect(sendMessageSpy).toHaveBeenCalledWith(
14301430
"STORED_WALLET_ADDRESSES",
@@ -1471,7 +1471,7 @@ describe("Encryption Escrow", () => {
14711471
);
14721472

14731473
// Clear addr1
1474-
onClearStoredBundles(requestId, "addr1");
1474+
onClearStoredBundles(requestId, "addr1", "org1");
14751475

14761476
expect(sendMessageSpy).toHaveBeenCalledWith(
14771477
"STORED_BUNDLES_CLEARED",
@@ -1528,7 +1528,7 @@ describe("Encryption Escrow", () => {
15281528
);
15291529

15301530
// Clear all
1531-
onClearStoredBundles(requestId, undefined);
1531+
onClearStoredBundles(requestId, undefined, "org1");
15321532

15331533
expect(sendMessageSpy).toHaveBeenCalledWith(
15341534
"STORED_BUNDLES_CLEARED",

export-and-sign/src/event-handlers.js

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,10 @@ async function onInjectDecryptionKeyBundle(
477477
const bundleData = storedBundles[addr];
478478

479479
try {
480+
const bundleOrgId = bundleData.organizationId;
481+
482+
if (organizationId !== bundleOrgId) continue; // skip bundles that don't match the organization ID of the decryption key
483+
480484
const encappedKeyBuf = TKHQ.uint8arrayFromHexString(
481485
bundleData.encappedPublic
482486
);
@@ -527,10 +531,15 @@ function onBurnSession(requestId) {
527531
* Handler for GET_STORED_WALLET_ADDRESSES events.
528532
* Returns a JSON array of all wallet addresses that have encrypted bundles in localStorage.
529533
* @param {string} requestId
534+
* @param {string} organizationId - Organization ID to filter addresses by (only return addresses with bundles matching the organization ID)
530535
*/
531-
function onGetStoredWalletAddresses(requestId) {
536+
function onGetStoredWalletAddresses(requestId, organizationId) {
532537
const bundles = TKHQ.getEncryptedBundles();
533-
const addresses = bundles ? Object.keys(bundles) : [];
538+
const addresses = bundles
539+
? Object.entries(bundles)
540+
.filter(([, bundle]) => bundle.organizationId === organizationId)
541+
.map(([address]) => address)
542+
: [];
534543
TKHQ.sendMessageUp(
535544
"STORED_WALLET_ADDRESSES",
536545
JSON.stringify(addresses),
@@ -544,14 +553,15 @@ function onGetStoredWalletAddresses(requestId) {
544553
* in-memory keys. If address is provided, removes only that address.
545554
* If no address, removes ALL encrypted bundles and ALL in-memory keys.
546555
* @param {string} requestId
556+
* @param {string} organizationId - Organization ID to filter addresses by (only remove bundles matching the organization ID)
547557
* @param {string|undefined} address - Optional wallet address
548558
*/
549-
function onClearStoredBundles(requestId, address) {
559+
function onClearStoredBundles(requestId, address, organizationId) {
550560
if (address) {
551-
TKHQ.removeEncryptedBundle(address);
561+
TKHQ.removeEncryptedBundle(address, organizationId);
552562
delete inMemoryKeys[address];
553563
} else {
554-
TKHQ.clearAllEncryptedBundles();
564+
TKHQ.clearAllEncryptedBundles(organizationId);
555565
inMemoryKeys = {};
556566
}
557567
TKHQ.sendMessageUp("STORED_BUNDLES_CLEARED", true, requestId);
@@ -873,7 +883,10 @@ function initMessageEventListener(HpkeDecrypt) {
873883
if (event.data && event.data["type"] == "GET_STORED_WALLET_ADDRESSES") {
874884
TKHQ.logMessage(`⬇️ Received message ${event.data["type"]}`);
875885
try {
876-
onGetStoredWalletAddresses(event.data["requestId"]);
886+
onGetStoredWalletAddresses(
887+
event.data["requestId"],
888+
event.data["organizationId"]
889+
);
877890
} catch (e) {
878891
TKHQ.sendMessageUp("ERROR", e.toString(), event.data["requestId"]);
879892
}
@@ -883,7 +896,11 @@ function initMessageEventListener(HpkeDecrypt) {
883896
`⬇️ Received message ${event.data["type"]}: address=${event.data["address"]}`
884897
);
885898
try {
886-
onClearStoredBundles(event.data["requestId"], event.data["address"]);
899+
onClearStoredBundles(
900+
event.data["requestId"],
901+
event.data["address"],
902+
event.data["organizationId"]
903+
);
887904
} catch (e) {
888905
TKHQ.sendMessageUp("ERROR", e.toString(), event.data["requestId"]);
889906
}

import/dist/551.bundle.27da59437a4e403e3c24.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

shared/turnkey-core.js

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,16 @@ function setSettings(settings) {
223223
*/
224224
function getEncryptedBundles() {
225225
const data = window.localStorage.getItem(TURNKEY_ENCRYPTED_BUNDLES);
226-
return data ? JSON.parse(data) : null;
226+
if (!data) {
227+
return null;
228+
}
229+
try {
230+
return JSON.parse(data);
231+
} catch (e) {
232+
// If the stored data is corrupted or not valid JSON, remove it to self-heal.
233+
window.localStorage.removeItem(TURNKEY_ENCRYPTED_BUNDLES);
234+
return null;
235+
}
227236
}
228237

229238
/**
@@ -242,28 +251,52 @@ function setEncryptedBundle(address, bundleData) {
242251

243252
/**
244253
* Removes a single encrypted bundle by address.
254+
* Only removes the bundle if it belongs to the specified organization.
245255
* @param {string} address - The wallet address to remove
256+
* @param {string} organizationId - Only remove if the bundle belongs to this org
246257
*/
247-
function removeEncryptedBundle(address) {
258+
function removeEncryptedBundle(address, organizationId) {
248259
const bundles = getEncryptedBundles();
249-
if (bundles && bundles[address]) {
250-
delete bundles[address];
251-
if (Object.keys(bundles).length === 0) {
252-
window.localStorage.removeItem(TURNKEY_ENCRYPTED_BUNDLES);
253-
} else {
254-
window.localStorage.setItem(
255-
TURNKEY_ENCRYPTED_BUNDLES,
256-
JSON.stringify(bundles)
257-
);
258-
}
260+
if (!bundles || !bundles[address]) return;
261+
262+
// Only remove if the bundle belongs to the specified organization
263+
if (bundles[address].organizationId !== organizationId) return;
264+
265+
delete bundles[address];
266+
if (Object.keys(bundles).length === 0) {
267+
window.localStorage.removeItem(TURNKEY_ENCRYPTED_BUNDLES);
268+
} else {
269+
window.localStorage.setItem(
270+
TURNKEY_ENCRYPTED_BUNDLES,
271+
JSON.stringify(bundles)
272+
);
259273
}
260274
}
261275

262276
/**
263-
* Removes all encrypted bundles from localStorage.
277+
* Removes all encrypted bundles belonging to the specified organization.
278+
* Bundles from other organizations are preserved.
279+
* @param {string} organizationId - Remove bundles belonging to this org
264280
*/
265-
function clearAllEncryptedBundles() {
266-
window.localStorage.removeItem(TURNKEY_ENCRYPTED_BUNDLES);
281+
function clearAllEncryptedBundles(organizationId) {
282+
const bundles = getEncryptedBundles();
283+
if (!bundles) return;
284+
285+
// Keep only bundles that do NOT belong to this organization
286+
const remaining = Object.fromEntries(
287+
Object.entries(bundles).filter(
288+
([, bundle]) => bundle.organizationId !== organizationId
289+
)
290+
);
291+
292+
if (Object.keys(remaining).length === 0) {
293+
window.localStorage.removeItem(TURNKEY_ENCRYPTED_BUNDLES);
294+
} else {
295+
window.localStorage.setItem(
296+
TURNKEY_ENCRYPTED_BUNDLES,
297+
JSON.stringify(remaining)
298+
);
299+
}
267300
}
268301

269302
/**

0 commit comments

Comments
 (0)