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
18 changes: 18 additions & 0 deletions apps/mcp-server/src/mcp/sse-auth.guard.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { createHmac } from 'crypto';
import { ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { SseAuthGuard } from './sse-auth.guard';

Expand Down Expand Up @@ -92,5 +93,22 @@ describe('SseAuthGuard', () => {

expect(() => guard.canActivate(context)).toThrow(UnauthorizedException);
});

it('should reject tokens of different lengths without timing leak', () => {
const context = createMockContext('Bearer short');

expect(() => guard.canActivate(context)).toThrow(UnauthorizedException);
});

it('should use HMAC-based comparison (no length early-return)', () => {
// Verify HMAC produces fixed-length digests regardless of input length
const key = Buffer.from('codingbuddy-sse-auth');
const expectedHash = createHmac('sha256', key).update('my-secret-token').digest();
const providedHash = createHmac('sha256', key).update('wrong').digest();

// Both hashes should be same length (32 bytes) regardless of input length
expect(expectedHash.length).toBe(providedHash.length);
expect(expectedHash.length).toBe(32);
});
});
});
18 changes: 7 additions & 11 deletions apps/mcp-server/src/mcp/sse-auth.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
UnauthorizedException,
} from '@nestjs/common';
import { Request } from 'express';
import { timingSafeEqual } from 'crypto';
import { createHmac, timingSafeEqual } from 'crypto';

/**
* Guard for SSE endpoints that validates Bearer token against MCP_SSE_TOKEN env var.
Expand Down Expand Up @@ -53,17 +53,13 @@ export class SseAuthGuard implements CanActivate {
}

/**
* Timing-safe token comparison to prevent timing attacks.
* Handles tokens of different lengths safely.
* Timing-safe token comparison using HMAC to prevent timing attacks.
* HMAC produces fixed-length digests, eliminating length-based timing leaks.
*/
private tokensMatch(expected: string, provided: string): boolean {
const expectedBuf = Buffer.from(expected, 'utf-8');
const providedBuf = Buffer.from(provided, 'utf-8');

if (expectedBuf.length !== providedBuf.length) {
return false;
}

return timingSafeEqual(expectedBuf, providedBuf);
const key = Buffer.from('codingbuddy-sse-auth');
const expectedHash = createHmac('sha256', key).update(expected).digest();
const providedHash = createHmac('sha256', key).update(provided).digest();
return timingSafeEqual(expectedHash, providedHash);
}
}
Loading