mirror of
https://github.com/Luchexx/wacrm.git
synced 2026-11-02 09:19:51 +00:00
fix(flows): constant-time compare for cron secret
The `/api/flows/cron` route gated on `supplied !== expected`, which compares strings byte-by-byte with early exit on first mismatch. An attacker who can hit the endpoint could in principle recover the secret one byte at a time by measuring response-time deltas. With a high-entropy 64-char secret this is mostly theoretical, but the upgrade is a one-liner. Switch to crypto.timingSafeEqual with a length pre-check (the function throws on mismatched lengths; the length itself isn't sensitive when the expected value is set by the operator). Not touching the equivalent line in /api/automations/cron in this PR — one fix per branch keeps the diff easy to review and revert. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9ff4a8d1ba
commit
fe4e0117da
@ -1,3 +1,4 @@
|
|||||||
|
import { timingSafeEqual } from 'node:crypto'
|
||||||
import { NextResponse } from 'next/server'
|
import { NextResponse } from 'next/server'
|
||||||
import { supabaseAdmin } from '@/lib/flows/admin-client'
|
import { supabaseAdmin } from '@/lib/flows/admin-client'
|
||||||
import { resolveFallbackPolicy } from '@/lib/flows/fallback'
|
import { resolveFallbackPolicy } from '@/lib/flows/fallback'
|
||||||
@ -30,8 +31,17 @@ export async function GET(request: Request) {
|
|||||||
if (!expected) {
|
if (!expected) {
|
||||||
return NextResponse.json({ error: 'cron not configured' }, { status: 503 })
|
return NextResponse.json({ error: 'cron not configured' }, { status: 503 })
|
||||||
}
|
}
|
||||||
const supplied = request.headers.get('x-cron-secret')
|
// Constant-time compare so an attacker who can hit the endpoint
|
||||||
if (supplied !== expected) {
|
// can't recover the secret byte-by-byte from response-time deltas.
|
||||||
|
// Length pre-check is required by timingSafeEqual (throws otherwise)
|
||||||
|
// and leaks only the length itself, which isn't sensitive.
|
||||||
|
const supplied = request.headers.get('x-cron-secret') ?? ''
|
||||||
|
const suppliedBuf = Buffer.from(supplied)
|
||||||
|
const expectedBuf = Buffer.from(expected)
|
||||||
|
if (
|
||||||
|
suppliedBuf.length !== expectedBuf.length ||
|
||||||
|
!timingSafeEqual(suppliedBuf, expectedBuf)
|
||||||
|
) {
|
||||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user