Compare commits
1
Commits
next
...
b261bafb03
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b261bafb03 |
@@ -2168,6 +2168,16 @@ the log level and turns the banner on, and that is all it may ever do: it feeds
|
|||||||
constant directly, so no runtime toggle in a release build can reach the
|
constant directly, so no runtime toggle in a release build can reach the
|
||||||
hardcoded test phrase.
|
hardcoded test phrase.
|
||||||
|
|
||||||
|
At the raised log level the console also shows the wallet's addresses with their
|
||||||
|
balances and ENS names, the token contracts looked up, and a line for each
|
||||||
|
request made through `debugFetch` in `src/shared/log.js` (the explorer, the
|
||||||
|
price feed, the RPC calls a site makes, and the endpoint checks in settings) and
|
||||||
|
for its response. A request is logged by its HTTP method, the origin of its URL
|
||||||
|
(scheme, host and port) and, for a JSON-RPC call, the method name; the balance
|
||||||
|
refresh and token lookup name the RPC endpoint by its origin too. The URL's path
|
||||||
|
and query string, where RPC providers put API keys, and the request body are
|
||||||
|
never logged.
|
||||||
|
|
||||||
### Key Decisions
|
### Key Decisions
|
||||||
|
|
||||||
- **No framework**: The popup UI is vanilla JS and HTML. The extension is small
|
- **No framework**: The popup UI is vanilla JS and HTML. The extension is small
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ but the review is broader than any of them.
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-10-04: Debug mode no longer writes RPC API keys to the console
|
||||||
|
([#410](https://git.eeqj.de/sneak/AutistMask/issues/410)). `debugFetch` logged
|
||||||
|
every request's full URL and body, so an RPC endpoint with a key in its path
|
||||||
|
or query string printed that key on every request. It now logs the HTTP
|
||||||
|
method, the URL's origin and, for a JSON-RPC call, the method name. The
|
||||||
|
balance refresh and token lookup log the RPC endpoint by its origin too. The
|
||||||
|
README's DEBUG Mode Policy says what debug mode logs.
|
||||||
|
|
||||||
- 2026-10-04: A site has at most one connection prompt and one signature prompt
|
- 2026-10-04: A site has at most one connection prompt and one signature prompt
|
||||||
open at a time ([#405](https://git.eeqj.de/sneak/AutistMask/issues/405)). Each
|
open at a time ([#405](https://git.eeqj.de/sneak/AutistMask/issues/405)). Each
|
||||||
`eth_requestAccounts` or `personal_sign` call opened another approval window,
|
`eth_requestAccounts` or `personal_sign` call opened another approval window,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const {
|
|||||||
} = require("ethers");
|
} = require("ethers");
|
||||||
const { ERC20_ABI } = require("./constants");
|
const { ERC20_ABI } = require("./constants");
|
||||||
const { NETWORKS } = require("./networks");
|
const { NETWORKS } = require("./networks");
|
||||||
const { log, debugFetch } = require("./log");
|
const { log, debugFetch, urlOrigin } = require("./log");
|
||||||
const { deriveAddressFromXpub } = require("./wallet");
|
const { deriveAddressFromXpub } = require("./wallet");
|
||||||
const { TOKEN_BY_ADDRESS } = require("./tokenList");
|
const { TOKEN_BY_ADDRESS } = require("./tokenList");
|
||||||
const { LOW_HOLDER_THRESHOLD, parseHoldersCount } = require("./holders");
|
const { LOW_HOLDER_THRESHOLD, parseHoldersCount } = require("./holders");
|
||||||
@@ -203,7 +203,7 @@ async function refreshBalances(
|
|||||||
trackedTokens,
|
trackedTokens,
|
||||||
networkId,
|
networkId,
|
||||||
) {
|
) {
|
||||||
log.debugf("refreshBalances start, rpc:", rpcUrl);
|
log.debugf("refreshBalances start, rpc:", urlOrigin(rpcUrl));
|
||||||
const provider = getProvider(rpcUrl, networkId);
|
const provider = getProvider(rpcUrl, networkId);
|
||||||
const updates = [];
|
const updates = [];
|
||||||
|
|
||||||
@@ -280,7 +280,7 @@ async function refreshBalances(
|
|||||||
// Look up token metadata from its contract.
|
// Look up token metadata from its contract.
|
||||||
// Calls symbol() and decimals() to verify it implements ERC-20.
|
// Calls symbol() and decimals() to verify it implements ERC-20.
|
||||||
async function lookupTokenInfo(contractAddress, rpcUrl, networkId) {
|
async function lookupTokenInfo(contractAddress, rpcUrl, networkId) {
|
||||||
log.debugf("lookupTokenInfo", contractAddress, "rpc:", rpcUrl);
|
log.debugf("lookupTokenInfo", contractAddress, "rpc:", urlOrigin(rpcUrl));
|
||||||
const provider = getProvider(rpcUrl, networkId);
|
const provider = getProvider(rpcUrl, networkId);
|
||||||
const contract = new Contract(contractAddress, ERC20_ABI, provider);
|
const contract = new Contract(contractAddress, ERC20_ABI, provider);
|
||||||
|
|
||||||
|
|||||||
+24
-5
@@ -42,14 +42,33 @@ const log = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Fetch wrapper that debug-logs every request and response.
|
// The origin (scheme, host and port) of a URL, for logging in place of the
|
||||||
|
// URL: RPC providers put API keys in the path or the query string. A URL that
|
||||||
|
// does not parse gives "", so logging never stops a request.
|
||||||
|
function urlOrigin(url) {
|
||||||
|
try {
|
||||||
|
return new URL(url).origin;
|
||||||
|
} catch {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch wrapper that debug-logs every request and response. It logs the
|
||||||
|
// URL's origin and, for a JSON-RPC body, the method name: never the full URL
|
||||||
|
// or body, which can carry an API key or a signed transaction.
|
||||||
async function debugFetch(url, opts) {
|
async function debugFetch(url, opts) {
|
||||||
const method = (opts && opts.method) || "GET";
|
const method = (opts && opts.method) || "GET";
|
||||||
const body = opts && opts.body;
|
const origin = urlOrigin(url);
|
||||||
log.debugf("fetch →", method, url, body || "");
|
let rpcMethod = "";
|
||||||
|
try {
|
||||||
|
rpcMethod = JSON.parse(opts.body).method || "";
|
||||||
|
} catch {
|
||||||
|
// no body, or a body that is not JSON
|
||||||
|
}
|
||||||
|
log.debugf("fetch →", method, origin, rpcMethod);
|
||||||
const resp = await fetch(url, opts);
|
const resp = await fetch(url, opts);
|
||||||
log.debugf("fetch ←", resp.status, url);
|
log.debugf("fetch ←", resp.status, origin);
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { log, debugFetch, setRuntimeDebug, isDebug };
|
module.exports = { log, debugFetch, urlOrigin, setRuntimeDebug, isDebug };
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
// What debugFetch writes to the console in debug mode.
|
||||||
|
//
|
||||||
|
// RPC providers put the API key in the URL's path or query string, and the
|
||||||
|
// debug log used to print the whole URL and request body, so turning debug
|
||||||
|
// mode on wrote the key to the console
|
||||||
|
// (https://git.eeqj.de/sneak/AutistMask/issues/410). The log now names the
|
||||||
|
// HTTP method, the URL's origin and the JSON-RPC method, and nothing else of
|
||||||
|
// the request.
|
||||||
|
|
||||||
|
const { debugFetch, setRuntimeDebug } = require("../src/shared/log");
|
||||||
|
|
||||||
|
const realFetch = globalThis.fetch;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
globalThis.fetch = realFetch;
|
||||||
|
setRuntimeDebug(false);
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("logs the origin and JSON-RPC method, not the key in the URL", async () => {
|
||||||
|
setRuntimeDebug(true);
|
||||||
|
const consoleLog = jest.spyOn(console, "log").mockImplementation(() => {});
|
||||||
|
globalThis.fetch = jest.fn(async () => ({ status: 200 }));
|
||||||
|
|
||||||
|
await debugFetch(
|
||||||
|
"https://rpc.example.invalid/v3/PATHKEY123?token=QUERYTOKEN456",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
id: 1,
|
||||||
|
method: "eth_chainId",
|
||||||
|
params: [],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const logged = consoleLog.mock.calls.flat().join(" ");
|
||||||
|
expect(logged).not.toContain("PATHKEY123");
|
||||||
|
expect(logged).not.toContain("QUERYTOKEN456");
|
||||||
|
expect(logged).toContain("https://rpc.example.invalid");
|
||||||
|
expect(logged).toContain("eth_chainId");
|
||||||
|
});
|
||||||
@@ -66,6 +66,7 @@ jest.mock("../src/shared/log", () => ({
|
|||||||
status: 200,
|
status: 200,
|
||||||
json: async () => mockExplorer.items,
|
json: async () => mockExplorer.items,
|
||||||
})),
|
})),
|
||||||
|
urlOrigin: () => "",
|
||||||
setRuntimeDebug: () => {},
|
setRuntimeDebug: () => {},
|
||||||
isDebug: () => false,
|
isDebug: () => false,
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ jest.mock("../src/shared/log", () => ({
|
|||||||
errorf: () => {},
|
errorf: () => {},
|
||||||
},
|
},
|
||||||
debugFetch: jest.fn(),
|
debugFetch: jest.fn(),
|
||||||
|
urlOrigin: () => "",
|
||||||
setRuntimeDebug: () => {},
|
setRuntimeDebug: () => {},
|
||||||
isDebug: () => false,
|
isDebug: () => false,
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user