Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a918bf6bf |
@@ -45,42 +45,6 @@ but the review is broader than any of them.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
- 2026-09-21: The test recovery phrase no longer survives in a release bundle,
|
|
||||||
and the committed-key guard matches by content
|
|
||||||
([#351](https://git.eeqj.de/sneak/AutistMask/issues/351)). `DEBUG_MNEMONIC` in
|
|
||||||
`src/shared/constants.js` is now behind the `__BUILD_DEBUG__` define, so a
|
|
||||||
release build folds the phrase to `null` and no emitted bundle carries it; it
|
|
||||||
used to survive as dead text because `module.exports` keeps the const alive.
|
|
||||||
`script/verify-build` now fails a release build if the phrase appears in any
|
|
||||||
emitted file, so the fold cannot silently regress. `tests/extensionId.test.js`
|
|
||||||
scans the content of every tracked file for a PEM private-key header instead
|
|
||||||
of matching filename extensions alone.
|
|
||||||
- 2026-09-21: A transaction response is honoured only for a transaction
|
|
||||||
approval, and the three remaining approval-settlement paths are pinned
|
|
||||||
([#262](https://git.eeqj.de/sneak/AutistMask/issues/262)). The liveness fix
|
|
||||||
the issue asks for — settle `4001` on release when the window it would be
|
|
||||||
retried in is gone — already landed with
|
|
||||||
[#271](https://git.eeqj.de/sneak/AutistMask/issues/271); this closes the rest.
|
|
||||||
`AUTISTMASK_TX_RESPONSE` now refuses any approval that is not a transaction
|
|
||||||
approval, so a reject no longer retires a sign or connection approval and a
|
|
||||||
signed artifact never runs the broadcast path against one, which before only
|
|
||||||
failed closed by throwing deeper in. Tests pin the site-connection port's
|
|
||||||
approve, reject and disconnect paths against a transaction approval
|
|
||||||
broadcasting behind them: each is declined and the dApp still receives its
|
|
||||||
broadcast result.
|
|
||||||
|
|
||||||
- 2026-09-21: Adding a second wallet no longer accepts a different password with
|
|
||||||
nothing saying it is a separate one
|
|
||||||
([#374](https://git.eeqj.de/sneak/AutistMask/issues/374)). Each wallet has its
|
|
||||||
own encrypted secret, so per-wallet passwords are by design; the add-wallet
|
|
||||||
screen said only "Choose a password". A note now appears on that screen when
|
|
||||||
the profile already holds a wallet, stating that each wallet has its own
|
|
||||||
password and this one need not match any already in use. It is shown only
|
|
||||||
then, since the first wallet has no other password to differ from, and it
|
|
||||||
stays consistent with the no-reset reality of
|
|
||||||
[#312](https://git.eeqj.de/sneak/AutistMask/issues/312) by promising no
|
|
||||||
recovery or reset.
|
|
||||||
|
|
||||||
- 2026-09-21: The dApp approval and transaction-status screens resolve a token's
|
- 2026-09-21: The dApp approval and transaction-status screens resolve a token's
|
||||||
symbol from the bundled list, then the tokens the user tracks, then the block
|
symbol from the bundled list, then the tokens the user tracks, then the block
|
||||||
explorer's report — the same sources and precedence the amount line already
|
explorer's report — the same sources and precedence the amount line already
|
||||||
|
|||||||
@@ -37,10 +37,6 @@ DISCARD_DIST="$ROOT/script/discard-dist-on-failure"
|
|||||||
MARKER_ON="autistmask-build-debug=on"
|
MARKER_ON="autistmask-build-debug=on"
|
||||||
MARKER_OFF="autistmask-build-debug=off"
|
MARKER_OFF="autistmask-build-debug=off"
|
||||||
|
|
||||||
# The same test recovery phrase verify-build searches release bundles for. Held
|
|
||||||
# here too, the way the markers above are, so a case can plant it in a bundle.
|
|
||||||
TEST_MNEMONIC="cube evolve unfold result inch risk jealous skill hotel bulb night wreck"
|
|
||||||
|
|
||||||
RECEIPT_HEADER="autistmask-build-receipt v1"
|
RECEIPT_HEADER="autistmask-build-receipt v1"
|
||||||
|
|
||||||
NEWLINE='
|
NEWLINE='
|
||||||
@@ -520,24 +516,6 @@ c_debug_build() {
|
|||||||
write_receipt
|
write_receipt
|
||||||
}
|
}
|
||||||
|
|
||||||
# A release bundle that still carries the test recovery phrase — the regression
|
|
||||||
# verify-build guards against, and the reason DEBUG_MNEMONIC is behind the
|
|
||||||
# __BUILD_DEBUG__ define in src/shared/constants.js. The receipt is regenerated
|
|
||||||
# so the phrase is caught as bundle content, not incidentally as a stale digest.
|
|
||||||
c_release_bundle_with_mnemonic() {
|
|
||||||
printf '/* %s */\n' "$TEST_MNEMONIC" >>dist/chrome/src/popup/index.js
|
|
||||||
write_receipt
|
|
||||||
}
|
|
||||||
|
|
||||||
# The same phrase in a debug build is expected: make build-debug ships it on
|
|
||||||
# purpose, so the phrase check must stay quiet under --expect debug.
|
|
||||||
c_debug_bundle_with_mnemonic() {
|
|
||||||
write_bundle dist/chrome/src/popup/index.js "$MARKER_ON"
|
|
||||||
write_bundle dist/firefox/src/popup/index.js "$MARKER_ON"
|
|
||||||
printf '/* %s */\n' "$TEST_MNEMONIC" >>dist/chrome/src/popup/index.js
|
|
||||||
write_receipt
|
|
||||||
}
|
|
||||||
|
|
||||||
c_no_dist() { rm -rf dist; }
|
c_no_dist() { rm -rf dist; }
|
||||||
|
|
||||||
# --- dist discard -----------------------------------------------------------
|
# --- dist discard -----------------------------------------------------------
|
||||||
@@ -775,13 +753,6 @@ run_cases() {
|
|||||||
check_case "debug bundles under --expect debug pass" \
|
check_case "debug bundles under --expect debug pass" \
|
||||||
no debug 0 "2 bundle(s) $MARKER_ON" c_debug_build
|
no debug 0 "2 bundle(s) $MARKER_ON" c_debug_build
|
||||||
|
|
||||||
check_case "release bundle carrying the test recovery phrase fails" \
|
|
||||||
no release 1 \
|
|
||||||
"carries the BIP-39 test recovery phrase" c_release_bundle_with_mnemonic
|
|
||||||
|
|
||||||
check_case "debug bundle carrying the test recovery phrase passes" \
|
|
||||||
no debug 0 "2 bundle(s) $MARKER_ON" c_debug_bundle_with_mnemonic
|
|
||||||
|
|
||||||
check_case "no --expect argument" \
|
check_case "no --expect argument" \
|
||||||
no no-expect 1 "no --expect argument." c_control
|
no no-expect 1 "no --expect argument." c_control
|
||||||
|
|
||||||
|
|||||||
@@ -13,12 +13,6 @@
|
|||||||
# fallback branch; the property only exists in the emitted output, so it has to
|
# fallback branch; the property only exists in the emitted output, so it has to
|
||||||
# be asserted against the emitted output.
|
# be asserted against the emitted output.
|
||||||
#
|
#
|
||||||
# The DEBUG half also checks the phrase directly: a release build must not carry
|
|
||||||
# the test recovery phrase in any emitted file. The phrase is behind the
|
|
||||||
# __BUILD_DEBUG__ define in src/shared/constants.js and folds away in a release
|
|
||||||
# build, but the marker only proves DEBUG compiled off, not that the fold
|
|
||||||
# removed the string; the phrase grep is the assertion that it did.
|
|
||||||
#
|
|
||||||
# Which mode to expect is an ARGUMENT (--expect release|debug) and is never
|
# Which mode to expect is an ARGUMENT (--expect release|debug) and is never
|
||||||
# taken from this script's environment. It used to be read from
|
# taken from this script's environment. It used to be read from
|
||||||
# AUTISTMASK_DEBUG here, which meant an operator with AUTISTMASK_DEBUG=1
|
# AUTISTMASK_DEBUG here, which meant an operator with AUTISTMASK_DEBUG=1
|
||||||
@@ -73,15 +67,6 @@ TAB=' '
|
|||||||
MARKER_ON="autistmask-build-debug=on"
|
MARKER_ON="autistmask-build-debug=on"
|
||||||
MARKER_OFF="autistmask-build-debug=off"
|
MARKER_OFF="autistmask-build-debug=off"
|
||||||
|
|
||||||
# The 12-word BIP-39 test recovery phrase from src/shared/constants.js. It is
|
|
||||||
# behind the __BUILD_DEBUG__ define there, so a release build folds it out of
|
|
||||||
# every bundle; this is the assertion that it stayed out. The phrase is a
|
|
||||||
# publicly committed test value rather than a secret, but a BIP-39 phrase in a
|
|
||||||
# distributed wallet artifact is exactly the string a scanner or auditor has to
|
|
||||||
# stop and reason about, so a release build must not ship it. A debug build
|
|
||||||
# ships it on purpose, so this is checked only when release is expected.
|
|
||||||
TEST_MNEMONIC="cube evolve unfold result inch risk jealous skill hotel bulb night wreck"
|
|
||||||
|
|
||||||
RECEIPT_HEADER="autistmask-build-receipt v1"
|
RECEIPT_HEADER="autistmask-build-receipt v1"
|
||||||
|
|
||||||
# Set by the arguments.
|
# Set by the arguments.
|
||||||
@@ -298,18 +283,6 @@ check_entry() {
|
|||||||
receipt records $ENTRY_HASH and the file on disk is $SHA. Something wrote
|
receipt records $ENTRY_HASH and the file on disk is $SHA. Something wrote
|
||||||
to dist/ after the build, so this artifact is not the one that was built."
|
to dist/ after the build, so this artifact is not the one that was built."
|
||||||
|
|
||||||
# No emitted file of a release build may carry the test recovery phrase.
|
|
||||||
# Checked on every file, not only the audited bundles, so a copy that
|
|
||||||
# reached some other emitted file fails here too. A debug build ships the
|
|
||||||
# phrase deliberately, so this runs only when release was expected.
|
|
||||||
if [ "$EXPECT" = "$MARKER_OFF" ] && has_marker "$TEST_MNEMONIC" "$ENTRY_PATH"; then
|
|
||||||
fail "$ENTRY_PATH carries the BIP-39 test recovery phrase, which a
|
|
||||||
release build must fold out. The __BUILD_DEBUG__ define in build.js is what
|
|
||||||
drops it from src/shared/constants.js; check that DEBUG_MNEMONIC is still
|
|
||||||
behind that flag. A recovery phrase in a distributed bundle is exactly the
|
|
||||||
string an auditor or scanner has to stop on, so this is a hard failure."
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ "$ENTRY_FLAG" = A ]; then
|
if [ "$ENTRY_FLAG" = A ]; then
|
||||||
read_marker "$ENTRY_PATH"
|
read_marker "$ENTRY_PATH"
|
||||||
[ "$MARKER" = "$EXPECT" ] ||
|
[ "$MARKER" = "$EXPECT" ] ||
|
||||||
|
|||||||
@@ -1344,15 +1344,6 @@ runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
|||||||
const approval = pendingApprovals[msg.id];
|
const approval = pendingApprovals[msg.id];
|
||||||
if (!approval) return false;
|
if (!approval) return false;
|
||||||
|
|
||||||
// This message signs and broadcasts a transaction, so it is honoured
|
|
||||||
// only for a transaction approval. A sign or connection approval
|
|
||||||
// carries no approvedTx, and reaching the broadcast path with one used
|
|
||||||
// to fail closed by throwing deeper in; refusing here keeps a future
|
|
||||||
// refactor from turning that incidental throw into a live path, and
|
|
||||||
// keeps a reject on this message from retiring an approval of another
|
|
||||||
// kind.
|
|
||||||
if (approval.type !== "tx") return false;
|
|
||||||
|
|
||||||
// A reject arriving while an attempt holds the approval is refused,
|
// A reject arriving while an attempt holds the approval is refused,
|
||||||
// not honoured: the attempt is on its way to broadcasting the
|
// not honoured: the attempt is on its way to broadcasting the
|
||||||
// transaction, and resolving 4001 here would tell the page the request
|
// transaction, and resolving 4001 here would tell the page the request
|
||||||
|
|||||||
@@ -152,21 +152,6 @@
|
|||||||
|
|
||||||
<!-- Shared password fields -->
|
<!-- Shared password fields -->
|
||||||
<div class="mb-2" id="add-wallet-password-section">
|
<div class="mb-2" id="add-wallet-password-section">
|
||||||
<!-- Shown only when the profile already holds a wallet:
|
|
||||||
each wallet has its own password (its own
|
|
||||||
encryptedSecret), so a second wallet does not reuse
|
|
||||||
the first one's. addWallet.js toggles this on screen
|
|
||||||
entry from state.wallets.length, so it is constant
|
|
||||||
while the screen is up and moves nothing. -->
|
|
||||||
<p
|
|
||||||
class="text-xs mb-2 border border-border border-dashed p-2 hidden"
|
|
||||||
id="add-wallet-separate-password-note"
|
|
||||||
>
|
|
||||||
You already have a wallet. Each wallet has its own
|
|
||||||
password: the one you choose here is only for this new
|
|
||||||
wallet, and it need not match any password you already
|
|
||||||
use.
|
|
||||||
</p>
|
|
||||||
<label class="block mb-1">Choose a password</label>
|
<label class="block mb-1">Choose a password</label>
|
||||||
<!-- The hint is swapped in place when the import tab
|
<!-- The hint is swapped in place when the import tab
|
||||||
changes, and it sits directly above the password
|
changes, and it sits directly above the password
|
||||||
|
|||||||
@@ -100,24 +100,9 @@ function clear() {
|
|||||||
$("add-wallet-phrase-warning").style.visibility = "hidden";
|
$("add-wallet-phrase-warning").style.visibility = "hidden";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Each wallet has its own password (its own encryptedSecret), so adding a
|
|
||||||
// second wallet does not reuse the first one's. The note that says so is
|
|
||||||
// only meaningful once a wallet exists — on the first wallet there is no
|
|
||||||
// other password to be separate from — so it is shown only then. This is
|
|
||||||
// decided on entry and stays put while the screen is up, so it does not
|
|
||||||
// move the password fields the way a per-tab hint would.
|
|
||||||
function updateSeparatePasswordNote() {
|
|
||||||
const hasExistingWallet = state.wallets.length > 0;
|
|
||||||
$("add-wallet-separate-password-note").classList.toggle(
|
|
||||||
"hidden",
|
|
||||||
!hasExistingWallet,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
clear();
|
clear();
|
||||||
switchMode("mnemonic");
|
switchMode("mnemonic");
|
||||||
updateSeparatePasswordNote();
|
|
||||||
showView("add-wallet");
|
showView("add-wallet");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,15 +22,8 @@ const BUILD_DEBUG_MARKER = DEBUG
|
|||||||
? "autistmask-build-debug=on"
|
? "autistmask-build-debug=on"
|
||||||
: "autistmask-build-debug=off";
|
: "autistmask-build-debug=off";
|
||||||
|
|
||||||
// Behind DEBUG for the same reason BUILD_DEBUG_MARKER is above: in a release
|
const DEBUG_MNEMONIC =
|
||||||
// build __BUILD_DEBUG__ is a compile-time false, esbuild drops this branch, and
|
"cube evolve unfold result inch risk jealous skill hotel bulb night wreck";
|
||||||
// the phrase never reaches a distributed bundle. The literal used to survive as
|
|
||||||
// dead text because module.exports keeps this const live even though wallet.js's
|
|
||||||
// only use of it is folded away; making the value itself fold to null removes
|
|
||||||
// it. script/verify-build fails a release build if the phrase appears anyway.
|
|
||||||
const DEBUG_MNEMONIC = DEBUG
|
|
||||||
? "cube evolve unfold result inch risk jealous skill hotel bulb night wreck"
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const ETHEREUM_MAINNET_CHAIN_ID = "0x1";
|
const ETHEREUM_MAINNET_CHAIN_ID = "0x1";
|
||||||
const ETHEREUM_SEPOLIA_CHAIN_ID = "0xaa36a7";
|
const ETHEREUM_SEPOLIA_CHAIN_ID = "0xaa36a7";
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
// Adding a second wallet accepts a password different from the first one's
|
|
||||||
// with nothing on screen saying the two are separate — each wallet has its
|
|
||||||
// own encryptedSecret, so per-wallet passwords are by design, but the add
|
|
||||||
// screen said only "Choose a password"
|
|
||||||
// (https://git.eeqj.de/sneak/AutistMask/issues/374).
|
|
||||||
//
|
|
||||||
// The fix is copy: a note on the password screen that says each wallet has
|
|
||||||
// its own password and this one need not match. It is only meaningful once
|
|
||||||
// a wallet exists — on the very first wallet there is no other password to
|
|
||||||
// be separate from — so it is shown then and hidden otherwise. These boot
|
|
||||||
// the real popup and reach the add-wallet screen through the same button a
|
|
||||||
// user presses, so the note's visibility is decided by the real show().
|
|
||||||
|
|
||||||
const {
|
|
||||||
bootPopup,
|
|
||||||
cleanupPopup,
|
|
||||||
unversionedValidProfile,
|
|
||||||
POPUP_HTML,
|
|
||||||
} = require("./support/popupBoot");
|
|
||||||
|
|
||||||
const NOTE = "add-wallet-separate-password-note";
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
cleanupPopup();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("second-wallet password note", () => {
|
|
||||||
test("hidden while onboarding the first wallet", async () => {
|
|
||||||
const page = await bootPopup(undefined);
|
|
||||||
expect(page.pageErrors).toEqual([]);
|
|
||||||
await page.click("btn-welcome-add");
|
|
||||||
expect(page.visibleViews()).toContain("add-wallet");
|
|
||||||
expect(page.hidden(NOTE)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("shown when a wallet already exists", async () => {
|
|
||||||
const page = await bootPopup(unversionedValidProfile());
|
|
||||||
expect(page.pageErrors).toEqual([]);
|
|
||||||
await page.click("btn-main-add-wallet");
|
|
||||||
expect(page.visibleViews()).toContain("add-wallet");
|
|
||||||
expect(page.hidden(NOTE)).toBe(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
// The copy states the two facts the definition of done asks for — each
|
|
||||||
// wallet has its own password, and this one need not match — and stays
|
|
||||||
// consistent with the no-password-reset reality of
|
|
||||||
// https://git.eeqj.de/sneak/AutistMask/issues/312 by not promising any
|
|
||||||
// recovery or reset here.
|
|
||||||
test("the note says the password is per-wallet and need not match", () => {
|
|
||||||
const note = /id="add-wallet-separate-password-note"[^>]*>([^]*?)<\/p>/
|
|
||||||
.exec(POPUP_HTML)[1]
|
|
||||||
.replace(/\s+/g, " ")
|
|
||||||
.trim();
|
|
||||||
expect(note).toContain("its own");
|
|
||||||
expect(note).toContain("need not match");
|
|
||||||
expect(note).not.toMatch(/recover|reset/i);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1541,149 +1541,6 @@ describe("a claimed approval outlives every other retirement path", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// The approval popup connects a port named for its approval whatever the
|
|
||||||
// approval's kind, so a decision or a disconnect on that port can reach a
|
|
||||||
// transaction approval. Both must be declined: the port decides only
|
|
||||||
// site-connection approvals, and settling a transaction approval it does not
|
|
||||||
// own — while an attempt is broadcasting behind it — is the round-3 fund-loss
|
|
||||||
// bug, where the page is told the request was rejected as the transaction goes
|
|
||||||
// out. These three paths route through settleApproval() and, before this
|
|
||||||
// suite, were exercised only against site approvals.
|
|
||||||
describe("the site-connection port never retires a transaction approval", () => {
|
|
||||||
async function txMidBroadcast() {
|
|
||||||
const bg = loadBackground();
|
|
||||||
const pending = bg.requestTx();
|
|
||||||
await settle();
|
|
||||||
const id = pending.id();
|
|
||||||
|
|
||||||
const inFlight = deferred();
|
|
||||||
bg.broadcastTransaction.mockReturnValue(inFlight.promise);
|
|
||||||
const first = bg.send(
|
|
||||||
{
|
|
||||||
type: "AUTISTMASK_TX_RESPONSE",
|
|
||||||
id,
|
|
||||||
approved: true,
|
|
||||||
rawSignedTx: await signedAtNonce(7),
|
|
||||||
},
|
|
||||||
{ url: bg.fromPopup.url },
|
|
||||||
);
|
|
||||||
await settle();
|
|
||||||
expect(bg.broadcastTransaction).toHaveBeenCalledTimes(1);
|
|
||||||
return { bg, pending, id, inFlight, first };
|
|
||||||
}
|
|
||||||
|
|
||||||
test("an approve on the port does not settle it", async () => {
|
|
||||||
const { bg, pending, id, inFlight, first } = await txMidBroadcast();
|
|
||||||
|
|
||||||
bg.connectApproval(id).decide(true, false);
|
|
||||||
await settle();
|
|
||||||
expect(pending.result()).toBeNull();
|
|
||||||
|
|
||||||
inFlight.resolve({ hash: "0xfeed" });
|
|
||||||
await settle();
|
|
||||||
expect(pending.result()).toEqual({ result: "0xfeed" });
|
|
||||||
expect(first.sendResponse).toHaveBeenCalledWith({ txHash: "0xfeed" });
|
|
||||||
});
|
|
||||||
|
|
||||||
test("a reject on the port does not settle it", async () => {
|
|
||||||
const { bg, pending, id, inFlight, first } = await txMidBroadcast();
|
|
||||||
|
|
||||||
bg.connectApproval(id).decide(false, false);
|
|
||||||
await settle();
|
|
||||||
expect(pending.result()).toBeNull();
|
|
||||||
|
|
||||||
inFlight.resolve({ hash: "0xfeed" });
|
|
||||||
await settle();
|
|
||||||
expect(pending.result()).toEqual({ result: "0xfeed" });
|
|
||||||
expect(first.sendResponse).toHaveBeenCalledWith({ txHash: "0xfeed" });
|
|
||||||
});
|
|
||||||
|
|
||||||
test("a port disconnect does not settle it", async () => {
|
|
||||||
const { bg, pending, id, inFlight, first } = await txMidBroadcast();
|
|
||||||
|
|
||||||
bg.connectApproval(id).disconnect();
|
|
||||||
await settle();
|
|
||||||
expect(pending.result()).toBeNull();
|
|
||||||
|
|
||||||
inFlight.resolve({ hash: "0xfeed" });
|
|
||||||
await settle();
|
|
||||||
expect(pending.result()).toEqual({ result: "0xfeed" });
|
|
||||||
expect(first.sendResponse).toHaveBeenCalledWith({ txHash: "0xfeed" });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// AUTISTMASK_TX_RESPONSE signs and broadcasts a transaction, so it is honoured
|
|
||||||
// only for a transaction approval. A reject shaped as this message used to
|
|
||||||
// retire a sign or connection approval outright, and an approve carrying a
|
|
||||||
// signed artifact used to run the broadcast path against an approval that names
|
|
||||||
// no transaction, failing closed only by throwing deeper in.
|
|
||||||
describe("a transaction response is honoured only for a transaction approval", () => {
|
|
||||||
test("a reject does not retire a sign approval", async () => {
|
|
||||||
const bg = loadBackground();
|
|
||||||
const pending = bg.requestSign();
|
|
||||||
await settle();
|
|
||||||
const id = pending.id();
|
|
||||||
|
|
||||||
bg.send(
|
|
||||||
{ type: "AUTISTMASK_TX_RESPONSE", id, approved: false },
|
|
||||||
{ url: bg.fromPopup.url },
|
|
||||||
);
|
|
||||||
await settle();
|
|
||||||
expect(pending.result()).toBeNull();
|
|
||||||
|
|
||||||
// Still live: its own reject settles it.
|
|
||||||
bg.send(
|
|
||||||
{ type: "AUTISTMASK_SIGN_RESPONSE", id, approved: false },
|
|
||||||
{ url: bg.fromPopup.url },
|
|
||||||
);
|
|
||||||
await settle();
|
|
||||||
expect(pending.result()).toEqual({
|
|
||||||
error: { code: 4001, message: "User rejected the request." },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test("a reject does not retire a connection approval", async () => {
|
|
||||||
const bg = loadBackground({ actionPopup: true });
|
|
||||||
const pending = bg.requestSite();
|
|
||||||
await settle();
|
|
||||||
const id = pending.id();
|
|
||||||
|
|
||||||
bg.send(
|
|
||||||
{ type: "AUTISTMASK_TX_RESPONSE", id, approved: false },
|
|
||||||
{ url: bg.fromPopup.url },
|
|
||||||
);
|
|
||||||
await settle();
|
|
||||||
expect(pending.result()).toBeNull();
|
|
||||||
|
|
||||||
// Still live: the port that owns it connects the site.
|
|
||||||
const port = bg.connectApproval(id);
|
|
||||||
port.decide(true, false);
|
|
||||||
port.disconnect();
|
|
||||||
await settle();
|
|
||||||
expect(pending.result()).toEqual({ result: [signer.address] });
|
|
||||||
});
|
|
||||||
|
|
||||||
test("an approve carrying a signed transaction never broadcasts against a sign approval", async () => {
|
|
||||||
const bg = loadBackground();
|
|
||||||
const pending = bg.requestSign();
|
|
||||||
await settle();
|
|
||||||
const id = pending.id();
|
|
||||||
|
|
||||||
bg.send(
|
|
||||||
{
|
|
||||||
type: "AUTISTMASK_TX_RESPONSE",
|
|
||||||
id,
|
|
||||||
approved: true,
|
|
||||||
rawSignedTx: await signedAtNonce(7),
|
|
||||||
},
|
|
||||||
{ url: bg.fromPopup.url },
|
|
||||||
);
|
|
||||||
await settle();
|
|
||||||
expect(bg.broadcastTransaction).not.toHaveBeenCalled();
|
|
||||||
expect(pending.result()).toBeNull();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// A handler that throws must still answer. `sendResponse` is the only thing
|
// A handler that throws must still answer. `sendResponse` is the only thing
|
||||||
// that settles the page's window.ethereum.request() promise, so a throw that
|
// that settles the page's window.ethereum.request() promise, so a throw that
|
||||||
// escapes a handler leaves that promise pending forever — no error, no
|
// escapes a handler leaves that promise pending forever — no error, no
|
||||||
|
|||||||
@@ -89,28 +89,19 @@ describe("chrome extension identity", () => {
|
|||||||
|
|
||||||
// The private half is a credential. It has never been in this repo and no
|
// The private half is a credential. It has never been in this repo and no
|
||||||
// target generates one into the working tree; this fails loudly if that
|
// target generates one into the working tree; this fails loudly if that
|
||||||
// ever changes, because a committed private key is one anyone can sign a
|
// ever changes, because a committed .pem is a key anyone can sign a CRX
|
||||||
// CRX with under this extension's id.
|
// with under this extension's id.
|
||||||
//
|
|
||||||
// Matched by CONTENT, not by filename: a key committed as notes.txt or with
|
|
||||||
// no extension carries the same risk as one named key.pem, and a
|
|
||||||
// filename-only check waves it through. The PEM header a private key opens
|
|
||||||
// with is the signature searched for. The pattern does not trip on its own
|
|
||||||
// source: the bracket-expression characters between the two anchors are not
|
|
||||||
// in the character class, so this file is not a match for it.
|
|
||||||
const PRIVATE_KEY_HEADER = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/;
|
|
||||||
test("no private key is committed anywhere in the tree", () => {
|
test("no private key is committed anywhere in the tree", () => {
|
||||||
const root = path.join(__dirname, "..");
|
|
||||||
const tracked = require("child_process")
|
const tracked = require("child_process")
|
||||||
.execSync("git ls-files", { cwd: root, encoding: "utf8" })
|
.execSync("git ls-files", {
|
||||||
|
cwd: path.join(__dirname, ".."),
|
||||||
|
encoding: "utf8",
|
||||||
|
})
|
||||||
.split("\n")
|
.split("\n")
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
const offenders = tracked.filter((f) =>
|
expect(tracked.filter((f) => /\.(pem|key|p12|pfx)$/i.test(f))).toEqual(
|
||||||
PRIVATE_KEY_HEADER.test(
|
[],
|
||||||
fs.readFileSync(path.join(root, f), "latin1"),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
expect(offenders).toEqual([]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -27,14 +27,6 @@ describe("generateMnemonic in a release build", () => {
|
|||||||
expect(constants.DEBUG).toBe(false);
|
expect(constants.DEBUG).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("the test phrase folds away when DEBUG is false", () => {
|
|
||||||
// The release bundle is what must not carry the phrase; here, with the
|
|
||||||
// define absent, DEBUG_MNEMONIC is the null branch the bundler keeps,
|
|
||||||
// and the literal only exists in the branch it drops.
|
|
||||||
const { constants } = loadWallet();
|
|
||||||
expect(constants.DEBUG_MNEMONIC).toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns fresh, valid 12-word phrases that are not the test phrase", () => {
|
test("returns fresh, valid 12-word phrases that are not the test phrase", () => {
|
||||||
const { constants, wallet } = loadWallet();
|
const { constants, wallet } = loadWallet();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user