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
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ jobs:
- run: pnpm install --frozen-lockfile
- run: pnpm format:check
- run: pnpm build

# Fails when a new critical (or more than the accepted number of high)
# production dependency finding appears. See docs/SECURITY_AUDIT.md.
- run: pnpm audit:gate
154 changes: 154 additions & 0 deletions docs/SECURITY_AUDIT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Dependency security audit

Tracking issue: [#223 — Remediate critical demo runtime dependency findings](https://github.com/wraith-protocol/demo/issues/223).

This document records how the production dependency audit for the demo is kept
healthy and lists every finding we knowingly accept, who owns it, and when it
must be re-checked. CI enforces the baseline described here.

## Policy

- **No critical findings are ever accepted.** `pnpm audit:gate` (run in CI)
fails the build as soon as a critical production finding appears.
- Direct dependencies are kept on patched releases.
- When a transitive finding has no patched release (or upgrading would break a
supported chain integration), it is recorded in [Accepted findings](#accepted-findings)
with an owner and an expiry date instead of being silently ignored.
- The accepted baseline is small and is expected to shrink, never grow.

## Result

`pnpm audit --prod` before and after this remediation:

| Severity | Before | After |
| --------- | -----: | ----: |
| Critical | 1 | 0 |
| High | 53 | 1 |
| Moderate | 86 | 8 |
| Low | 7 | 2 |
| **Total** | 147 | 11 |

The single remaining high finding (`braces`) has no patched release upstream and
is documented below. The critical (`shell-quote`) was removed by an override.

## How to run the audit

```bash
pnpm audit:prod # human-readable production audit
pnpm audit:gate # what CI enforces (exit non-zero on regression)
```

`pnpm audit:gate` runs `pnpm audit --prod --json`, compares the severity counts
against the baseline in `scripts/audit-gate.mjs`, and prints a summary. The gate
fails when the audit does not return a successful result — a registry error
(`{"error": {...}}`), non-JSON output, or a payload without
`metadata.vulnerabilities`. An unsuccessful audit is never read as zero
findings; `scripts/audit-gate.test.mjs` guards that regression.

## Remediation applied

### Direct dependencies upgraded

| Package | From | To | Why |
| ---------------------------- | ----------------- | ----------------- | ------------------------------------------------------ |
| `react-router-dom` | `^7.6.0` | `^7.18.4` | Resolves the `react-router` advisories. |
| `@stellar/stellar-sdk` | `^13.1.0` | `^13.3.0` | Latest patched 13.x; avoids a breaking major API jump. |
| `@ckb-ccc/ccc` | `^1.1.25` | `^1.3.6` | Patched 1.x line (CKB). |
| `wagmi` | `^2.14.0` | `^2.19.5` | Latest 2.x (RainbowKit 2.x is not compatible with v3). |
| `@rainbow-me/rainbowkit` | `^2.2.0` | `^2.2.11` | Matches the wagmi 2.x line. |
| `viem` | `^2.23.0` | `^2.57.7` | Patched 2.x. |
| `@solana/web3.js` | `^1.98.4` | `^1.99.0` | Latest patched 1.x. |
| `@solana/wallet-adapter-*` | `0.9.27`/`0.9.29` | `0.9.28`/`0.9.30` | Latest patched adapters. |
| `@walletconnect/core` | `^2.23.10` | `^2.26.0` | Latest patched 2.x. |
| `@walletconnect/sign-client` | `^2.23.10` | `^2.26.0` | Latest patched 2.x. |
| `@tanstack/react-query` | `^5.64.0` | `^5.104.3` | Latest patched 5.x. |

### Transitive dependencies forced via `pnpm.overrides`

Some findings live deep in the tree and are only reachable by forcing a patched
version with `pnpm.overrides` (see `package.json`). Except where noted, every
override stays within a range the consumer already declares, so runtime
behaviour is preserved:

| Override (package selector) | Forced to | Reported advisory |
| --------------------------------------------- | --------- | ---------------------------------------------------- |
| `axios` | `1.20.0` | Multiple GHSA advisories on the 1.x line. |
| `form-data` | `4.0.6` | GHSA-hmw2-7cc7-3qxx. |
| `hono` | `4.13.13` | Multiple GHSA advisories on the 4.x line. |
| `socket.io-parser` | `4.2.7` | GHSA-2m8v-j782-fhvr. |
| `browserslist` | `4.29.3` | GHSA-c83g-rgw3-j3cx, GHSA-73wf-gq98-2v4g. |
| `metro>image-size` | `2.0.4` | GHSA-5p2g-fcmc-qvqq, GHSA-w3rx-r6r6-pgpr. |
| `shell-quote` | `1.12.0` | GHSA-pqg4-j6r4-53mv (critical), GHSA-395f-4hp3-45gv. |
| `@stellar/stellar-sdk>toml` | `4.2.0` | GHSA-82x6-q7mm-w9cf, GHSA-v5mp-jgw5-2x6j. |
| `engine.io-client>ws`, `ethers>ws`, `viem>ws` | `8.22.0` | GHSA-96hv-2xvq-fx4p, GHSA-58qx-3vcg-4xpx. |

Overrides that cross a declared major range are scoped to the single path that
needs them with pnpm's `parent>child` syntax, so they cannot silently change a
different consumer:

- **`ws` stays on the major each consumer declares.** The `^7` consumers
(`jayson`, `metro`, `react-native`, `@react-native/dev-middleware`,
`react-devtools-core`, `@walletconnect/jsonrpc-ws-connection`) resolve to the
patched `7.5.13` from their own range. Only the exact/tilde-pinned `ws` 8
consumers (`engine.io-client`, `ethers`, `viem`) are bumped, within major 8.
- **`image-size` and `toml` have no patched release inside the major the
consumer declares** (`image-size` 1.x, `toml` 3.x), so those two overrides do
cross a major. They are scoped to `metro` and `@stellar/stellar-sdk`
respectively, and `scripts/dependency-compatibility.test.mjs` exercises each
affected path so the bump cannot regress silently.

The `toml` override is safe because `@stellar/stellar-sdk` only uses `toml`
inside `StellarToml.Resolver`, which the demo never calls (`@stellar/stellar-sdk`
is used for `rpc`, `xdr`, `Address` and transaction building only). The
compatibility test asserts that the installed `toml` still parses a valid
`stellar.toml` and still throws on malformed input. Likewise,
`scripts/dependency-compatibility.test.mjs` sizes a real PNG through the
`image-size` version `metro` resolves, and confirms the `ws` 7 consumers still
receive a constructable `ws` 7 module.

## Accepted findings

Everything below is transitive, has no patched release inside the range the
consumer declares, and would require a breaking major bump of a package we do
not control. All are accepted with an owner and an expiry; re-check on or before
the expiry.

| Package | Severity | Advisory | Dependency path | Owner | Expiry |
| ---------------------- | -------- | ------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------ | ---------- |
| `braces` | high | GHSA-vfj7-8cjw-p6xm | `@solana/wallet-adapter-react` → `@solana-mobile/wallet-adapter-mobile` → `react-native` → `metro` → `micromatch` | `@wraith-protocol/maintainers` | 2027-01-15 |
| `uuid` | moderate | GHSA-w5hq-g745-h8pq | `@solana/web3.js` → `jayson`/`rpc-websockets`; `wagmi` → `@metamask/utils` | `@wraith-protocol/maintainers` | 2027-01-15 |
| `valibot` | moderate | GHSA-5qjj-4xww-7phc | `@ckb-ccc/connector-react` → `@ckb-ccc/connector` → `@ckb-ccc/ccc` → `@ckb-ccc/xverse` | `@wraith-protocol/maintainers` | 2027-01-15 |
| `decode-uri-component` | moderate | GHSA-vcc3-ghjq-m6fr | `wagmi` → `@walletconnect/ethereum-provider` → `@reown/appkit` → `query-string` | `@wraith-protocol/maintainers` | 2027-01-15 |
| `stream-json` | moderate | GHSA-528h-pc64-c93x, GHSA-hqr4-qq8f-hg3x, GHSA-mjw6-4jj6-33hc | `@solana/web3.js` → `jayson` → `stream-json` | `@wraith-protocol/maintainers` | 2027-01-15 |
| `elliptic` | low | GHSA-848j-6mx2-7j84 | `@ckb-ccc/ccc` → `@ckb-ccc/eip6963` → `@joyid/ckb` → `@nervosnetwork/ckb-sdk-utils` | `@wraith-protocol/maintainers` | 2027-01-15 |
| `@babel/core` | low | GHSA-4x5r-pxfx-6jf8 | `@solana/wallet-adapter-react` → `react-native` → `@react-native/codegen` | `@wraith-protocol/maintainers` | 2027-01-15 |

Notes:

- `braces` is reached only through the React Native/Metro tooling pulled in by
the Solana Mobile wallet adapter; it is build-time code and is not part of the
browser bundle shipped by `vite build`.
- `braces` is currently unfixable: the latest published release (`3.0.3`) is the
affected version. This entry should be removed as soon as a patched release is
published.

## CI gate

`.github/workflows/ci.yml` runs `pnpm audit:gate` on every push and pull request
to `main`/`develop`. The gate lives in `scripts/audit-gate.mjs` and enforces the
baseline:

- **critical ≤ 0** — a new critical finding always fails CI.
- **high ≤ 1** — the single accepted `braces` finding above.

Raising either baseline requires a matching entry (owner + expiry) in the table
above.

The gate also fails when `pnpm audit` itself does not succeed: a registry error,
non-JSON output, or a payload without `metadata.vulnerabilities` is treated as
a failing audit rather than an audit with no findings.

## Review cadence

Re-run `pnpm audit:prod` on or before each expiry date, upgrade where a patched
release now exists, and remove the corresponding override/exception.
45 changes: 31 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"preview": "vite preview",
"format": "prettier --write .",
"format:check": "prettier --check .",
"audit:prod": "pnpm audit --prod",
"audit:gate": "node scripts/audit-gate.mjs",
"prepare": "husky",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
Expand All @@ -22,22 +24,22 @@
},
"dependencies": {
"@albedo-link/intent": "^0.13.0",
"@ckb-ccc/ccc": "^1.1.25",
"@ckb-ccc/ccc": "^1.3.6",
"@ckb-ccc/connector-react": "^1.0.34",
"@noble/curves": "^1.9.7",
"@noble/hashes": "^1.8.0",
"@rainbow-me/rainbowkit": "^2.2.0",
"@solana/wallet-adapter-base": "^0.9.27",
"@solana/wallet-adapter-phantom": "^0.9.29",
"@solana/wallet-adapter-react": "^0.15.39",
"@solana/wallet-adapter-react-ui": "^0.9.39",
"@solana/web3.js": "^1.98.4",
"@rainbow-me/rainbowkit": "^2.2.11",
"@solana/wallet-adapter-base": "^0.9.28",
"@solana/wallet-adapter-phantom": "^0.9.30",
"@solana/wallet-adapter-react": "^0.15.40",
"@solana/wallet-adapter-react-ui": "^0.9.40",
"@solana/web3.js": "^1.99.0",
"@stellar/freighter-api": "^3.1.0",
"@stellar/stellar-sdk": "^13.1.0",
"@tanstack/react-query": "^5.64.0",
"@stellar/stellar-sdk": "^13.3.0",
"@tanstack/react-query": "^5.104.3",
"@tanstack/react-virtual": "^3.0.0",
"@walletconnect/core": "^2.23.10",
"@walletconnect/sign-client": "^2.23.10",
"@walletconnect/core": "^2.26.0",
"@walletconnect/sign-client": "^2.26.0",
"@wraith-protocol/sdk": "^1.4.5",
"@zxing/browser": "0.0.7",
"bs58": "^6.0.0",
Expand All @@ -50,9 +52,9 @@
"react-dom": "^19.0.0",
"react-i18next": "^15.5.1",
"react-qr-reader": "3.0.0-beta-1",
"react-router-dom": "^7.6.0",
"viem": "^2.23.0",
"wagmi": "^2.14.0",
"react-router-dom": "^7.18.4",
"viem": "^2.57.7",
"wagmi": "^2.19.5",
"workbox-cacheable-response": "^7.4.1",
"workbox-core": "^7.4.1",
"workbox-expiration": "^7.4.1",
Expand Down Expand Up @@ -116,5 +118,20 @@
"public"
]
},
"pnpm": {
"overrides": {
"axios": "1.20.0",
"browserslist": "4.29.3",
"form-data": "4.0.6",
"hono": "4.13.13",
"metro>image-size": "2.0.4",
"shell-quote": "1.12.0",
"socket.io-parser": "4.2.7",
"@stellar/stellar-sdk>toml": "4.2.0",
"engine.io-client>ws": "8.22.0",
"ethers>ws": "8.22.0",
"viem>ws": "8.22.0"
}
},
"packageManager": "pnpm@10.28.2"
}
Loading
Loading