diff --git a/.jules/sentinel.md b/.jules/sentinel.md new file mode 100644 index 00000000..02434c99 --- /dev/null +++ b/.jules/sentinel.md @@ -0,0 +1,4 @@ +## 2024-07-25 - Timing Attack Fast Path in Token Comparison +**Vulnerability:** The authentication validation used a short-circuit buffer length check (`tokenBuffer.length === authTokenBuffer.length`) before calling `crypto.timingSafeEqual()`. +**Learning:** Checking the length of secrets before a constant-time comparison creates a fast path that leaks the length of the expected secret via timing differences, rendering the constant-time check partially ineffective. +**Prevention:** Always hash both secrets to a fixed length (e.g., using `crypto.createHash('sha256')`) before comparison to ensure true constant-time evaluation and avoid short-circuiting logic based on length. \ No newline at end of file diff --git a/src/cli/commands/serve.ts b/src/cli/commands/serve.ts index 2c37293e..e20ec168 100644 --- a/src/cli/commands/serve.ts +++ b/src/cli/commands/serve.ts @@ -346,13 +346,10 @@ export async function handleServeCommand(_options: unknown, command: Command) { let isAuthenticated = false; if (scheme?.toLowerCase() === 'bearer' && token) { - const tokenBuffer = Buffer.from(token); + const tokenHash = crypto.createHash('sha256').update(token).digest(); for (const authToken of authTokens) { - const authTokenBuffer = Buffer.from(authToken); - if ( - tokenBuffer.length === authTokenBuffer.length && - crypto.timingSafeEqual(tokenBuffer, authTokenBuffer) - ) { + const authTokenHash = crypto.createHash('sha256').update(authToken).digest(); + if (crypto.timingSafeEqual(tokenHash, authTokenHash)) { isAuthenticated = true; break; }