Skip to content

Commit b0416d3

Browse files
authored
Merge pull request #105 from jplomas/main
Audit remediations
2 parents 2ac6be6 + 31d1048 commit b0416d3

176 files changed

Lines changed: 9467 additions & 1985 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ on:
1111
permissions:
1212
contents: read
1313

14+
# Single source of truth for jobs that pin one Node version. Referenced by both
15+
# the `setup-node` input and the step name so the two can never drift apart.
16+
env:
17+
NODE_VERSION: 22.x
18+
1419
concurrency:
1520
group: ${{ github.workflow }}-${{ github.ref }}
1621
cancel-in-progress: true
@@ -22,6 +27,11 @@ jobs:
2227
strategy:
2328
fail-fast: false
2429
matrix:
30+
# The advertised floor (engines.node: >=20.19.0) is NOT exercised here: the dev
31+
# toolchain cannot install on it (.npmrc sets engine-strict=true and
32+
# semantic-release requires ^22.14.0 || >=24.10.0). The floor is proven instead
33+
# where it actually matters — against published tarballs, on 20.19.0, in
34+
# release.yml's smoke job — plus scripts/doctor.js and the package_engines test.
2535
node-version: [22.x, 24.x]
2636

2737
steps:
@@ -47,11 +57,15 @@ jobs:
4757
- name: Check repository hygiene
4858
run: pnpm run hygiene
4959

60+
# Build before lint: type-aware ESLint rules need built cross-package types to resolve.
61+
- name: Build packages
62+
run: pnpm run build
63+
5064
- name: Lint workspace
5165
run: pnpm run lint
5266

53-
- name: Build packages
54-
run: pnpm run build
67+
- name: Enforce lint warning budget
68+
run: pnpm run lint:budget
5569

5670
- name: Build browser bundle
5771
run: pnpm run build:web
@@ -71,9 +85,15 @@ jobs:
7185
# `pnpm audit` calls npm's retired /security/audits endpoint (HTTP 410), so
7286
# scan the lockfile with Trivy instead, failing on HIGH/CRITICAL. Accepted
7387
# advisories are documented in .trivyignore.
88+
#
89+
# `version` is pinned rather than left to the action's default so this leg
90+
# and `pnpm run audit:supply-chain` (which runs the same Trivy release from
91+
# a digest-pinned image) cannot drift apart. scripts/doctor.js fails if the
92+
# two stop agreeing; bump both together.
7493
- name: Trivy vulnerability scan (high/critical)
7594
uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
7695
with:
96+
version: v0.70.0
7797
scan-type: fs
7898
scan-ref: pnpm-lock.yaml
7999
scanners: vuln
@@ -90,10 +110,10 @@ jobs:
90110
with:
91111
persist-credentials: false
92112

93-
- name: Use Node.js 20.x
113+
- name: Use Node.js ${{ env.NODE_VERSION }}
94114
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
95115
with:
96-
node-version: 22.x
116+
node-version: ${{ env.NODE_VERSION }}
97117

98118
- name: Enable pnpm
99119
run: corepack enable
@@ -127,10 +147,10 @@ jobs:
127147
with:
128148
persist-credentials: false
129149

130-
- name: Use Node.js 20.x
150+
- name: Use Node.js ${{ env.NODE_VERSION }}
131151
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
132152
with:
133-
node-version: 22.x
153+
node-version: ${{ env.NODE_VERSION }}
134154

135155
- name: Enable pnpm
136156
run: corepack enable
@@ -176,10 +196,10 @@ jobs:
176196
with:
177197
persist-credentials: false
178198

179-
- name: Use Node.js 20.x
199+
- name: Use Node.js ${{ env.NODE_VERSION }}
180200
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
181201
with:
182-
node-version: 22.x
202+
node-version: ${{ env.NODE_VERSION }}
183203

184204
- name: Enable pnpm
185205
run: corepack enable

.github/workflows/release.yml

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ concurrency:
1212
permissions:
1313
contents: read
1414

15+
# The exact floor advertised by every published manifest (engines.node). The
16+
# tarball smoke runs on this precise version so the advertised floor is proven
17+
# rather than assumed. Keep in step with the root package.json engines.node.
18+
env:
19+
NODE_FLOOR_VERSION: 20.19.0
20+
1521
jobs:
1622
ci:
1723
name: CI
@@ -116,8 +122,8 @@ jobs:
116122
dist/checksums-sha512.txt
117123
retention-days: 5
118124

119-
smoke-node20:
120-
name: Smoke Tarballs (Node 20)
125+
smoke-node-floor:
126+
name: Smoke Tarballs (advertised Node floor)
121127
needs: prepare
122128
if: needs.prepare.outputs.released == 'true'
123129
runs-on: ubuntu-latest
@@ -127,14 +133,15 @@ jobs:
127133
with:
128134
persist-credentials: false
129135

130-
- name: Use Node.js
136+
- name: Use Node.js ${{ env.NODE_FLOOR_VERSION }}
131137
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
132138
with:
133139
# Smoke test runs `npm install` against the published tarballs in a
134140
# fresh temp dir; it never installs the monorepo's devDependencies,
135141
# so the semantic-release Node 22+ engine constraint does not apply.
136-
# Stay on Node 20 here to verify consumer-facing `engines.node: >=20`.
137-
node-version: 20.x
142+
# Pin the exact advertised floor here so consumers on the oldest
143+
# supported Node are covered, not just the newest 20.x release.
144+
node-version: ${{ env.NODE_FLOOR_VERSION }}
138145

139146
- name: Download release candidate tarballs
140147
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
@@ -147,7 +154,7 @@ jobs:
147154

148155
publish:
149156
name: Publish
150-
needs: [prepare, smoke-node20]
157+
needs: [prepare, smoke-node-floor]
151158
if: needs.prepare.outputs.released == 'true'
152159
runs-on: ubuntu-latest
153160
environment: npm-publish
@@ -171,8 +178,13 @@ jobs:
171178
registry-url: https://registry.npmjs.org/
172179

173180
- name: Use npm 11 for trusted publishing
181+
# zizmor: ignore[adhoc-packages]
182+
# npm trusted publishing (OIDC provenance) requires npm >= 11.5.1, which
183+
# is newer than the npm bundled with the pinned Node in setup-node above.
184+
# The version is pinned exactly (not @latest), so this is a deliberate,
185+
# reviewed exception rather than an unpinned ad-hoc install.
174186
run: |
175-
npm install -g npm@^11.5.1
187+
npm install -g npm@11.5.1
176188
npm --version
177189
178190
- name: Download release candidate tarballs

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
# Secrets
77
.secrets.json
88

9+
# Ephemeral TLS fixtures generated for Firefox system tests
10+
templates/cypress/.cert/
11+
*.pem
12+
913
# Logs
1014
logs
1115
*.log

.lint-budget.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"description": "Per-package ESLint warning budget enforced by scripts/lint-budget.js. A package that emits more warnings than its budget fails CI. Lower a budget with `pnpm run lint:budget -- --update` after removing warnings; never raise one to hide a new warning.",
3+
"budgets": {
4+
"packages/abi": 1865,
5+
"packages/web3": 869,
6+
"packages/web3-core": 1056,
7+
"packages/web3-errors": 224,
8+
"packages/web3-net": 108,
9+
"packages/web3-providers-http": 339,
10+
"packages/web3-providers-ipc": 235,
11+
"packages/web3-providers-ws": 296,
12+
"packages/web3-qrl": 2622,
13+
"packages/web3-qrl-abi": 202,
14+
"packages/web3-qrl-accounts": 1557,
15+
"packages/web3-qrl-contract": 1391,
16+
"packages/web3-qrl-iban": 91,
17+
"packages/web3-qrl-qrns": 549,
18+
"packages/web3-rpc-methods": 476,
19+
"packages/web3-types": 62,
20+
"packages/web3-utils": 1293,
21+
"packages/web3-validator": 882,
22+
"tools/eslint-config-base-web3": 0,
23+
"tools/web3-packagetemplate": 7,
24+
"tools/web3-plugin-example": 40
25+
}
26+
}

.trivyignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,11 @@
1+
# Accepted vulnerability advisories for `pnpm run audit:supply-chain` and the
2+
# matching CI scan in .github/workflows/ci.yml.
3+
#
4+
# Both scans read this file, so an entry here suppresses the finding locally and
5+
# in CI alike. Add one only with a maintainer's reason for why the advisory is
6+
# unreachable and a time-bounded follow-up, per docs/supply-chain-security.md.
7+
# One advisory ID per line, e.g.:
8+
#
9+
# CVE-0000-00000 # unreachable: <reason>; revisit by <date>
10+
111
# (none)

RELEASE.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Release publishing is guarded by:
1414
- The repository variable `RELEASES_ENABLED` must be set to `true`.
1515
- The protected GitHub environment `npm-publish` must approve the job.
1616
- The CI workflow must pass first.
17-
- Release tarballs must pass CommonJS and ESM smoke tests on Node 20 before publish.
17+
- Release tarballs must pass CommonJS and ESM smoke tests on Node 20.19.0 (the advertised `engines.node` floor) before publish.
1818
- npm publishing must run from Node 22.14 or newer with npm 11.5.1 or newer so npm trusted publishing can use OIDC.
1919
- npm publishing must use provenance (`NPM_CONFIG_PROVENANCE=true`).
2020

@@ -81,7 +81,7 @@ The release workflow:
8181
8. Packs released packages.
8282
9. Generates SHA-256 and SHA-512 checksums.
8383
10. Uploads tarballs and release metadata as a short-lived workflow artefact.
84-
11. Downloads the tarballs in a Node 20 job and smoke-tests CommonJS `require()` and ESM `import`.
84+
11. Downloads the tarballs in a Node 20.19.0 job (the advertised floor) and smoke-tests CommonJS `require()` and ESM `import`.
8585
12. Publishes the already-tested tarballs from a Node 22.14/npm 11 job using npm trusted publishing.
8686
13. Generates SPDX and CycloneDX SBOMs.
8787
14. Creates GitHub attestations for release artefacts and SBOMs.

SECURITY.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,45 @@ Please include as much of the following as possible:
3131
The QRL team will triage private reports, coordinate fixes, and publish
3232
security advisories or release notes when appropriate.
3333

34+
## Key Material & Memory Security
35+
36+
The secret protected by this library is the wallet **seed** (and the extended seed / mnemonic that
37+
contain it). Anyone with the seed can derive the ML-DSA-87 secret key and sign arbitrary
38+
transactions, so it is critical material.
39+
40+
**JavaScript does not provide guaranteed secure memory handling.** Unlike languages that expose raw
41+
buffers, a JS runtime may keep copies of a secret that the application cannot reach or erase — the
42+
garbage collector may relocate or retain copies, the JIT may materialise additional copies, and the
43+
value may reach swap or a core dump. This is a platform limitation, not a defect in this library, and
44+
it is the same constraint documented for [`@theqrl/wallet.js`](https://github.com/theQRL/wallet.js)
45+
(see its `SECURITY.md` "Memory Security").
46+
47+
**Seeds are held as immutable strings.** `web3.qrl.accounts` exposes the seed as a hex string
48+
(`account.seed`). A JavaScript string is immutable, so — unlike a `Uint8Array` — it **cannot be
49+
zeroized/overwritten** after use. Best-effort scrubbing therefore does not apply to the seed as this
50+
API exposes it; treat any seed string as unrecoverably resident in memory for the process lifetime.
51+
52+
What this library **does** do to reduce accidental exposure:
53+
54+
- `account.seed` is defined **non-enumerable**, so it does not appear in a spread, `Object.keys`, or
55+
a naive `for…in`.
56+
- `account.toJSON()` **redacts** the seed (`'<redacted>'`), so `JSON.stringify(account)` and most
57+
loggers will not serialise it.
58+
59+
Recommendations for applications handling seeds:
60+
61+
- **Minimise seed lifetime.** Derive what you need and drop references promptly; do not hold seeds in
62+
long-lived state, closures, or global caches.
63+
- **Never log or serialise raw seeds.** Rely on the redaction above rather than passing the seed to a
64+
logger, telemetry, or error report yourself.
65+
- **Prefer the encrypted keystore at rest.** Store seeds as an encrypted V1 keystore (Argon2id +
66+
AES-256-GCM), never as plaintext.
67+
- **For tighter in-memory control**, operate on the lower-level `Uint8Array`-backed primitives in
68+
[`@theqrl/wallet.js`](https://github.com/theQRL/wallet.js) and call its `zeroize()` when done —
69+
understanding that this is **best-effort** protection, not a cryptographic guarantee.
70+
- **For high-value use**, consider a hardware security module, a dedicated signer process, or a
71+
worker with a constrained lifetime rather than holding seeds in a general-purpose runtime.
72+
3473
## Release Verification
3574

3675
After the first audited release, published packages are expected to be verifiable

docs/audit-evidence.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ corepack enable
2121

2222
Expected result:
2323

24-
- Node is within the supported `>=20` range.
24+
- Node is within the supported `>=20.19.0` range.
2525
- pnpm is the Corepack-managed pnpm 10 line used by the lockfile.
2626
- `corepack enable` exits `0`.
2727

@@ -51,10 +51,16 @@ Expected result:
5151

5252
Required GitHub status checks should match the commands above:
5353

54-
- `Verify (Node 20.x)`, `Verify (Node 22.x)`, and `Verify (Node 24.x)` run
55-
frozen ignored-script install, doctor, hygiene, lint, build, browser build,
56-
and unit tests.
57-
- `Supply Chain Audit` runs the production high/critical audit.
54+
- `Verify (Node 22.x)` and `Verify (Node 24.x)` run frozen ignored-script
55+
install, doctor, hygiene, lint, build, browser build, and unit tests. The
56+
advertised `engines.node` floor (`>=20.19.0`) is deliberately not exercised
57+
here — the dev toolchain cannot install on it (`.npmrc` sets
58+
`engine-strict=true`; `semantic-release` requires `^22.14.0 || >=24.10.0`).
59+
The floor is proven against published tarballs on 20.19.0 by the release
60+
smoke job, plus `scripts/doctor.js` and the `package_engines` metadata test.
61+
- `Supply Chain Audit` runs the pinned Trivy 0.70.0 lockfile scan, failing on
62+
HIGH/CRITICAL. Reproduce locally with `pnpm run audit:supply-chain` (same
63+
version, from a digest-pinned image).
5864
- `Package Inspection` builds, inspects, packs, and smoke-tests release
5965
tarballs.
6066
- `Unit Coverage` runs unit coverage and uploads configured JSON artefacts to

docs/dependency-warning-triage.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,21 @@ not block a release solely because a known warning below is still present.
2121
- Migrated linting to ESLint 10 flat config and removed the deprecated
2222
`eslint-plugin-deprecation` rule because its latest release does not declare
2323
compatibility with ESLint 10.
24-
- The npm registry's latest ESLint release observed during the migration was
25-
`10.3.0`, but it was published on 2026-05-01 and is inside the seven-day
26-
`minimumReleaseAge` quarantine. The lockfile therefore uses the newest
27-
policy-eligible ESLint release, `10.2.1`.
24+
- ESLint tracks the newest release that is outside the seven-day
25+
`minimumReleaseAge` quarantine rather than the newest release on the registry.
26+
The pinned version lives in the workspace manifests (root `package.json` and
27+
each package's `devDependencies`), which are the source of truth; it is not
28+
restated here because it moves every time the quarantine window clears.
2829

2930
## Accepted For The Current Lockfile
3031

3132
- Some release-tool transitive warnings are inherited from
3233
`multi-semantic-release`, `semantic-release`, conventional-changelog tooling,
33-
and the current docs/build stack. They remain acceptable while the production
34-
high/critical audit gate is green.
35-
- `@typescript-eslint` is locked at `8.59.0` and `globals` at `17.5.0` for the
36-
same reason: the newest registry releases observed on 2026-05-01 were still
37-
inside the seven-day quarantine window.
34+
and the current docs/build stack. They remain acceptable while the
35+
high/critical audit gate (`pnpm run audit:supply-chain`) is green.
36+
- `@typescript-eslint` and `globals` follow the same rule as ESLint above: they
37+
track the newest release outside the quarantine window, and the manifests —
38+
not this document — record which version that currently is.
3839
- ESLint 10 reports additional type-safety and stale-disable warnings. These
3940
are tracked as cleanup debt, not release blockers for this tooling migration,
4041
because `pnpm run lint` passes with zero errors across all package/tool

docs/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
]
5050
},
5151
"engines": {
52-
"node": ">=20"
52+
"node": ">=20.19.0"
5353
},
5454
"packageManager": "pnpm@10.27.0"
5555
}

0 commit comments

Comments
 (0)