Generated by the improve skill on 2026-07-29 at commit 90411f2 (standard-effort audit; all findings vetted against source before planning). Extended the same day with a full review of all open GitHub issues (18) and PRs (6) on payload-auth/payload-auth — plans 007–010 and the "GitHub reconciliation" section come from that pass. Execute in the order below unless dependencies say otherwise. Each executor: read the plan fully before starting, honor its STOP conditions, and update your row when done.
Selection note: this run executed non-interactively, so per the skill's default the top findings by leverage were planned. The full vetted backlog below lists everything else found; any backlog item can be promoted to a plan on request.
Recommended order: 007 first (secrets leak — ship + release + rotation advisory), then 001 → 002, then the rest as staffing allows.
| Plan | Title | Priority | Effort | Depends on | Status |
|---|---|---|---|---|---|
| 007 | Strip secrets from admin serverProps (land PR #153) + release + rotation advisory | P0 | S | — | DONE (approved 2026-07-29; branch advisor/007-strip-secrets in worktree agent-a60fb8419788204d7 — awaiting user merge; merge + release + secret-rotation advisory ASAP) |
| 001 | Portable test infra (env-driven DB, compose, root scripts) | P1 | S | — | DONE (approved 2026-07-29; branch advisor/001-portable-test-infra in worktree agent-a18a995c7754a2309 — awaiting user merge; baseline suite 21/21 files green after pnpm build) |
| 002 | CI quality gate + frozen-lockfile release install | P1 | S | 001 | DONE (approved 2026-07-29; branch advisor/002-ci-quality-gate in worktree agent-aa5b7b1ebea1e5120, includes 001's commits — awaiting user merge) |
| 010 | nextCookies infinite form-state loop — read-only session lookup in auth strategy (#139) | P1 | S | — | DONE (approved 2026-07-29; branch advisor/010-disable-refresh-strategy in worktree agent-aaa737c8e74dbfbc2 — awaiting user merge) |
| 003 | March security punchlist (open redirect, invite expiry, cascade re-throw, api-key refs) | P1 | M | 001 | DONE (approved 2026-08-04; 6 commits in worktree agent-a3c0bb44a5921823c — suite 260 passed verified by reviewer. Note: api-key FK is referenceId post-BA-1.5, fix wired accordingly; migration expires pre-existing invite tokens immediately — release note) |
| 004 | Join/relationship normalization correctness in the adapter | P1 | M | 001 | DONE (approved 2026-08-04; branch advisor/004-join-normalization, merged into v3; 252-test suite green in worktree; 004×009 interaction specs verified green on v3) |
| 008 | Better Auth 1.6.x upgrade + implement missing adapter methods (consumeOne, #160) |
P1 | M–L | 001, 005 | DONE (approved 2026-08-04; branch advisor/008-ba-1.6-compat, merged into v3. BA 1.6.25; consumeOne + incrementOne implemented — all 13 DBAdapter members covered; #124's feared team-join gap did NOT materialize on 1.6.25) |
| 011 | v3 integration fixes (BA 1.6 × session forward-join root cause + NaN hardening) | P1 | S | 004, 008, 009 | DONE (approved 2026-08-05; branch advisor/011-v3-integration-fixes, merged into v3. Root cause: BA 1.6 getAuthTables hardcodes session/account userId.references.model to literal "user"; patched to resolved slug in createTransform. convertWhereValue now throws descriptive errors instead of emitting params: NaN) |
| 009 | Scope role transforms to users collection (#112, #128, case preservation) | P1 | M | 001 | DONE (approved 2026-07-29; branch advisor/009-role-transform-scoping in worktree agent-a1c0aa400d60ab797 — awaiting user merge. #112 confirmed+fixed; #128 did NOT reproduce — read path already converts array→string; locked in with impersonate.test.ts. Release note needed: role casing now preserved) |
| 005 | Package manifest integrity (phantom/unused deps, exports, d.ts aliases, peer floors) | P2 | M | — | DONE (approved 2026-07-29; branch advisor/005-manifest-integrity in worktree agent-a80c703465c688271 — awaiting user merge; full suite verified green by reviewer) |
| 006 | 2FA admin-panel lockout bugs | P2 | M | — | DONE (approved 2026-07-29; 5 commits in worktree agent-a8a3f7984e19ebd33 — awaiting user merge; full suite + 5 new OTP tests verified green by reviewer) |
Status values: TODO | IN PROGRESS | DONE | BLOCKED (with one-line reason) | REJECTED (with one-line rationale)
Integration state (2026-08-05): all 11 plans are merged into the v3 branch, verified by a full-suite run on a from-scratch database (auth_test_v3). Scratch databases used during execution (auth_test_003/004/008/009/v3/v3fix) can be dropped once v3 merges. Release notes for the eventual publish must mention: (1) secret-rotation advisory (plan 007 — secrets were previously served in login-page HTML), (2) pre-existing admin invite tokens are expired immediately by the 003 migration, (3) role values are no longer lowercased (plan 009), (4) Better Auth peer floor is now effectively 1.6.x-tested (plan 008).
- 002 requires 001: the CI workflow runs the test suite, which is unrunnable off the author's machine until the DB connection is env-driven.
- 003, 004, 008, 009 require 001 to verify (their regression tests need a runnable suite); the code changes themselves don't depend on it.
- 008 and 005 both edit
packages/payload-auth/package.json— land 005's manifest changes first or rebase 008 on it. - 010 must confirm the installed better-auth supports
disableRefresh; if not, it sequences after 008. - 005 and 006 are independent; 005's full-suite verification is cheaper after 001.
Every open item mapped to a disposition. "Close-with-release" = fixed on main but never published; cut a release, then comment + close.
Highest-leverage first. IDs refer to the audit session; evidence was verified against 90411f2.
Security / correctness
- PLUGIN-08 — users collection spreads
...existingUserCollection?.accessLAST (plugin/lib/build-collections/users/index.ts:~256-267), letting a pre-existing users collection silently overwrite the plugin's access guards (same class as fixed P1-4, higher-value collection). S effort. - PLUGIN-05 —
getSafeRedirect(plugin/payload/utils/get-safe-redirect.ts) is prefix-check based; encoded control chars (e.g./%09/evil.com) survive and browsers normalize to protocol-relative URLs. Harden withnew URL(value, base)origin check + control-char rejection + unit tests. S effort. - ADAPTER-11 — after-signup invite middleware deletes the token before assigning the role with no error handling/compensation; a failed role update burns the invitation and skips
originalAfter. S–M effort; preserve delete-first single-use semantics (P0-4). - PLUGIN-03 —
AdminButtonscallsuseMemoafter an earlyreturn null(admin-buttons/index.tsx:22-34): conditional hook → React 19 crash of the edit view whenidresolves. S. - PLUGIN-11 — admin-invite modal caches the generated link; switching role then Copy/Send distributes a token for the previously selected role. S.
- ADAPTER-10 —
normalizeDatalowercases all role values on write; any camelCase configured role breaks select validation/admin checks. S, MED risk (existing data). - PLUGIN-18 — self-update access returns
falsewhen Payload evaluates permissions withoutid/data, making the admin UI render the account view read-only for non-admins; also strict===id comparison across idType. S, MED risk. - ADAPTER-08 —
updateMany/deleteManydiscard Payload's per-docerrorsarray; partial failure (e.g. "revoke all sessions") reports success. S. - PLUGIN-17 —
refresh-token.tslooks up thedontRememberTokensigned cookie without its__Secure-prefix, so "don't remember me" is ignored on HTTPS deployments. S. - PLUGIN-04 — optional username field rejects empty string (regex refine ignores
required: false), blocking default-config admin signup when the field is left blank. S. - ADAPTER-12 —
findManysingle-ID fast path ignoresoffset/sortBy;findOne/updatereturnundefinedinstead ofnull. S. - PLUGIN-09/10/13/14/15/16/19 — smaller admin-UI issues: silent passkey fetch/delete failures; dead loading state on social/passkey buttons; soft navigation after passkey/2FA success vs hard reload on password path; hydration-unsafe
errorquery read; unsafedata.tokenderef + unmemoized auth client in signup; password policy not enforced client-side on reset/signup; token field keyed on hardcoded name. S each. - NEW-4 (from March review, still open) — small find-then-delete race window on invite consumption; NEW-7 — cookie token format unvalidated in
after-logout.ts; NEW-9/NEW-10/NEW-11 — role-hierarchy gap, missing Zod validation, allowedFields TODO. Small items; NEW-6 (?? falseno-op) is trivial.
Performance
- ADAPTER-03 — forward joins are N+1: one sequential
findByIDper doc per join infindMany, plus per-docflattenAllFields. Batch with onein-query per joined collection. M. - ADAPTER-07 —
get-collection.tsfield-name mapping helpers re-flatten the collection field tree on every call (~2N flattens per transformed doc); add per-slug memo like the existingflattenedFieldsCache. S. - ADAPTER-04 — reverse joins escalate the whole query to
depth: 1, contradicting the documented depth-0 contract (ARCHITECTURE.md:829); non-requested relationships come back populated. Needs a design decision (collapse non-requested relationships) + doc update. M.
Tests (all blocked on 001 for practicality)
- TEST-02 — no integration test authenticates a Payload request with a real BA session cookie (
payload.auth()untested end-to-end). - TEST-03 — no e2e guard against self-role-escalation (
overrideAccess: falsewrite-path tests missing; existing admin update test bypasses access entirely). - TEST-05 — self-defeating assertions: empty-catch fixture creation in cascade-delete test, either/or access-control assertions,
not.toBe(200). - TEST-06 — cascade delete + transactions verified only against mocks; test config sets
transactionOptions: false. - TEST-09 — 12 BA plugins enabled in the test config, ~8 with zero runtime assertions (
captureOTPharness paid for and unused — magic-link/email-OTP tests are near-free). - TEST-04 —
dev-docs/TEST-PLAN.mdpromises 13 test files, 0 exist; inventory paths stale. Reconcile doc + land T7/T1/T9 first. - TEST-07 — 32 client
.tsxfiles (3,737 lines), zero tests, zero component-test infra; cheapest slice is unit-testingget-safe-redirect.tsandshared/form/validation.ts. - TEST-08 — 4 test files bypass the shared Payload singleton (second PG pool); global truncation cleanup blocks parallelism.
Dependencies / DX / docs
- NEW (found by plan-005 execution, CONFIRMED) — strict-Node-ESM consumers can't
import("payload-auth"): barrel files use extensionless directory re-exports (src/index.ts→export * from "./better-auth"), which swc emits verbatim; Node's ESM resolver rejects them withERR_UNSUPPORTED_DIR_IMPORT. Pre-existing (reproduced at90411f2); only bundler-based consumers work today. Fix: add explicit/index.js-resolvable specifiers or an extension-rewriting build step acrosssrc/**barrels. S–M effort; promotes the DEPS-06 MED-confidence note to confirmed. - DEPS-04 — demo pins
@payloadcms/*3.67.0 (critical drizzle SQLi advisory) against hoistedpayload3.79.0;latestspecifiers on radix packages; TS/types/zod versions inconsistent across workspaces. M. - DX-01/DX-02 — Biome and Prettier both configured with opposing styles (source matches Biome;
pnpm formatwould rewrite the repo); Biome's 30-line linter config hasenabled: false; prettier + its 3 plugins undeclared at root. Decide → delete one → enable linting → add scripts. S, MED risk (big mechanical diff). - DX-04 — root
clean-allscript deletespnpm-lock.yaml, has brokenfindprecedence, no-pruneon node_modules, and unquotedxargs rm -rf(repo path contains spaces!). S — genuinely hazardous as written. - DX-05 — no CLAUDE.md/AGENTS.md; agents rediscover build/test topology each session and risk hand-editing
generated-types.ts. S. - DX-03 — commitlint config imports packages declared nowhere; no local hook. S.
- DX-10 — demo
preparescript rewritesdemo/package.jsonon every install (lockfile-drift driver). S. - DX-08 — stale duplicate
vite.config.ts(vitest usesvitest.config.ts); delete. Trivial. - DEBT-01 — the two 900+-line adapter closure factories (
adapter/index.ts,adapter/transform/index.ts) — the 5-hotfix join area is untestable in isolation. L; do after 004 lands. - DEBT-02 — two divergent model-key→slug resolvers (
get-collection-slug.tsswitch vs schemamodelName), a documented drift hazard that has already caused bugs. M. - DEBT-03 — mixed
@/alias vs relative imports (158 vs 302) with the alias configured in 4 places. S codemod; do after 005 (tsc-alias interacts). - DEBT-05/06 — dead
try-catch.ts/shared/index.ts; invertedSession/Usertype aliases intests/dev/index.ts:280-283. S. - DOCS-01 —
docs/is 99 vendored Better Auth pages documenting a different product (installation page installsbetter-auth, notpayload-auth); actively misleading. L (design decision: rewrite vs delete-and-link). - DOCS-02 — the npm package ships no README (
files: ["dist"], nopackages/payload-auth/README.md); npmjs.com page is blank. S–M, high visibility. - DOCS-03/04 —
ARCHITECTURE.mddrifted (wrong export path, phantom root commands, stale test inventory);CONTRIBUTING.mdis a 2-line stub. S each.
Direction (maintainer strategy calls — see final audit report for trade-offs)
- DIR-01 — replace vendored BA docs with plugin-native reference docs; immediate slice = the npm README (DOCS-02).
- DIR-02 — ship a
createPayloadAuthClientfactory deriving client plugins +roletyping from server options; the demo's hand-rolled client has a broken import and wrong role type today, proving the need. - DIR-03 — decide whether the billing/enterprise surface (stripe/polar/scim/sso in
supportedBAPluginIds+ collection builders, zero configurators/tests/docs) is supported or should be de-advertised. - DIR-04 — absorb the demo's ~1,475 lines of hand-rolled org/team/multi-session admin UI; smallest slice is the
members.ts:50role-selector TODO.
- P1-11 (adapter transaction no-op), P2-4 (password verify via
login()), P2-15 (cookie domain on delete), P3-11 (role string↔array bridge): documented known limitations / by-design — re-confirmed, not re-planned. - Depth-0 everywhere: deliberate data-shape contract per
ARCHITECTURE.md; only the join-related deviation from it is a finding (ADAPTER-04, backlog). @better-auth/api-key/sso/stripe/scimas devDeps: correctly scoped (used by the type-generation script) — not dead weight.onlyBuiltDependenciesduplicated in root package.json vs pnpm-workspace.yaml: inert cosmetic issue (pnpm 10 reads the workspace file).- P1-1 WHERE connector semantics and P3-4
like-on-MongoDB: confirmed NOT bugs (match official BA adapters / Payload abstracts operators). - Big-bang removal of the 152
as anycasts: not worth doing as a project; adopt no-new-as any-in-adapter rule and fix opportunistically (DEBT-04).
