Skip to content

Commit 6870c90

Browse files
authored
feat(security): Phase 3 audit — TS-boundary validation + WebCrypto hardening (#985)
1 parent e1b306a commit 6870c90

29 files changed

Lines changed: 1168 additions & 113 deletions

.github/workflows/deploy-docs.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@ concurrency:
1717
group: 'pages'
1818
cancel-in-progress: true
1919

20+
env:
21+
# Opt actions still on Node 20 into the runner's Node 24 instead.
22+
# https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
23+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
24+
2025
jobs:
2126
build:
2227
runs-on: ubuntu-latest
2328
steps:
2429
- name: Checkout
25-
uses: actions/checkout@v4
30+
uses: actions/checkout@v5
2631

2732
- name: Setup Bun
2833
uses: ./.github/actions/setup-bun

.github/workflows/e2e-android-test.yml

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ on:
2525

2626
env:
2727
EMULATOR_API_LEVEL: 34
28+
# Opt actions still on Node 20 (setup-java, upload/download-artifact, setup-android, etc.)
29+
# into the runner's Node 24 instead. Silences deprecation warnings until each action ships a v5.
30+
# https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
31+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
2832

2933
jobs:
3034
# ============================================================================
@@ -51,7 +55,7 @@ jobs:
5155
- name: Setup Node.js
5256
uses: actions/setup-node@v5
5357
with:
54-
node-version: '20'
58+
node-version: '24'
5559

5660
- name: Install Bun
5761
uses: ./.github/actions/setup-bun
@@ -87,7 +91,7 @@ jobs:
8791
packages/react-native-quick-crypto/android/build
8892
node_modules/.bun/react-native-nitro-modules*/node_modules/react-native-nitro-modules/android/.cxx
8993
node_modules/.bun/react-native-nitro-modules*/node_modules/react-native-nitro-modules/android/build
90-
key: ${{ runner.os }}-gradle-${{ github.run_id }}
94+
key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/**/*.gradle*', 'example/android/gradle.properties', 'example/android/gradle/wrapper/gradle-wrapper.properties', 'packages/react-native-quick-crypto/android/build.gradle', 'packages/react-native-quick-crypto/android/gradle.properties', 'bun.lock') }}
9195
restore-keys: |
9296
${{ runner.os }}-gradle-
9397
@@ -141,7 +145,7 @@ jobs:
141145
packages/react-native-quick-crypto/android/build
142146
node_modules/.bun/react-native-nitro-modules*/node_modules/react-native-nitro-modules/android/.cxx
143147
node_modules/.bun/react-native-nitro-modules*/node_modules/react-native-nitro-modules/android/build
144-
key: ${{ runner.os }}-gradle-${{ github.run_id }}
148+
key: ${{ runner.os }}-gradle-${{ hashFiles('example/android/**/*.gradle*', 'example/android/gradle.properties', 'example/android/gradle/wrapper/gradle-wrapper.properties', 'packages/react-native-quick-crypto/android/build.gradle', 'packages/react-native-quick-crypto/android/gradle.properties', 'bun.lock') }}
145149

146150
# ============================================================================
147151
# AVD Job - Create and cache emulator snapshot (runs in parallel with build)
@@ -167,9 +171,7 @@ jobs:
167171
path: |
168172
~/.android/avd/*
169173
~/.android/adb*
170-
key: avd-pixel7pro-${{ env.EMULATOR_API_LEVEL }}-${{ github.run_id }}
171-
restore-keys: |
172-
avd-pixel7pro-${{ env.EMULATOR_API_LEVEL }}-
174+
key: avd-pixel7pro-${{ env.EMULATOR_API_LEVEL }}-x86_64-v1
173175

174176
- name: Create AVD and Generate Snapshot for Caching
175177
if: steps.avd-cache.outputs.cache-hit != 'true'
@@ -190,7 +192,7 @@ jobs:
190192
path: |
191193
~/.android/avd/*
192194
~/.android/adb*
193-
key: avd-pixel7pro-${{ env.EMULATOR_API_LEVEL }}-${{ github.run_id }}
195+
key: avd-pixel7pro-${{ env.EMULATOR_API_LEVEL }}-x86_64-v1
194196

195197
# ============================================================================
196198
# Test Job - Run E2E tests (needs both build and AVD)
@@ -209,7 +211,7 @@ jobs:
209211
- name: Setup Node.js
210212
uses: actions/setup-node@v5
211213
with:
212-
node-version: '20'
214+
node-version: '24'
213215

214216
- name: Install Bun
215217
uses: ./.github/actions/setup-bun
@@ -226,7 +228,7 @@ jobs:
226228
uses: actions/cache/restore@v5
227229
with:
228230
path: node_modules
229-
key: ${{ runner.os }}-node-modules-${{ github.run_id }}
231+
key: ${{ runner.os }}-node-modules-${{ hashFiles('bun.lock') }}
230232
restore-keys: |
231233
${{ runner.os }}-node-modules-
232234
@@ -237,7 +239,7 @@ jobs:
237239
uses: actions/cache/save@v5
238240
with:
239241
path: node_modules
240-
key: ${{ runner.os }}-node-modules-${{ github.run_id }}
242+
key: ${{ runner.os }}-node-modules-${{ hashFiles('bun.lock') }}
241243

242244
- name: Download APK
243245
uses: actions/download-artifact@v4
@@ -263,9 +265,7 @@ jobs:
263265
path: |
264266
~/.android/avd/*
265267
~/.android/adb*
266-
key: avd-pixel7pro-${{ env.EMULATOR_API_LEVEL }}-${{ github.run_id }}
267-
restore-keys: |
268-
avd-pixel7pro-${{ env.EMULATOR_API_LEVEL }}-
268+
key: avd-pixel7pro-${{ env.EMULATOR_API_LEVEL }}-x86_64-v1
269269

270270
- name: Run E2E Tests
271271
uses: reactivecircus/android-emulator-runner@v2

.github/workflows/e2e-ios-test.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ on:
2323
- 'packages/react-native-quick-crypto/src/**'
2424
- 'packages/react-native-quick-crypto/ios/**'
2525

26+
env:
27+
# Opt actions still on Node 20 (upload-artifact, McCzarny/upload-image, peter-evans/*, etc.)
28+
# into the runner's Node 24 instead. Silences deprecation warnings until each action ships a v5.
29+
# https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
30+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
31+
2632
jobs:
2733
e2e-tests-ios:
2834
runs-on: macOS-26
@@ -34,7 +40,7 @@ jobs:
3440

3541
steps:
3642
- name: Checkout
37-
uses: actions/checkout@v4
43+
uses: actions/checkout@v5
3844

3945
- name: Select Xcode 26.2
4046
run: |

.github/workflows/release.yml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@ on:
1212
type: boolean
1313
default: false
1414

15+
env:
16+
# Opt actions still on Node 20 into the runner's Node 24 instead.
17+
# https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
18+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
19+
1520
jobs:
1621
release:
1722
runs-on: macos-latest
@@ -20,7 +25,7 @@ jobs:
2025
id-token: write
2126
steps:
2227
- name: Checkout
23-
uses: actions/checkout@v4
28+
uses: actions/checkout@v5
2429
with:
2530
fetch-depth: 0
2631
token: ${{ secrets.GITHUB_TOKEN }}
@@ -29,7 +34,7 @@ jobs:
2934
uses: ./.github/actions/setup-bun
3035

3136
- name: Setup Node.js (for npm publish with OIDC)
32-
uses: actions/setup-node@v4
37+
uses: actions/setup-node@v5
3338
with:
3439
node-version: '24'
3540
registry-url: 'https://registry.npmjs.org'

.github/workflows/update-lockfiles.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,18 @@ on:
1515
permissions:
1616
contents: write
1717

18+
env:
19+
# Opt actions still on Node 20 into the runner's Node 24 instead.
20+
# https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
21+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
22+
1823
jobs:
1924
update-lockfiles:
2025
name: 'Update lockfiles (Podfile.lock)'
2126
if: github.actor == 'dependabot[bot]'
2227
runs-on: macOS-latest
2328
steps:
24-
- uses: actions/checkout@v4
29+
- uses: actions/checkout@v5
2530
with:
2631
fetch-depth: 0
2732
ref: ${{ github.event.pull_request.head.ref }}

.github/workflows/validate-cpp.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,18 @@ on:
1616
- 'packages/react-native-quick-crypto/cpp/**'
1717
- 'packages/react-native-quick-crypto/nitrogen/generated/shared/**'
1818

19+
env:
20+
# Opt actions still on Node 20 (e.g. reviewdog/action-cpplint) into the runner's Node 24.
21+
# Silences deprecation warnings until each action ships a v5.
22+
# https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
23+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
24+
1925
jobs:
2026
validate_cpp:
2127
name: C++ Lint
2228
runs-on: ubuntu-latest
2329
steps:
24-
- uses: actions/checkout@v4
30+
- uses: actions/checkout@v5
2531
- name: Set up clang-format
2632
run: sudo apt-get install -y clang-format
2733
- name: Run clang-format check

.github/workflows/validate-js.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,18 @@ on:
3030
- 'example/*.*sx'
3131
- 'example/bun.lock'
3232

33+
env:
34+
# Opt actions still on Node 20 (e.g. reviewdog/action-setup) into the runner's Node 24.
35+
# Silences deprecation warnings until each action ships a v5.
36+
# https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/
37+
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: 'true'
38+
3339
jobs:
3440
compile_js:
3541
name: Compile JS (tsc)
3642
runs-on: ubuntu-latest
3743
steps:
38-
- uses: actions/checkout@v4
44+
- uses: actions/checkout@v5
3945

4046
- uses: ./.github/actions/setup-bun
4147

@@ -63,7 +69,7 @@ jobs:
6369
runs-on: ubuntu-latest
6470
steps:
6571
- name: Checkout
66-
uses: actions/checkout@v4
72+
uses: actions/checkout@v5
6773

6874
- name: Setup Bun
6975
uses: ./.github/actions/setup-bun

example/src/tests/argon2/argon2_tests.ts

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,13 @@ test(SUITE, 'argon2Sync: deterministic with same inputs', () => {
137137
);
138138
});
139139

140-
// --- Numeric parameter validation (Phase 1.1: validateUInt) ---
140+
// --- Numeric parameter validation (Phase 1.1: validateUInt + Phase 3.2 RFC 9106) ---
141141
//
142142
// `static_cast<uint32_t>(NaN | +/-Infinity | -1)` is undefined behavior in
143-
// C++. The C++ layer used to do these casts naked; the validateUInt helper
144-
// now rejects them with a descriptive error before the cast.
143+
// C++. The C++ layer's validateUInt helper used to be the first line of
144+
// defense; Phase 3.2 added a TS-side RFC 9106 §3.1 check that fires
145+
// earlier and produces a clearer message. The regex below matches the
146+
// new RFC 9106 wording.
145147

146148
const baseParams = {
147149
message: Buffer.from('password'),
@@ -173,20 +175,20 @@ test(SUITE, 'argon2Sync: rejects -Infinity passes', () => {
173175
test(SUITE, 'argon2Sync: rejects negative tagLength', () => {
174176
assert.throws(() => {
175177
argon2Sync('argon2id', { ...baseParams, tagLength: -1 });
176-
}, /tagLength.*non-negative/i);
178+
}, /Invalid Argon2 tagLength: -1/);
177179
});
178180

179181
test(SUITE, 'argon2Sync: rejects fractional passes', () => {
180182
assert.throws(() => {
181183
argon2Sync('argon2id', { ...baseParams, passes: 3.5 });
182-
}, /passes.*integer/i);
184+
}, /Invalid Argon2 passes: 3\.5/);
183185
});
184186

185187
test(SUITE, 'argon2Sync: rejects out-of-range memory', () => {
186188
// memory is uint32_t — anything beyond UINT32_MAX must be rejected.
187189
assert.throws(() => {
188190
argon2Sync('argon2id', { ...baseParams, memory: 2 ** 32 });
189-
}, /memory.*out of range/i);
191+
}, /Invalid Argon2 memory: 4294967296/);
190192
});
191193

192194
test(SUITE, 'argon2: async path also rejects NaN parallelism', () => {
@@ -202,3 +204,49 @@ test(SUITE, 'argon2: async path also rejects NaN parallelism', () => {
202204
});
203205
});
204206
});
207+
208+
// --- RFC 9106 §3.1 minimum-bound validation (Phase 3.2) ---
209+
210+
test(SUITE, 'argon2Sync: rejects parallelism = 0 (RFC 9106 mins)', () => {
211+
assert.throws(() => {
212+
argon2Sync('argon2id', { ...baseParams, parallelism: 0 });
213+
}, /parallelism: 0/);
214+
});
215+
216+
test(SUITE, 'argon2Sync: rejects tagLength < 4 (RFC 9106 mins)', () => {
217+
assert.throws(() => {
218+
argon2Sync('argon2id', { ...baseParams, tagLength: 3 });
219+
}, /tagLength: 3/);
220+
});
221+
222+
test(SUITE, 'argon2Sync: rejects passes = 0 (RFC 9106 mins)', () => {
223+
assert.throws(() => {
224+
argon2Sync('argon2id', { ...baseParams, passes: 0 });
225+
}, /passes: 0/);
226+
});
227+
228+
test(SUITE, 'argon2Sync: rejects memory < 8 * parallelism (RFC 9106)', () => {
229+
// p=4 ⇒ memory must be ≥ 32 KiB; 16 KiB must be rejected.
230+
assert.throws(() => {
231+
argon2Sync('argon2id', {
232+
...baseParams,
233+
parallelism: 4,
234+
memory: 16,
235+
});
236+
}, /memory: 16/);
237+
});
238+
239+
test(SUITE, 'argon2Sync: rejects nonce shorter than 8 bytes (RFC 9106)', () => {
240+
assert.throws(() => {
241+
argon2Sync('argon2id', {
242+
...baseParams,
243+
nonce: Buffer.from('1234567'), // 7 bytes
244+
});
245+
}, /nonce length: 7/);
246+
});
247+
248+
test(SUITE, 'argon2Sync: rejects unsupported version', () => {
249+
assert.throws(() => {
250+
argon2Sync('argon2id', { ...baseParams, version: 0x42 });
251+
}, /Invalid Argon2 version/);
252+
});

0 commit comments

Comments
 (0)