Skip to content

Commit 6b591f6

Browse files
Fix/vulnerabilities (#36)
* security: fix critical timing attack vulnerabilities and update dependencies Fix three critical timing attack vulnerabilities in CSRF token validation that could allow attackers to reconstruct valid tokens through timing analysis. Replace non-constant-time string comparisons with timingSafeEqual() to prevent timing side-channel attacks. Additionally, fix weak default secret generation and update all vulnerable dependencies to their patched versions. Security Fixes: Fix timing attack in validateDoubleSubmit token comparison Fix timing attack in validateSignedDoubleSubmit cookie integrity check Fix timing attack in validateSignedDoubleSubmit token matching Fix weak secret generation using proper base64 encoding instead of comma-separated decimals Dependency Updates: Update qs to >=6.14.1 via pnpm override (fixes CVE-2025-15284 - DoS vulnerability) Update tsdown to 0.20.1 (fixes CVE-2026-24001 in diff dependency) Update @changesets/cli to 2.29.8 (fixes js-yaml vulnerability) Update next to 15.4.10+ (fixes multiple CVEs: 1112593, 1112638, 1112649) Update @biomejs/biome to 2.3.13 Update @types/node to 25.1.0 Update vitest and related packages to 4.0.18 Update typescript to 5.9.3 Update jsdom to 27.4.0 * chore: add changeset * chore: fix changeset CVE notation and align @types/node with engine requirement Update Next.js changeset entry to use proper CVE-YYYY-NNNNN format (CVE-2025-59471, CVE-2025-59472, CVE-2026-23864) Downgrade @types/node from ^25.1.0 to ^20.0.0 to match Node >=18.0.0 engine requirement Resolves peer dependency warnings and prevents type/runtime divergence * Update .changeset/critical-security-fixes.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Muneeb Samuels <muneebs@users.noreply.github.com> * Update .changeset/critical-security-fixes.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Muneeb Samuels <muneebs@users.noreply.github.com> * Update .changeset/critical-security-fixes.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Signed-off-by: Muneeb Samuels <muneebs@users.noreply.github.com> --------- Signed-off-by: Muneeb Samuels <muneebs@users.noreply.github.com> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent e4d6cb2 commit 6b591f6

File tree

9 files changed

+1144
-900
lines changed

9 files changed

+1144
-900
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
---
2+
"@csrf-armor/core": patch
3+
"@csrf-armor/express": patch
4+
"@csrf-armor/nextjs": patch
5+
---
6+
7+
## SECURITY FIXES: Critical timing attack vulnerabilities and dependency updates
8+
9+
This release addresses critical security vulnerabilities and updates all vulnerable dependencies.
10+
11+
## Critical Security Fixes
12+
13+
### Timing Attack Vulnerabilities (CRITICAL)
14+
Fixed three timing attack vulnerabilities in CSRF token validation that could allow attackers to reconstruct valid tokens through timing analysis:
15+
16+
- **validateDoubleSubmit** (validation.ts:104): Replaced non-constant-time string comparison with `timingSafeEqual()`
17+
- **validateSignedDoubleSubmit cookie check** (validation.ts:142): Fixed cookie integrity comparison to use constant-time equality
18+
- **validateSignedDoubleSubmit token matching** (validation.ts:147): Fixed token comparison to prevent timing side-channel attacks
19+
20+
These vulnerabilities could have allowed attackers to bypass CSRF protection entirely by analyzing response timing patterns. All token comparisons now use cryptographically constant-time operations.
21+
22+
### Weak Secret Generation (HIGH)
23+
Fixed default secret generation (constants.ts:146) that produced weak comma-separated decimal strings instead of proper base64-encoded secrets. Now uses `generateSecureSecret()` for high-entropy, properly-encoded secrets.
24+
25+
## Dependency Security Updates
26+
27+
All vulnerable dependencies have been updated to patched versions:
28+
29+
- **qs** (CVE-2025-15284): Updated to >=6.14.1 via pnpm override - fixes DoS vulnerability via memory exhaustion
30+
- **diff** (CVE-2026-24001): Updated to 8.0.3 via tsdown 0.20.1 - fixes denial of service vulnerability
31+
- **js-yaml**: Updated via @changesets/cli 2.29.8 - resolves YAML parsing vulnerabilities
32+
- **next** (npm advisories: 1112593, 1112638, 1112649): Updated to 16.1.6 - fixes multiple security vulnerabilities including CVE-2025-59471, CVE-2025-59472, and CVE-2026-23864
33+
34+
## Other Updates
35+
36+
- Updated `@biomejs/biome` to 2.3.13
37+
- Updated `@types/node` to 20.0.0 (fixes peer dependency warnings)
38+
- Updated vitest and related packages to 4.0.18
39+
- Updated typescript to 5.9.3
40+
- Updated jsdom to 27.4.0
41+
- Updated package exports to match new tsdown output format (.mjs files)
42+
43+
## Security Impact
44+
45+
- ✅ Zero critical vulnerabilities remaining
46+
- ✅ Zero high-severity vulnerabilities remaining
47+
- ✅ No remaining known CVEs after upgrade (verified via pnpm audit)
48+
- ✅ All 66 tests passing across all packages
49+
50+
**Upgrade Priority: CRITICAL** - All users should upgrade immediately to address timing attack vulnerabilities.

examples/express-app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
},
1010
"dependencies": {
1111
"@csrf-armor/express": "workspace:*",
12-
"express": "^4.18.2",
12+
"express": "^4.22.1",
1313
"cookie-parser": "^1.4.6"
1414
},
1515
"author": "Muneeb Samuels",

package.json

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,22 @@
3535
"pnpm": ">=8.0.0"
3636
},
3737
"devDependencies": {
38-
"@biomejs/biome": "^2.1.2",
38+
"@biomejs/biome": "^2.3.13",
3939
"@changesets/changelog-github": "^0.5.1",
40-
"@changesets/cli": "^2.29.7",
41-
"@types/node": "^16.18.126",
42-
"@vitest/coverage-v8": "^4.0.8",
43-
"@vitest/ui": "^4.0.8",
44-
"jsdom": "^26.1.0",
45-
"typescript": "^5",
46-
"vitest": "^4.0.8"
40+
"@changesets/cli": "^2.29.8",
41+
"@types/node": "^20.0.0",
42+
"@vitest/coverage-v8": "^4.0.18",
43+
"@vitest/ui": "^4.0.18",
44+
"jsdom": "^27.4.0",
45+
"typescript": "^5.9.3",
46+
"vitest": "^4.0.18"
4747
},
4848
"pnpm": {
4949
"overrides": {
5050
"brace-expansion": "^2.0.2",
5151
"tmp": ">=0.2.4",
52-
"vite": "^6.4.1"
52+
"vite": "^6.4.1",
53+
"qs": ">=6.14.1"
5354
}
5455
},
5556
"packageManager": "pnpm@10.2.1+sha512.398035c7bd696d0ba0b10a688ed558285329d27ea994804a52bad9167d8e3a72bcb993f9699585d3ca25779ac64949ef422757a6c31102c12ab932e5cbe5cc92"

packages/core/package.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
"version": "1.2.0",
44
"description": "Framework-agnostic CSRF protection core functionality",
55
"type": "module",
6-
"main": "./dist/index.js",
7-
"types": "./dist/index.d.ts",
6+
"main": "./dist/index.mjs",
7+
"types": "./dist/index.d.mts",
88
"exports": {
9-
".": "./dist/index.js",
9+
".": "./dist/index.mjs",
1010
"./package.json": "./package.json"
1111
},
1212
"files": [
@@ -48,8 +48,8 @@
4848
"node": ">=18.0.0"
4949
},
5050
"devDependencies": {
51-
"tsdown": "^0.12.6",
51+
"tsdown": "^0.20.1",
5252
"typescript": "^5"
5353
},
54-
"module": "./dist/index.js"
54+
"module": "./dist/index.mjs"
5555
}

packages/core/src/constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { generateSecureSecret } from './crypto.js';
12
import type { CookieOptions, CsrfConfig } from './types.js';
23

34
/**
@@ -142,7 +143,7 @@ export const DEFAULT_CONFIG: CsrfConfig = {
142143
reissueThreshold: 500,
143144
},
144145
cookie: DEFAULT_COOKIE_OPTIONS,
145-
secret: crypto.getRandomValues(new Uint8Array(32)).toString(),
146+
secret: generateSecureSecret(),
146147
allowedOrigins: [],
147148
excludePaths: [],
148149
skipContentTypes: [],

packages/core/src/validation.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { SAFE_METHODS } from './constants.js';
2-
import { parseSignedToken, verifySignedToken } from './crypto.js';
2+
import {
3+
parseSignedToken,
4+
timingSafeEqual,
5+
verifySignedToken,
6+
} from './crypto.js';
37
import { OriginMismatchError } from './errors.js';
48
import type {
59
CsrfRequest,
@@ -97,7 +101,7 @@ export async function validateDoubleSubmit(
97101
return { isValid: false, reason: 'No CSRF token submitted' };
98102
}
99103

100-
if (cookieToken !== submittedToken) {
104+
if (!timingSafeEqual(cookieToken, submittedToken)) {
101105
return { isValid: false, reason: 'Token mismatch' };
102106
}
103107

@@ -135,12 +139,12 @@ export async function validateSignedDoubleSubmit(
135139
);
136140

137141
// 2. Ensure client cookie matches the verified token
138-
if (unsignedCookieToken !== verifiedUnsignedToken) {
142+
if (!timingSafeEqual(unsignedCookieToken, verifiedUnsignedToken)) {
139143
return { isValid: false, reason: 'Cookie integrity check failed' };
140144
}
141145

142146
// 3. Ensure submitted token matches the unsigned token
143-
if (submittedToken !== unsignedCookieToken) {
147+
if (!timingSafeEqual(submittedToken, unsignedCookieToken)) {
144148
return { isValid: false, reason: 'Token mismatch' };
145149
}
146150

packages/express/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
},
3838
"devDependencies": {
3939
"@types/express": "^4.17.21",
40-
"tsdown": "^0.12.6",
40+
"tsdown": "^0.20.1",
4141
"typescript": "^5.3.3"
4242
},
4343
"peerDependencies": {

packages/nextjs/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,15 @@
5555
"@csrf-armor/core": "workspace:*"
5656
},
5757
"peerDependencies": {
58-
"next": "^13.0.0 || ^14.0.0 || ^15.0.0",
58+
"next": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
5959
"react": "^18.2.0 || ^19.0.0"
6060
},
6161
"devDependencies": {
6262
"@testing-library/jest-dom": "^6.9.1",
6363
"@testing-library/react": "^16.3.0",
6464
"@types/react": "^19.1.6",
65-
"next": "^15.4.10",
66-
"tsdown": "^0.12.6",
65+
"next": "^16.1.6",
66+
"tsdown": "^0.20.1",
6767
"typescript": "^5"
6868
},
6969
"module": "./dist/index.js"

0 commit comments

Comments
 (0)