Skip to content

fix: harden frontend security entrypoints#7601

Open
fairlighteth wants to merge 18 commits into
developfrom
fix/deepsec-high-frontend-hardening
Open

fix: harden frontend security entrypoints#7601
fairlighteth wants to merge 18 commits into
developfrom
fix/deepsec-high-frontend-hardening

Conversation

@fairlighteth

@fairlighteth fairlighteth commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

What changed

  • Added safe URL validation before rendering external links from hook dapps, CMS notifications, bridge explorer data, and shared explorer URL builders.
  • Stopped hydrating hidden recipient state from the legacy recipientAddress URL param.
  • Tightened account-proxy recovery so signing is only enabled for valid token input and an owned proxy.
  • Restricted widget Safe SDK message forwarding to the expected iframe and parent origins.
  • Removed the browser-facing export of the Pinata secret env var.

Why

This addresses the frontend/widget DeepSec HIGH findings that can be fixed with validation and trust-boundary hardening.

Out of scope for this PR:

  • cow-fi
  • hook iframe payload semantic validation and review/confirmation flow
  • structural TWAP Safe transaction decoding for direct calls and MultiSend inner calls

Validation

Automated:

  • Safe URL tests cover HTTPS handling, localhost HTTP in dev, IPv6 localhost, bare-origin normalization, and unsafe URL rejection.
  • Explorer link tests cover shared URL builder normalization.
  • Recipient tests cover the recipientAddress-only URL case.
  • Hook rendering tests cover unsafe simulation URLs rendering inertly.

Manual QA:

  • Valid hook website URLs still render as clickable external links.
  • Invalid hook website URLs render as plain text.
  • Valid CMS notification CTAs still open internal/external links correctly.
  • Unsafe notification or bridge explorer URLs do not render clickable links.
  • Opening swap with only recipientAddress=0x... does not enable recipient mode.
  • Account-proxy recovery still works for an owned proxy and valid token when balance exists.
  • Account-proxy recovery stays disabled for an unowned proxy or invalid token.
  • Widget Safe SDK messages still pass between the expected iframe and parent, while unrelated postMessage traffic is ignored.
pr-7601-manual-qa.mp4

Reviewer notes:

  • recipient=<value> remains the intended custom-recipient URL param.
  • The old hidden recipientAddress param is intentionally no longer trusted.

- validate external links and bridge explorer URLs before rendering
- stop trusting URL-derived recipients and arbitrary proxy recovery
- scope Safe SDK iframe relaying to trusted widget and parent origins
- remove browser exposure of the Pinata secret export
@vercel

vercel Bot commented Jun 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cowfi Ready Ready Preview Jun 17, 2026 4:09pm
explorer-dev Ready Ready Preview Jun 17, 2026 4:09pm
storybook Ready Ready Preview Jun 17, 2026 4:09pm
swap-dev Ready Ready Preview Jun 17, 2026 4:09pm
widget-configurator Ready Ready Preview Jun 17, 2026 4:09pm
2 Skipped Deployments
Project Deployment Actions Updated (UTC)
cosmos Ignored Ignored Jun 17, 2026 4:09pm
sdk-tools Ignored Ignored Preview Jun 17, 2026 4:09pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Introduces safe URL parsing utilities that validate protocol schemes, reject credentials, and normalize bare origins; applies them to link rendering across hook components, bridge/transaction/notification UI, manifest validation, and explorer helpers. Hardens the iframe SDK bridge to use explicit trusted origins for message posting instead of wildcards. Also includes account proxy input validation, trade state field consolidation, and Pinata environment variable updates.

Changes

URL Safety Validation & Security Hardening

Layer / File(s) Summary
Safe link utility and tests
libs/common-utils/src/safeLink.ts, libs/common-utils/src/safeLink.test.ts, libs/common-utils/src/index.ts
New getSafeAbsoluteUrl (https always allowed, http only in dev for localhost) and getSafeSameOriginOrAbsoluteUrl (marks external links by origin comparison) with comprehensive test coverage and re-export.
Explorer link normalization
libs/common-utils/src/getExplorerLink.ts, libs/common-utils/src/getExplorerLink.test.ts, libs/common-utils/src/legacyAddressUtils.ts, libs/common-utils/src/legacyAddressUtils.test.ts
Explorer base URLs normalized via safe parsing; prevents double slashes when origin lacks trailing slash; tests verify correct formatting.
Hook component safe rendering
apps/cowswap-frontend/src/common/containers/OrderHooksDetails/HookItem/index.tsx, .../index.test.tsx, apps/cowswap-frontend/src/modules/hooksStore/pure/AppliedHookItem/index.tsx, .../index.test.tsx, apps/cowswap-frontend/src/modules/hooksStore/pure/HookDappDetails/index.tsx
HookItem, AppliedHookItem, and HookDappDetails derive safe simulation and website URLs; external anchors render only when validation succeeds; tests assert unsafe hrefs render as inert text.
Hook manifest validation & localization
apps/cowswap-frontend/src/modules/hooksStore/validateHookDappManifest.tsx, apps/cowswap-frontend/src/modules/hooksStore/pure/AddCustomHookForm/constants.tsx, apps/cowswap-frontend/src/locales/en-US.po
Manifest website field validated with getSafeAbsoluteUrl; new INVALID_WEBSITE_URL error message specifies https required and http only in dev; locale entries updated.
Bridge, transaction & notification links
apps/cowswap-frontend/src/modules/bridge/pure/TransactionLink/TransactionLinkDisplay.tsx, apps/cowswap-frontend/src/modules/bridge/pure/contents/BridgingProgressContent/index.tsx, apps/cowswap-frontend/src/modules/orders/containers/BridgingSuccessNotification/index.tsx, apps/cowswap-frontend/src/modules/application/containers/AppContainer/CowSpeechBubble/CowSpeechBubbleNotificationBanner.tsx
Explorer and notification URLs sanitized via safe helpers; external links render only when validation succeeds; unsafe links return null or render as plain text.
Bridge analytics & survey sanitization
apps/cowswap-frontend/src/modules/bridge/updaters/PendingBridgeOrdersUpdater.tsx
Explorer URLs normalized before sending snackbar events, analytics payloads, and Appzi survey data (nps, pending-too-long).
SDK bridge trusted-origin routing
libs/widget-lib/src/IframeSafeSdkBridge.ts, libs/widget-lib/src/cowSwapWidget.ts
IframeSafeSdkBridge now validates event.source and event.origin against explicit iframe/parent origins, posts messages only to trusted origins, and rejects mismatched window relationships. New getTrustedParentOrigin helper derives parent from document.referrer. Widget wiring updated with computed origins.

Input Validation, Trade State, & Environment Configuration

Layer / File(s) Summary
Account proxy validation
apps/cowswap-frontend/src/modules/accountProxy/containers/AccountProxyRecoverPage/index.tsx
Proxy and token addresses validated from URL params before wiring into recovery hooks; validated token used for balance updates; button disabled state requires valid proxy and token.
Trade state recipient consolidation
apps/cowswap-frontend/src/modules/trade/hooks/setupTradeState/useSetupTradeStateFromUrl.ts, apps/cowswap-frontend/src/modules/trade/hooks/useWithRecipient.test.ts
Remove recipientAddress from URL parsing; consolidate to recipient field. Test added for recipientAddress-only input.
Pinata environment variable
libs/common-const/src/ipfs.ts, apps/cowswap-frontend/src/legacy/state/application/localWarning.ts
Rename PINATA_SECRET_API_KEY to PINATA_API_KEY; update local warning to check API key and instruct server-side configuration.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • cowprotocol/cowswap#7460: Related Safe SDK bridge origin validation and trusted parent-origin derivation in widget initialization.
  • cowprotocol/cowswap#7298: Related iframe communication hardening using validated/trusted origins instead of wildcards across widget bridge and transport layers.
  • cowprotocol/cowswap#7631: Overlaps in widget iframe setup modifications within libs/widget-lib, including bridge initialization paths.

Suggested reviewers

  • Danziger
  • alfetopito
  • kernelwhisperer
  • shoom3301

Poem

🐰 Rabbit's hop toward safer clicks:
URLs parsed with care and tricks,
https hops and localhost picks,
Iframe bridges now trust origins true—
Safe message forwarding breaks through! 🐇✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: harden frontend security entrypoints' directly aligns with the main objective of the PR, which addresses security findings by validating external links, controlling access to trusted origins, and removing secret exposure.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description provides a comprehensive summary of changes, reasoning, validation approach, and manual QA steps, addressing all key aspects of the security-focused modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/deepsec-high-frontend-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 3, 2026

Copy link
Copy Markdown

Deploying explorer-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: d318f9f
Status: ✅  Deploy successful!
Preview URL: https://bac7ab86.explorer-dev-dxz.pages.dev
Branch Preview URL: https://fix-deepsec-high-frontend-ha.explorer-dev-dxz.pages.dev

View logs

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jun 3, 2026

Copy link
Copy Markdown

Deploying swap-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: d318f9f
Status: ✅  Deploy successful!
Preview URL: https://f223476c.swap-dev-5u6.pages.dev
Branch Preview URL: https://fix-deepsec-high-frontend-ha.swap-dev-5u6.pages.dev

View logs

- stop checking an unprefixed secret env var from browser code
- warn only on missing client-visible Pinata config
fairlighteth and others added 2 commits June 3, 2026 16:40
…rontend-hardening

# Conflicts:
#	apps/cowswap-frontend/src/modules/bridge/pure/TransactionLink/TransactionLinkDisplay.tsx
#	libs/widget-lib/src/cowSwapWidget.ts
- normalize bracketed IPv6 localhost before allowlist checks
- add a regression test for http://[::1] development URLs
const dappName = item.dapp?.name || t`Unknown Hook`
const safeWebsiteUrl = item.dapp ? getSafeAbsoluteUrl(item.dapp.website) : null
const websiteHostname = safeWebsiteUrl ? new URL(safeWebsiteUrl).hostname : null
const safeSimulationUrl = simulationData ? getSafeAbsoluteUrl(simulationData.link) : null

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes hook website/simulation URLs safe before turning them into links, so a malicious hook cannot sneak in a javascript: link.

@@ -0,0 +1,77 @@
import React from 'react'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds a test proving an unsafe simulation URL is shown as text, not as a clickable link.

simulationTooltip: string
}

function BundleSimulationStatus({

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applies the same safety rule to already-applied hook simulation links, so bad simulation URLs are not clickable.

@@ -0,0 +1,102 @@
import React from 'react'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds coverage that applied hook simulation links become plain text when the URL is unsafe.

INVALID_MANIFEST: msg`Invalid manifest format: Missing "cow_hook_dapp" property in manifest.json`,
SMART_CONTRACT_INCOMPATIBLE: msg`This hook is not compatible with smart contract wallets. It only supports EOA wallets.`,
INVALID_HOOK_ID: msg`Invalid hook dapp ID format. The ID must be a 64-character hexadecimal string.`,
INVALID_WEBSITE_URL: msg`Invalid website URL in manifest. Only http(s) URLs are allowed, with http limited to local development.`,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds the user-facing error message for rejecting unsafe website URLs in custom hook manifests.

@fairlighteth fairlighteth left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding lightweight reviewer notes for the remaining DeepSec hardening files.

return i18n._(ERROR_MESSAGES.INVALID_HOOK_ID)
}

if (!getSafeAbsoluteUrl(dapp.website)) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rejects custom hook manifests with unsafe website URLs before saving or rendering them.

const { i18n } = useLingui()
const tags = useMemo(() => {
const { version, website, type, conditions } = dapp
const safeWebsiteUrl = getSafeAbsoluteUrl(website)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only renders the hook website as a link if it passes the safe URL check; otherwise it stays inert text.


const { title, description, url, id } = currentNotification
const linkTarget = url && isInternal(url) ? '_parent' : '_blank'
const safeLink =

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validates CMS notification links before rendering, so CMS content cannot create unsafe Learn more links.

}

export function TransactionLinkDisplay({ link, label, linkText }: TransactionLinkDisplayProps): ReactNode {
const safeLink = getSafeAbsoluteUrl(link)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refuses to render bridge transaction links unless the URL is safe.

statusResult,
explorerUrl,
} = props
const safeExplorerUrl = getSafeAbsoluteUrl(explorerUrl) || undefined

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleans the bridge explorer URL before passing it down to pending and received bridge UI.

})
})

describe('#getBlockExplorerUrl', () => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds test coverage for the older explorer URL builder.

export * from './request'
export * from './resolveENSContentHash'
export * from './retry'
export * from './safeLink'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exports the new safe-link helper so app modules can use the same validation everywhere.


if (isSafeMessageRequest(event.data)) {
this.appWindow.parent.postMessage(event.data, '*')
if (

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Locks Safe SDK message forwarding to the expected iframe and parent origins instead of forwarding any Safe-shaped message.

Comment thread libs/widget-lib/src/cowSwapWidget.ts Outdated

// 10. Listen for Safe SDK messages from the iframe only when explicitly enabled by the host.
const iframeSafeSdkBridge = enableSafeSdkBridge ? new IframeSafeSdkBridge(window, iframeWindow) : null
const iframeSafeSdkBridge = enableSafeSdkBridge

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passes the trusted iframe and parent origins into the Safe SDK bridge so it can enforce the origin checks.

msgstr "This transaction can be simulated before execution to ensure that it will be succeed, generating a detailed report of the transaction execution."

#: apps/cowswap-frontend/src/modules/hooksStore/pure/AddCustomHookForm/constants.tsx
msgid "Invalid website URL in manifest. Only http(s) URLs are allowed, with http limited to local development."

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adds the translated string for the new invalid hook website URL error.

- reduce Safe SDK bridge lint complexity

- preserve UI exports in AppliedHookItem test mock
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants