Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 5 additions & 41 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 }}"
Expand Down
65 changes: 65 additions & 0 deletions src/lib/export/analyticsExport.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
Loading