fix: harden security post-review (JWT exposure, rate limiter, token expiry, timing)
- Raw JWT never sent to browser: page.tsx uses peekLinkingToken (read-only), client sends opaque linkingId, API calls retrieveLinkingToken server-side - link-account rate limiter now uses isRateLimited/registerFailedAttempt/ resetAttempts correctly (count only failures, reset on success) - linking_tokens gains expiresAt column (indexed) + opportunistic expiry purge on insert to prevent unbounded table growth - secureTokenCompare fixed: pad+slice to expected length so timing is constant regardless of submitted token length (no length leak) - autoLinkOAuth uses config.oauth.allowAutoLinking (boolean) instead of process.env truthy check that mishandles OAUTH_ALLOW_AUTO_LINKING=false - Add Permissions-Policy header; restore X-Frame-Options for legacy UAs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,13 +16,12 @@ const SYNC_RATE_LIMITS = new Map<string, { count: number; windowStart: number }>
|
||||
* Timing-safe token comparison to prevent timing attacks
|
||||
*/
|
||||
function secureTokenCompare(a: string, b: string): boolean {
|
||||
if (a.length !== b.length) {
|
||||
// Compare against dummy to maintain constant time
|
||||
const dummy = Buffer.alloc(a.length, 0);
|
||||
timingSafeEqual(Buffer.from(a), dummy);
|
||||
return false;
|
||||
}
|
||||
return timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
||||
// Always compare buffers of the expected length (b) to avoid leaking
|
||||
// the expected token length via early-return timing when a.length !== b.length
|
||||
const bufA = Buffer.from(a.padEnd(b.length, "\0").slice(0, b.length));
|
||||
const bufB = Buffer.from(b);
|
||||
const equal = timingSafeEqual(bufA, bufB);
|
||||
return equal && a.length === b.length;
|
||||
}
|
||||
|
||||
function getClientIp(request: NextRequest): string {
|
||||
|
||||
Reference in New Issue
Block a user