-
Notifications
You must be signed in to change notification settings - Fork 171
fix: harden release workflows and widget snippets #7616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 4 commits
b33b63a
f199dc5
bf1c05d
3e9c2c8
c7c2223
196741e
ec41df5
3b16d1f
df6fe38
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As Sasha mentioned, this one can remain untouched as it'll be removed soon. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,8 +3,8 @@ name: Deployment | |
| on: | ||
| # build when pushing to main/develop, or create a release | ||
| push: | ||
| branches: [ main, develop ] | ||
| tags: [ cowswap-v*, explorer-v* ] | ||
| branches: [main, develop] | ||
| tags: [cowswap-v*, explorer-v*] | ||
| workflow_dispatch: # Manually trigger it via UI/CLI/API | ||
| inputs: | ||
| app: | ||
|
|
@@ -16,7 +16,39 @@ on: | |
| - EXPLORER | ||
| - ALL | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| validate-release-tag: | ||
| name: Validate release tag | ||
| if: startsWith(github.ref, 'refs/tags/') | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| release-commit: ${{ steps.validate.outputs.release-commit }} | ||
| steps: | ||
| - name: Checkout trusted workflow repository | ||
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
| with: | ||
| ref: refs/heads/main | ||
| fetch-depth: 0 | ||
| persist-credentials: false | ||
|
|
||
| - name: Validate release commit | ||
| id: validate | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| git fetch origin main --tags | ||
|
|
||
| release_commit="$(git rev-list -n 1 "${GITHUB_REF}")" | ||
|
|
||
| if ! git merge-base --is-ancestor "${release_commit}" "origin/main"; then | ||
| echo "::error::Release tag ${GITHUB_REF_NAME} does not point to a commit reachable from origin/main" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "release-commit=${release_commit}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| vercel-dev: | ||
| # Deploys to Vercel dev environment | ||
|
|
@@ -26,7 +58,7 @@ jobs: | |
| secrets: inherit | ||
| strategy: | ||
| matrix: | ||
| app: [ EXPLORER, COWSWAP ] | ||
| app: [EXPLORER, COWSWAP] | ||
| with: | ||
| env_name: dev | ||
| app: ${{ matrix.app }} | ||
|
|
@@ -35,15 +67,49 @@ jobs: | |
| # Deploys to Vercel staging environment only when there is a tag for CowSwap or Explorer | ||
| name: Vercel pre-prod | ||
| if: startsWith(github.ref, 'refs/tags') | ||
| uses: ./.github/workflows/vercel.yml | ||
| secrets: inherit | ||
| needs: validate-release-tag | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as I understand, we should remove
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes!
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok reverted the file now ec41df5 |
||
| uses: cowprotocol/cowswap/.github/workflows/vercel.yml@main | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| secrets: | ||
| VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} | ||
| VERCEL_PROJECT_ID_COWSWAP: ${{ secrets.VERCEL_PROJECT_ID_COWSWAP }} | ||
| VERCEL_PROJECT_ID_EXPLORER: ${{ secrets.VERCEL_PROJECT_ID_EXPLORER }} | ||
| VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} | ||
| SENTRY_DSN: ${{ secrets.SENTRY_DSN }} | ||
| REACT_APP_PINATA_API_KEY: ${{ secrets.REACT_APP_PINATA_API_KEY }} | ||
| REACT_APP_PINATA_SECRET_API_KEY: ${{ secrets.REACT_APP_PINATA_SECRET_API_KEY }} | ||
| REACT_APP_BLOCKNATIVE_API_KEY: ${{ secrets.REACT_APP_BLOCKNATIVE_API_KEY }} | ||
| REACT_APP_GOOGLE_ANALYTICS_ID: ${{ secrets.REACT_APP_GOOGLE_ANALYTICS_ID }} | ||
| REACT_APP_LAUNCH_DARKLY_KEY: ${{ secrets.REACT_APP_LAUNCH_DARKLY_KEY }} | ||
| REACT_APP_INFURA_KEY: ${{ secrets.REACT_APP_INFURA_KEY }} | ||
| REACT_APP_NETWORK_URL_1: ${{ secrets.REACT_APP_NETWORK_URL_1 }} | ||
| REACT_APP_NETWORK_URL_56: ${{ secrets.REACT_APP_NETWORK_URL_56 }} | ||
| REACT_APP_NETWORK_URL_100: ${{ secrets.REACT_APP_NETWORK_URL_100 }} | ||
| REACT_APP_NETWORK_URL_137: ${{ secrets.REACT_APP_NETWORK_URL_137 }} | ||
| REACT_APP_NETWORK_URL_8453: ${{ secrets.REACT_APP_NETWORK_URL_8453 }} | ||
| REACT_APP_NETWORK_URL_9745: ${{ secrets.REACT_APP_NETWORK_URL_9745 }} | ||
| REACT_APP_NETWORK_URL_42161: ${{ secrets.REACT_APP_NETWORK_URL_42161 }} | ||
| REACT_APP_NETWORK_URL_43114: ${{ secrets.REACT_APP_NETWORK_URL_43114 }} | ||
| REACT_APP_NETWORK_URL_57073: ${{ secrets.REACT_APP_NETWORK_URL_57073 }} | ||
| REACT_APP_NETWORK_URL_59144: ${{ secrets.REACT_APP_NETWORK_URL_59144 }} | ||
| REACT_APP_NETWORK_URL_11155111: ${{ secrets.REACT_APP_NETWORK_URL_11155111 }} | ||
| REACT_APP_WC_PROJECT_ID: ${{ secrets.REACT_APP_WC_PROJECT_ID }} | ||
| REACT_APP_IPFS_READ_URI: ${{ secrets.REACT_APP_IPFS_READ_URI }} | ||
| EXPLORER_SENTRY_DSN: ${{ secrets.EXPLORER_SENTRY_DSN }} | ||
| REACT_APP_SUBGRAPH_URL_MAINNET: ${{ secrets.REACT_APP_SUBGRAPH_URL_MAINNET }} | ||
| REACT_APP_SUBGRAPH_URL_ARBITRUM_ONE: ${{ secrets.REACT_APP_SUBGRAPH_URL_ARBITRUM_ONE }} | ||
| REACT_APP_SUBGRAPH_URL_BASE: ${{ secrets.REACT_APP_SUBGRAPH_URL_BASE }} | ||
| REACT_APP_SUBGRAPH_URL_GNOSIS_CHAIN: ${{ secrets.REACT_APP_SUBGRAPH_URL_GNOSIS_CHAIN }} | ||
| BFF_BASE_URL: ${{ secrets.BFF_BASE_URL }} | ||
| CMS_BASE_URL: ${{ secrets.CMS_BASE_URL }} | ||
| REACT_APP_NEAR_API_KEY: ${{ secrets.REACT_APP_NEAR_API_KEY }} | ||
| strategy: | ||
| matrix: | ||
| env_name: [ staging ] # deploys both in parallel | ||
| env_name: [staging] # deploys both in parallel | ||
| with: | ||
| env_name: ${{ matrix.env_name }} | ||
| # Pick app according to published tag | ||
| app: ${{ startsWith(github.ref, 'refs/tags/explorer') && 'EXPLORER' || 'COWSWAP' }} | ||
| checkout_ref: ${{ needs.validate-release-tag.outputs.release-commit }} | ||
| disable_nx_cache: true | ||
|
|
||
| vercel-prod: | ||
|
|
@@ -62,7 +128,7 @@ jobs: | |
|
|
||
| notify-failure: | ||
| name: Notify Slack on Failure | ||
| needs: [ vercel-dev, vercel-pre-prod, vercel-prod ] | ||
| needs: [vercel-dev, vercel-pre-prod, vercel-prod] | ||
| runs-on: ubuntu-latest | ||
| if: failure() && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') | ||
|
|
||
|
|
||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| import { CowSwapWidgetParams, TradeType } from '@cowprotocol/widget-lib' | ||
|
|
||
| import { vanillaNoDepsExample } from './htmlExample' | ||
| import { jsExample } from './jsExample' | ||
| import { tsExample } from './tsExample' | ||
|
|
||
| import { ColorPalette } from '../../configurator/types' | ||
|
|
||
| const defaultPalette: ColorPalette = { | ||
| primary: '#000000', | ||
| background: '#111111', | ||
| paper: '#222222', | ||
| text: '#ffffff', | ||
| } | ||
|
|
||
| describe('widget snippet serialization', () => { | ||
| it('escapes script-breaking token values in the HTML snippet', () => { | ||
| const params: CowSwapWidgetParams = { | ||
| appCode: 'Widget App', | ||
| sell: { | ||
| asset: '</script><script>alert(1)</script>', | ||
| }, | ||
| } | ||
|
|
||
| const snippet = vanillaNoDepsExample(params, defaultPalette) | ||
|
|
||
| expect(snippet).not.toContain('</script><script>alert(1)</script>') | ||
| expect(snippet).toContain('\\u003c/script\\u003e\\u003cscript\\u003ealert(1)\\u003c/script\\u003e') | ||
| }) | ||
|
|
||
| it('drops invalid tradeType values from Javascript snippets', () => { | ||
| const params = { | ||
| appCode: 'Widget App', | ||
| tradeType: 'swap";alert(1);//', | ||
| } as unknown as CowSwapWidgetParams | ||
|
|
||
| const snippet = jsExample(params, defaultPalette) | ||
|
|
||
| expect(snippet).not.toContain('tradeType') | ||
| expect(snippet).not.toContain('alert(1)') | ||
| }) | ||
|
|
||
| it('emits TradeType enums only for valid Typescript trade types', () => { | ||
| const validParams: CowSwapWidgetParams = { | ||
| appCode: 'Widget App', | ||
| tradeType: TradeType.ADVANCED, | ||
| enabledTradeTypes: [TradeType.SWAP, TradeType.YIELD], | ||
| } | ||
|
|
||
| const validSnippet = tsExample(validParams, defaultPalette) | ||
|
|
||
| expect(validSnippet).toContain('TradeType.ADVANCED') | ||
| expect(validSnippet).toContain('TradeType.SWAP') | ||
| expect(validSnippet).toContain('TradeType.YIELD') | ||
|
|
||
| const invalidParams = { | ||
| appCode: 'Widget App', | ||
| tradeType: 'advanced};alert(1);//', | ||
| enabledTradeTypes: [TradeType.SWAP, 'yield);alert(1);//'], | ||
| } as unknown as CowSwapWidgetParams | ||
|
|
||
| const invalidSnippet = tsExample(invalidParams, defaultPalette) | ||
|
|
||
| expect(invalidSnippet).not.toContain('alert(1)') | ||
| expect(invalidSnippet).not.toContain('TradeType.ADVANCED};ALERT(1);//') | ||
| expect(invalidSnippet).toContain('TradeType.SWAP') | ||
| }) | ||
|
|
||
| it('handles malformed trade type shapes safely', () => { | ||
| const malformedParams = { | ||
| appCode: 'Widget App', | ||
| tradeType: '', | ||
| enabledTradeTypes: 'swap', | ||
| } as unknown as CowSwapWidgetParams | ||
|
|
||
| expect(() => jsExample(malformedParams, defaultPalette)).not.toThrow() | ||
| expect(() => tsExample(malformedParams, defaultPalette)).not.toThrow() | ||
|
|
||
| const jsSnippet = jsExample(malformedParams, defaultPalette) | ||
| const tsSnippet = tsExample(malformedParams, defaultPalette) | ||
|
|
||
| expect(jsSnippet).not.toContain('tradeType') | ||
| expect(jsSnippet).not.toContain('enabledTradeTypes') | ||
| expect(tsSnippet).not.toContain('tradeType') | ||
| expect(tsSnippet).not.toContain('enabledTradeTypes') | ||
| }) | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍