From c262d27d1c85c4264925f967720c5232a6e4f164 Mon Sep 17 00:00:00 2001 From: Coredevjay Date: Thu, 22 Oct 2026 09:36:40 +0000 Subject: [PATCH] fix(ci): repair ci.yml jobs and add share-URL privacy guard test Closes #649 Closes #650 Closes #651 Closes #652 --- .github/workflows/ci.yml | 46 ++---------------- src/lib/export/analyticsExport.test.ts | 65 ++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 41 deletions(-) create mode 100644 src/lib/export/analyticsExport.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40f3f02..8a53997 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -205,7 +205,7 @@ jobs: - run: npm ci - run: npx playwright install --with-deps ${{ matrix.browser }} - name: Run Playwright tests - run: npx playwright test --project=${{ matrix.browser }} --shard=${{ matrix.shard }}/${{ strategy.job-total }} + run: npx playwright test --project=${{ matrix.browser }} env: PLAYWRIGHT_RETRIES: '2' - name: Report flaky tests @@ -256,17 +256,6 @@ jobs: path: sbom.json retention-days: 90 - - name: Build Storybook - run: npm run build:storybook - - name: Run Storybook accessibility tests - run: | - npx playwright install --with-deps chromium - npx --yes http-server storybook-static --port 6006 --silent & - npx --yes wait-on --timeout 60000 tcp:127.0.0.1:6006 - npm run test:storybook - - name: Run visual regression tests - run: npm run test:visual - - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1 - name: Build Storybook run: npm run build:storybook - name: Run Storybook accessibility tests @@ -284,31 +273,6 @@ jobs: path: blob-report/ retention-days: 30 - e2e: - name: E2E (Playwright) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'npm' - - run: npm ci - - name: Check dependencies for supply-chain security (Issue #311) - run: node scripts/check-dependencies.mjs - env: - PR_BODY: ${{ github.event.pull_request.body }} - GITHUB_ACTOR: ${{ github.actor }} - - name: Lint lockfile - run: npm ls --all > /dev/null - - name: Generate SBOM - run: npx --yes @cy - - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - cache: 'npm' - - run: npm ci security: name: Security & supply chain runs-on: ubuntu-latest @@ -339,8 +303,8 @@ jobs: path: sbom.json retention-days: 30 - e2e: - name: E2E (Playwright) + e2e-smoke: + name: E2E smoke (Playwright, chromium) runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -376,12 +340,12 @@ jobs: ci-success: name: ci-success if: always() - needs: [static, unit, build, e2e, storybook-visual, security] + needs: [frontend, unit, build, e2e, storybook-visual, security] runs-on: ubuntu-latest steps: - name: Verify all required jobs succeeded run: | - echo "static=${{ needs.static.result }}" + echo "frontend=${{ needs.frontend.result }} }}" echo "unit=${{ needs.unit.result }}" echo "build=${{ needs.build.result }}" echo "e2e=${{ needs.e2e.result }}" diff --git a/src/lib/export/analyticsExport.test.ts b/src/lib/export/analyticsExport.test.ts new file mode 100644 index 0000000..a94045b --- /dev/null +++ b/src/lib/export/analyticsExport.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from "vitest"; +import { + buildShareUrl, + encodeShareState, + type AnalyticsShareState, +} from "./analyticsExport"; + +const STELLAR_PUBLIC_KEY = /G[A-Z2-7]{55}/; +const STELLAR_SECRET_KEY = /S[A-Z2-7]{55}/; +const XDR_PREFIX = /AAAA[A-Za-z0-9+/]{8,}/; + +/** True when `s` looks like a Stellar public/secret key or a base64 XDR blob. */ +function looksLikeStellarKeyOrXdr(s: string): boolean { + return ( + STELLAR_PUBLIC_KEY.test(s) || + STELLAR_SECRET_KEY.test(s) || + XDR_PREFIX.test(s) + ); +} + +const FIXTURES: AnalyticsShareState[] = [ + {}, + { from: "2025-01-01T00:00:00.000Z", to: "2025-12-31T23:59:59.999Z" }, + { chain: "stellar", token: "USDC" }, + { + from: "2025-01-01", + to: "2025-06-30", + chain: "ethereum", + token: "XLM", + }, +]; + +describe("analytics share URL privacy policy", () => { + it.each(FIXTURES.map((f) => [JSON.stringify(f), f] as const))( + "never carries wallet or XDR data: %s", + (_label, state) => { + const encoded = encodeShareState(state); + const decodedJson = atob(encoded); + const url = buildShareUrl(state); + + for (const [field, value] of Object.entries(JSON.parse(decodedJson))) { + expect( + looksLikeStellarKeyOrXdr(String(value)), + `share-state field "${field}" looks like a Stellar key or XDR`, + ).toBe(false); + } + expect(looksLikeStellarKeyOrXdr(encoded), "encoded share state").toBe( + false, + ); + expect(looksLikeStellarKeyOrXdr(decodedJson), "decoded share state").toBe( + false, + ); + expect(looksLikeStellarKeyOrXdr(url), "share URL").toBe(false); + }, + ); + + it("the guard detects wallet-shaped values", () => { + const g = "G" + "A".repeat(55); + const s = "S" + "B".repeat(55); + expect(looksLikeStellarKeyOrXdr(g)).toBe(true); + expect(looksLikeStellarKeyOrXdr(s)).toBe(true); + expect(looksLikeStellarKeyOrXdr("AAAAAgAAAAC12345678")).toBe(true); + expect(looksLikeStellarKeyOrXdr("USDC")).toBe(false); + }); +});