Skip to content

Security: post-review fixes for merchant-sdk#8

Merged
marceloceccon merged 1 commit intodevelopfrom
security/post-review-fixes-2026-05-10
May 10, 2026
Merged

Security: post-review fixes for merchant-sdk#8
marceloceccon merged 1 commit intodevelopfrom
security/post-review-fixes-2026-05-10

Conversation

@marceloceccon
Copy link
Copy Markdown
Member

Summary

Hardenings flowing from the security review of the v0.5.0 additions (estimateGas per chain, telemetry breadcrumb, headless hooks + Web Components, EIP-2612 permit signing, SDK-level ConfirmationPolicy).

  • EIP-2612 permit hardenings (src/evm/permit.ts)
    • Cap permit deadlines at 60 min from now (MAX_PERMIT_DEADLINE_WINDOW_SECONDS). Blocks Number.MAX_SAFE_INTEGER / other "no-expiry" bearer windows. EIP-2612 signatures are bearer instruments; replay risk grows linearly with the deadline window.
    • Cross-validate input.chainId against walletClient.getChainId() before signing. Stops a stale-config caller from producing a payload that's actually valid on the wallet's real chain instead.
    • Run validatePermitSignature() on the wallet's reply inside signPermit before returning. Catches malformed signatures (zero r/s, high-s, bad v) before the caller broadcasts an unrecoverable permit(...) tx.
  • Telemetry redaction (src/core/telemetry.ts)redactErrorMessage now 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.
  • Solana fee estimator (src/solana/estimateGas.ts) — throw when both simulateTransaction and getRecentPrioritizationFees fail, 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 the wc subpath as having side effects so tree-shaking 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 drive a deadline-picker UI without hard-coding the constant).

Out of scope (surfaced, not fixed)

  • payment-config is unsigned. A MITM with TLS-stripping or a compromised proxy could swap contractAddress / token allowlist in the response and the SDK would happily use the attacker's contract. Confirmed with the brief — known v1.0 gap, deferred.
  • Web Component innerHTML — only ever assigns a constant template; attribute values flow into textContent / boolean disabled. No XSS vector.
  • Headless hooks have no side effects on import. wc/pay-button.ts does call registerWebComponents() at module top-level, but it's gated behind typeof customElements !== 'undefined' so it's SSR-safe.
  • All target=\"_blank\" anchors already carry rel=\"noopener noreferrer\".
  • npm audit reports 0 vulnerabilities; the overrides block 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:
    • permit deadline cap, chainId mismatch refusal, malformed-signature rejection, validatePermitSignature corner cases (5 cases each in assertDeadlineFresh, buildPermitTypedData, validatePermitSignature, signPermit — 17 total in permit.test.ts);
    • telemetry redaction of POSIX/Windows/file:// paths and long hex blobs (4 new in telemetry.test.ts);
    • Solana estimator throw when both dynamic sources fail (1 new in solana-estimateGas.test.ts).
  • npm run lint clean.
  • npm run typecheck clean.
  • npm run build succeeds; bundle sizes unchanged.
  • npm audit --audit-level=low reports 0 vulnerabilities.

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).
@marceloceccon marceloceccon merged commit 81450ba into develop May 10, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant