Read against 0.11.0. Split out of #102, where it is named as "a separate, smaller improvement".
Problem
saveTranslatedDocument calls payload.update(...) with no user and no overrideAccess: false
(src/server/features/translate-document/handler.ts). The Local API defaults to
overrideAccess: true, so collection access control never runs on a translated write.
Measured on Payload 3.84.1: a collection declared access: { update: () => false } — a blanket deny —
still accepted the translated value into the target locale. Nothing about the write is checked.
Why this matters
The plugin's own endpoint guard (access?: AccessGuard) controls who may trigger a translation, and
it defaults to undefined — no restriction. So on a default install the two layers that could stop an
unauthorized write are both absent: the endpoint is open and the write bypasses collection access.
For a host that models editorial permissions per collection, translation is a hole straight through
them.
Why it is not a one-line fix
Passing overrideAccess: false alone would deny every translation, because there is no user to check
against. Nothing threads one: every handler receives req.payload rather than req, and
TranslateDocumentHandler.handle(payload, input) takes a bare Payload. Background jobs make it
harder still — a job runs with no authenticated user by construction, so even a fully threaded request
has nobody to authorize on the async path.
So a real fix has to decide what a background translation runs as.
Suggested shape
Two parts, roughly independent:
- Thread the request. Pass
req (or a user) from the endpoint down to the write, so a
synchronously-run translation is authorized as the caller. Payload's update accepts
req?: Partial<PayloadRequest> for exactly this, and it threads the transaction too.
- Decide the background identity. Give the plugin config a way to name the user a queued
translation acts as — a fixed service user, or the user who enqueued it, captured at enqueue time
and replayed on the job.
Whatever the shape, the default cannot silently start denying writes on existing installs.
Related
Read against 0.11.0. Split out of #102, where it is named as "a separate, smaller improvement".
Problem
saveTranslatedDocumentcallspayload.update(...)with nouserand nooverrideAccess: false(
src/server/features/translate-document/handler.ts). The Local API defaults tooverrideAccess: true, so collection access control never runs on a translated write.Measured on Payload 3.84.1: a collection declared
access: { update: () => false }— a blanket deny —still accepted the translated value into the target locale. Nothing about the write is checked.
Why this matters
The plugin's own endpoint guard (
access?: AccessGuard) controls who may trigger a translation, andit defaults to
undefined— no restriction. So on a default install the two layers that could stop anunauthorized write are both absent: the endpoint is open and the write bypasses collection access.
For a host that models editorial permissions per collection, translation is a hole straight through
them.
Why it is not a one-line fix
Passing
overrideAccess: falsealone would deny every translation, because there is no user to checkagainst. Nothing threads one: every handler receives
req.payloadrather thanreq, andTranslateDocumentHandler.handle(payload, input)takes a barePayload. Background jobs make itharder still — a job runs with no authenticated user by construction, so even a fully threaded request
has nobody to authorize on the async path.
So a real fix has to decide what a background translation runs as.
Suggested shape
Two parts, roughly independent:
req(or auser) from the endpoint down to the write, so asynchronously-run translation is authorized as the caller. Payload's update accepts
req?: Partial<PayloadRequest>for exactly this, and it threads the transaction too.translation acts as — a fixed service user, or the user who enqueued it, captured at enqueue time
and replayed on the job.
Whatever the shape, the default cannot silently start denying writes on existing installs.
Related