harden: debug mode logs only a request's origin and JSON-RPC method (closes #410)
With debug mode on, debugFetch logged every request's full URL and body, so an RPC endpoint with an API key in its path or query string printed that key to the console on every request. It now logs the HTTP method, the URL's origin and, for a JSON-RPC body, the method name. The balance refresh and token lookup log the RPC endpoint by its origin too. Failed RPC calls print ethers' short message, since its full message for an HTTP error carries the request URL. A failed endpoint check in settings prints the endpoint's origin, since fetch's error for a URL with a user name and password carries the whole URL. The README's DEBUG Mode Policy says what debug mode logs. Model: opus-5-5
This commit was merged in pull request #436.
This commit is contained in:
@@ -75,7 +75,7 @@ async function getFullWarnings(address, provider, options = {}) {
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log.errorf("contract check failed:", e.message);
|
||||
log.errorf("contract check failed:", e.shortMessage || e.message);
|
||||
}
|
||||
|
||||
// Skip tx count check for contracts — they may legitimately have
|
||||
@@ -92,7 +92,7 @@ async function getFullWarnings(address, provider, options = {}) {
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
log.errorf("tx count check failed:", e.message);
|
||||
log.errorf("tx count check failed:", e.shortMessage || e.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ const {
|
||||
} = require("ethers");
|
||||
const { ERC20_ABI } = require("./constants");
|
||||
const { NETWORKS } = require("./networks");
|
||||
const { log, debugFetch } = require("./log");
|
||||
const { log, debugFetch, urlOrigin } = require("./log");
|
||||
const { deriveAddressFromXpub } = require("./wallet");
|
||||
const { TOKEN_BY_ADDRESS } = require("./tokenList");
|
||||
const { LOW_HOLDER_THRESHOLD, parseHoldersCount } = require("./holders");
|
||||
@@ -203,7 +203,7 @@ async function refreshBalances(
|
||||
trackedTokens,
|
||||
networkId,
|
||||
) {
|
||||
log.debugf("refreshBalances start, rpc:", rpcUrl);
|
||||
log.debugf("refreshBalances start, rpc:", urlOrigin(rpcUrl));
|
||||
const provider = getProvider(rpcUrl, networkId);
|
||||
const updates = [];
|
||||
|
||||
@@ -246,7 +246,7 @@ async function refreshBalances(
|
||||
log.errorf(
|
||||
"ENS reverse failed",
|
||||
addr.address,
|
||||
e.message,
|
||||
e.shortMessage || e.message,
|
||||
);
|
||||
// Keep existing addr.ensName if we had one
|
||||
}),
|
||||
@@ -280,7 +280,7 @@ async function refreshBalances(
|
||||
// Look up token metadata from its contract.
|
||||
// Calls symbol() and decimals() to verify it implements ERC-20.
|
||||
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 contract = new Contract(contractAddress, ERC20_ABI, provider);
|
||||
|
||||
@@ -305,7 +305,10 @@ async function lookupTokenInfo(contractAddress, rpcUrl, networkId) {
|
||||
name = await contract.name();
|
||||
log.debugf("name() =", name);
|
||||
} catch (e) {
|
||||
log.warnf("name() failed, using symbol as name:", e.message);
|
||||
log.warnf(
|
||||
"name() failed, using symbol as name:",
|
||||
e.shortMessage || e.message,
|
||||
);
|
||||
name = symbol;
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -42,7 +42,11 @@ async function resolveEnsName(address, rpcUrl, networkId) {
|
||||
setCache(address, name);
|
||||
return name;
|
||||
} catch (e) {
|
||||
log.errorf("ENS reverse lookup failed", address, e.message);
|
||||
log.errorf(
|
||||
"ENS reverse lookup failed",
|
||||
address,
|
||||
e.shortMessage || e.message,
|
||||
);
|
||||
// Don't cache failures — let subsequent lookups retry
|
||||
return null;
|
||||
}
|
||||
|
||||
+25
-5
@@ -42,14 +42,34 @@ 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, and a URL
|
||||
// can carry a user name and password, which the origin leaves out. 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) {
|
||||
const method = (opts && opts.method) || "GET";
|
||||
const body = opts && opts.body;
|
||||
log.debugf("fetch →", method, url, body || "");
|
||||
const origin = urlOrigin(url);
|
||||
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);
|
||||
log.debugf("fetch ←", resp.status, url);
|
||||
log.debugf("fetch ←", resp.status, origin);
|
||||
return resp;
|
||||
}
|
||||
|
||||
module.exports = { log, debugFetch, setRuntimeDebug, isDebug };
|
||||
module.exports = { log, debugFetch, urlOrigin, setRuntimeDebug, isDebug };
|
||||
|
||||
Reference in New Issue
Block a user