---
name: payload-cms-overrideaccess-bypass
description: overrideAccess:true in Payload CMS completely bypasses the access control system — critical anti-pattern in webhook handlers
type: concept
---

# Payload CMS — `overrideAccess: true` Bypasses All Access Control

## What It Does

Passing `overrideAccess: true` to any Payload local API call (`payload.find()`, `payload.update()`, `payload.create()`, etc.) tells Payload to **skip the collection's `access` functions entirely**. No role check, no ownership check, no field-level restriction — the call succeeds as if it came from a superadmin.

```ts
// This bypasses ALL access control — any caller can modify any lead
await payload.update({
  collection: 'leads',
  id: leadId,
  data: { status: 'converted' },
  overrideAccess: true,   // ← NEVER do this in a public-facing handler
})
```

## Why It Exists

`overrideAccess` is intended for:
- Server-side seed scripts
- Internal background jobs that run with inherent trust
- Admin CLI utilities

It is **not** intended for request handlers that accept untrusted input.

## The Anti-Pattern

Using `overrideAccess: true` inside a webhook route (or any publicly-reachable API route) means:

1. **HMAC validation failure** (or intentional bypass) → attacker can manipulate Payload documents without any identity.
2. Even with HMAC in place — if HMAC is mis-implemented (e.g., empty-string secret fallback) the access control is your last defense. `overrideAccess: true` removes it.

### Real Example (Shumiland project — `src/app/api/binotel/webhook/route.ts`)

```ts
// BAD: overrideAccess:true in a webhook handler
const lead = await payload.find({
  collection: 'leads',
  where: { phone: { equals: phone } },
  overrideAccess: true,   // ← attacker can enumerate leads
})

await payload.update({
  collection: 'leads',
  id: lead.docs[0].id,
  data: { callStatus: newStatus },
  overrideAccess: true,   // ← attacker can overwrite any lead
})
```

## The Fix

Remove `overrideAccess: true` and either:

**Option A — use a system-level user identity (recommended for webhooks):**

```ts
// Create a service account user and use req.user
const systemUser = await getSystemUser()  // cached, read once
await payload.update({
  collection: 'leads',
  id: lead.docs[0].id,
  data: { callStatus: newStatus },
  user: systemUser,         // proper identity context
  // no overrideAccess
})
```

**Option B — use `req.payload` with the Payload admin user if running inside a Next.js route handler:**

```ts
// The req object from NextRequest has no Payload user attached
// You need to explicitly provide identity
```

**Minimum mitigation** if full fix isn't feasible: scope `overrideAccess: true` only to the narrowest possible read (by phone + exact match), and verify HMAC rigorously before reaching it.

## Related Vulnerabilities

When combined with other gaps this becomes critical:

| Gap | Effect when combined with overrideAccess |
|-----|------------------------------------------|
| `BINOTEL_HMAC_SECRET ?? ''` empty fallback | HMAC skipped entirely → full unauthenticated write |
| No timestamp validation | Replayed requests can re-trigger status changes indefinitely |
| HMAC logged on failure | Secret leaks to any log aggregation system |

## See Also

- [[wiki/concepts/webhook-replay-attack-prevention]] — timestamp + nonce deduplication
- [[wiki/concepts/secrets-git-history-purge]] — if the HMAC secret was committed

**Source:** daily/2026-05-09.md | 2026-05-09
