feat!: self-only default write access for auth collections - #17806
Open
DanRibbens wants to merge 11 commits into
Open
feat!: self-only default write access for auth collections#17806DanRibbens wants to merge 11 commits into
DanRibbens wants to merge 11 commits into
Conversation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ctions Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BREAKING CHANGE: defaultAuthAccess is now a factory that takes the target collection slug. Prevents a cross-collection authorization bypass where an authenticated user in collection A with an id colliding a doc in auth collection B could update/delete that doc via the default access.
Removes the factory shape introduced in 0951f53 by reading `slug` from AccessArgs directly. Keeps the security guard (`user.collection !== slug` denies) but restores the plain `Access` signature so the export shape is unchanged from Tasks 2/3. Also tightens the cross-collection regression test to explicitly set the attacker user's collection instead of relying on createLocalReq's admin.user fallback.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
📦 esbuild Bundle Analysis for payloadThis analysis was generated by esbuild-bundle-analyzer. 🤖
Largest pathsThese visualization shows top 20 largest paths in the bundle.Meta file: packages/next/meta_index.json, Out file: esbuild/index.js
Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js
Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js
Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js
Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js
Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js
DetailsNext to the size is how much the size has increased or decreased compared with the base branch of this PR.
|
JessRynkar
pushed a commit
that referenced
this pull request
Aug 17, 2026
## What / Why `test/trash/e2e.spec.ts` → **"Should collapse breadcrumbs into a popup menu when they do not fit the available width"** is flaky on the `trash [tanstack-start]` CI variant (fails all retries in some runs, passes in others; passes reliably on `next`). Seen on [PR #17806](https://github.com/payloadcms/payload/pull/17806/checks) and [run 31884980215](https://github.com/payloadcms/payload/actions/runs/31884980215), and confirmed intermittent on recent `main` runs too. ## Root cause The test used a **400px** viewport. At 400px the expanded breadcrumbs (`Dashboard / Posts / Trash / Trashed Post`) measure **~298px** against **~298px** of available space — within 1px. `StepNav`'s overflow check is `needed - available > 1`, so at that exact-fit boundary the collapse decision is effectively a coin-flip: | Viewport | available | needed | collapses | | --- | --- | --- | --- | | **400px** | **298** | **298** | **no ← flaky knife-edge** | | 360px | 213 | 298 | yes | | 320px | 213 | 298 | yes | | 280px | 196 | 298 | yes | Because the `.app-header__step-nav-wrapper` shrink-wraps to the breadcrumb content, both the collapsed and expanded states are self-stable, so once a run lands "expanded" at the boundary it stays expanded and the toggle never renders. Slower environments (tanstack-start) land on the wrong side of the boundary more often. Reproduced deterministically locally by rendering wide (expanded) then resizing to 400px. ## Fix Move the test viewport to **320px** (a standard small-mobile width), where the breadcrumbs are unambiguously past the available width (~213px available vs ~298px needed). This makes the collapse deterministic without touching the shared `StepNav` component. ## Verification - 15/15 consecutive local runs of the target test pass. - All 7 `-g "breadcrumb"` tests in the trash suite pass. ## Note The knife-edge is a symptom of a latent measurement quirk in `StepNav`: `available` is read from a container that shrink-wraps to the breadcrumb content, so the measure is content-dependent rather than a stable available-width reference. That only manifests at an exact-fit width and is benign for users (the breadcrumbs genuinely fit), so it's intentionally out of scope here — flagging for the UI team. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
defaultAuthAccessthat constrainsupdate/delete/unlockon auth-enabled collections to{ id: { equals: req.user.id } }whenreq.user.collectionmatches the target collection slug, and denies otherwise.addDefaultsToCollectionConfigto usedefaultAuthAccessforupdate/delete/unlockwhencollection.authis truthy.read,create,readVersions, andadmincontinue to usedefaultAccess. Non-auth collections are untouched.docs/access-control/collections.mdxunder Update, Delete, and Unlock.BREAKING CHANGE
On auth-enabled collections, the default
update,delete, andunlockaccess is now self-only. Apps that relied on any authenticated admin-collection user being able to modify other users must add an explicitaccess.update(and/ordelete,unlock) function to restore prior behavior. Example:The built-in
unlockendpoint is effectively disabled by default and now requires an explicitaccess.unlockfor admin-to-user unlock flows.Cross-collection safety: an authenticated user in auth collection A can no longer update/delete/unlock docs in a different auth collection B, even if IDs collide (a real risk on Postgres where serial IDs are shared across collections).
Test plan
pnpm run test:int auth— 104 pass (includes 7 new default-access tests)pnpm run test:int access-control— 41 passpnpm run test:int custom-strategy— 1 passpnpm run build:core— passpnpm run dev auth, log in as one user, attempt to update another via the admin UI — expect 403/not-found; self-update expected to succeed.🤖 Generated with Claude Code