Skip to content

Unauthenticated cron endpoints trigger cross-tenant source reprocessing #1

@noobx123

Description

@noobx123

security email is not working.

Severity : High
CVSS : 8.1 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H)
Endpoint : https://vectorbase.dev/api/cron/auto-retrain
https://vectorbase.dev/api/process

Unauthenticated cron endpoints trigger cross-tenant source reprocessing

Summary

Two internal API routes — /api/cron/auto-retrain and /api/process — are reachable without any authentication because the CRON_SECRET environment variable is not set in production, causing the guarding if condition to short-circuit and skip all auth checks. Any unauthenticated request can trigger source reprocessing across all organizations, and /api/process accepts an arbitrary sourceId with no ownership check, enabling cross-tenant data manipulation.

Steps / PoC

  1. Confirm the endpoints are publicly reachable without auth:
curl -s https://vectorbase.dev/api/cron/auto-retrain

Response (HTTP 500 — reaches the handler and fails only at DB connection, not at auth):

{"error":"Auto-retrain failed","message":"(ENOTFOUND) tenant/user postgres.zkwujwhzmqbykehvgdhs not found"}

HTTP 200/204 would be returned for any tenant's sources when the DB is reachable.

  1. Trigger processing of an arbitrary source (no auth, no ownership check):
curl -s -X POST https://vectorbase.dev/api/process \
  -H "Content-Type: application/json" \
  -d '{"sourceId":"<victim-org-source-uuid>"}'

Response (HTTP 500 — DB unreachable, same handler reached):

{"error":"(ENOTFOUND) tenant/user postgres.zkwujwhzmqbykehvgdhs not found"}
  1. Root cause in source (src/app/api/process/route.ts and src/app/api/cron/auto-retrain/route.ts):
const CRON_SECRET = process.env.CRON_SECRET
if (CRON_SECRET && authHeader !== `Bearer ${CRON_SECRET}`) {
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// proceeed to processSourceWithPrisma(sourceId) without ownership check

When CRON_SECRET is undefined (unset), the && short-circuits and the auth block is never entered. The route continues to call processSourceWithPrisma(sourceId) on any UUID supplied by the caller.

Impact

Any unauthenticated attacker can trigger reprocessing of every source across all tenant organizations (auto-retrain) or force-reprocess a specific target source by UUID, corrupting its embeddings or triggering resource exhaustion on the operator's OpenAI account.

Fix

  1. Set CRON_SECRET to a strong random value in the production environment.
  2. Change the guard to if (!CRON_SECRET || authHeader !== ...) so an absent secret causes a hard failure, not an open gate.
  3. Add an ownership check in /api/process before calling processSourceWithPrisma.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions