Security: post-review fixes for merchant-sdk#8
Merged
marceloceccon merged 1 commit intodevelopfrom May 10, 2026
Merged
Conversation
Hardenings flowing from the security review of the v0.5.0 additions
(estimateGas, telemetry breadcrumb, headless hooks + Web Components,
EIP-2612 permit signing, ConfirmationPolicy):
- src/evm/permit.ts:
* cap permit deadlines at 60 minutes from now (rejects MAX_SAFE_INTEGER
and other no-expiry bearer windows);
* cross-validate input.chainId against walletClient.getChainId() before
signing so a stale config can't produce a payload replayable on the
wallet's actual chain;
* run validatePermitSignature() on the wallet's reply before returning,
catching corrupt signatures earlier than the token contract would.
- src/core/telemetry.ts: redactErrorMessage now strips POSIX/Windows
filesystem paths, file:// URLs, and long bare hex blobs (>=64 chars,
private-key shaped) in addition to addresses, tx hashes, UUIDs, and
base58 pubkeys. Stops integrators from leaking stack-trace paths and
raw secrets via 3rd-party analytics pipelines.
- src/solana/estimateGas.ts: throw when BOTH simulateTransaction and
getRecentPrioritizationFees fail rather than silently returning the
static 5000-lamport signature fee (which would render as "~$0.001"
even on a congested cluster). The single-failure tolerant paths stay
in place.
- package.json: declare the wc subpath entrypoint as having side effects
so tree-shaking-aware bundlers don't drop the customElements.define
call. This is the documented contract; the manifest now matches.
- src/index.ts: re-export MAX_PERMIT_DEADLINE_WINDOW_SECONDS so callers
can introspect the cap (e.g. to render their own deadline-picker UI
without hard-coding the value).
Tests: 171 pass (149 baseline + 22 new for permit hardenings, telemetry
path/hex redaction, and the Solana both-fail throw).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Hardenings flowing from the security review of the v0.5.0 additions (
estimateGasper chain, telemetry breadcrumb, headless hooks + Web Components, EIP-2612 permit signing, SDK-levelConfirmationPolicy).src/evm/permit.ts)MAX_PERMIT_DEADLINE_WINDOW_SECONDS). BlocksNumber.MAX_SAFE_INTEGER/ other "no-expiry" bearer windows. EIP-2612 signatures are bearer instruments; replay risk grows linearly with the deadline window.input.chainIdagainstwalletClient.getChainId()before signing. Stops a stale-config caller from producing a payload that's actually valid on the wallet's real chain instead.validatePermitSignature()on the wallet's reply insidesignPermitbefore returning. Catches malformed signatures (zero r/s, high-s, bad v) before the caller broadcasts an unrecoverablepermit(...)tx.src/core/telemetry.ts) —redactErrorMessagenow strips POSIX & Windows filesystem paths,file://URLs, and long bare hex blobs (>=64 chars, private-key/raw-signature shaped) in addition to the existing address/tx-hash/UUID/base58 redactions. Stops integrators from leaking stack-trace paths or raw secrets via 3rd-party analytics.src/solana/estimateGas.ts) — throw when bothsimulateTransactionandgetRecentPrioritizationFeesfail, instead of silently returning the static 5 000-lamport signature fee (which would render as "~$0.001" even on a congested cluster). The single-failure tolerant paths are unchanged.package.json— declare thewcsubpath as having side effects so tree-shaking bundlers don't drop thecustomElements.definecall. This is the documented contract; the manifest now matches.src/index.ts— re-exportMAX_PERMIT_DEADLINE_WINDOW_SECONDSso callers can introspect the cap (e.g. to drive a deadline-picker UI without hard-coding the constant).Out of scope (surfaced, not fixed)
payment-configis unsigned. A MITM with TLS-stripping or a compromised proxy could swapcontractAddress/ token allowlist in the response and the SDK would happily use the attacker's contract. Confirmed with the brief — known v1.0 gap, deferred.innerHTML— only ever assigns a constant template; attribute values flow intotextContent/ booleandisabled. No XSS vector.wc/pay-button.tsdoes callregisterWebComponents()at module top-level, but it's gated behindtypeof customElements !== 'undefined'so it's SSR-safe.target=\"_blank\"anchors already carryrel=\"noopener noreferrer\".npm auditreports 0 vulnerabilities; theoverridesblock resolves to current latest versions of axios / hono / fast-uri / lodash / follow-redirects / esbuild — none deprecated or unmaintained.Test plan
npm run test -- --run— 171 pass (149 baseline + 22 new). New tests cover:validatePermitSignaturecorner cases (5 cases each inassertDeadlineFresh,buildPermitTypedData,validatePermitSignature,signPermit— 17 total inpermit.test.ts);file://paths and long hex blobs (4 new intelemetry.test.ts);solana-estimateGas.test.ts).npm run lintclean.npm run typecheckclean.npm run buildsucceeds; bundle sizes unchanged.npm audit --audit-level=lowreports 0 vulnerabilities.