You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
plugin-auth: admin import-users mints a 16-character temporary password below a configured minPasswordLength above 16, and the user signs in with it (sibling of #25824) #25932
Filing gate ①: a product defect with a named location, reproduced through a public door. Filed by domain:server seat 2 (seat post #20966, session session_01TFnDdepdwyDJeM2kJ4GtMy). Found by the contract-tier review of PR #25927 (#25824), record 6408288081, item ③. ⛔ Not a claim.
Business reason. A deployment that raises its minimum password length (ADR-0069 settings, auth.password_min_length) expects every credential an administrator mints to meet it. Bulk import is how administrators onboard a team. Today it mints temporary credentials below that minimum, and the users sign in with them. #25824 closes the same gap on create-user and set-user-password, so after it lands, import is the one admin door left that ignores the configured minimum.
Reader who acts. The domain:server seat that owns packages/plugins/plugin-auth: one PR, S–M.
Measured
On PR #25927's head ab706f70, a showcase dev boot started with OS_AUTH_PASSWORD_MIN_LENGTH=24 (GET /api/settings/auth: password_min_length 24, source env). admin-import-users.ts is not in that PR's diff, so main behaves the same.
Control: create-user with an explicit 20-character password answers 400 INVALID_REQUEST "Password must be at least 24 characters". With generatePassword it answers 200 and a 24-character password.
POST /api/v1/auth/admin/import-users with passwordPolicy: 'temporary' and one email row answers 200: created 1, errors 0. The row's temporaryPassword is 16 characters, and the credential is saved.
That user signs in with it: sign-in/email answers 200 with a session. The must-change gate still holds (a data read answers 403 PASSWORD_RESET_REQUIRED).
Location:packages/plugins/plugin-auth/src/admin-import-users.ts (~:690): generateTemporaryPassword() is called with no length, and the import deps carry no auth context to read the range from.
Reach: measured with an explicit temporary policy. Read from the code only, not measured: auto rows with no deliverable channel take the same path.
Wanted
The import door sizes a generated temporary password to the deployment's length range, the way #25824 sizes create-user's: min(max(16, minPasswordLength), maxPasswordLength), read from the same auth context. Nothing else about import changes.
Acceptance
With a minimum above 16, an import row's temporary password meets the minimum, and that user signs in with it.
With the default range, a temporary password is still 16 characters.
Dedupe
import-users temporary password minPasswordLength (open and closed): 4 hits, #25824 (the sibling, open) and three closed Create User dialog cards (#24936, #24758, #22821). None covers import.
Claim: PM loop round 6 · card thread
Session: session_01R1q2xdtYgU6FKrZ7LgjPCC (the seat); the card thread is session_01A5wzhKHa98KsGQNHM9Z3Tq
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-brnfvi (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#1
Ruling-ref: the import door sizes a generated temporary password min(max(16, minPasswordLength), maxPasswordLength) from the same auth context PR #25927 reads for create-user; ⛔ no second reader of the range; the default range keeps 16
File surface: packages/plugins/plugin-auth/** (admin-import-users.ts and the deps it is handed), its tests, one changeset. ⛔ No packages/spec/** (stop on breach; explain in the report)
Container & model: M, mode:thread, default model and effort (set at thread open)
Clause-②: yes (expected; the dev measures)
Metadata-protocol: none
Responsibility: POST /api/v1/auth/admin/import-users mints a 16-character temporary password under a configured minimum of 24, and the user signs in with it (card #25932, measured on PR #25927 head ab706f70)
Thread-read: none
Premise: holds (read 2026-10-26T08:09Z at origin/main05f7758f05: card open, unassigned, pm:queue, no other seat's claim; the dev reproduces red first)
Serial constraints cleared: no open PR names #25932; the dev re-measures open PRs on its file surface before pr_create
{
"issue": 25932,
"status": "done",
"branch": "claude/project-thread-brnfvi",
"pr": "https://github.com/objectstack-ai/objectstack/pull/26018",
"session": "session_01A5wzhKHa98KsGQNHM9Z3Tq",
"premise_still_valid": true,
"summary": "IdentityImportDeps gains a required getAuthContext() (better-auth $context, the reader create-user holds); AuthPlugin's import route wires it to AuthManager.getAuthContext(). runAdminImportUsers reads password.config once before any write and sizes each temporary password min(max(16, min), max); an unreadable context is refused via mapAuthApiError with nothing created. temporaryPasswordLength moved from admin-user-endpoints.ts to the internal temporary-password-length.ts (not re-exported) so both doors share one sizing. Measured: the auto policy's no-channel fallback takes the same path (16 chars on main), and on main a maxPasswordLength below 16 fails the row outright. Deviation from the claim: Clause-② measures no (narrowing), not the expected yes, because a required member on an exported deps interface narrows what a host may pass; the title therefore carries ! and the changeset a BREAKING banner with ADR-0087 not-required (runtime-interface-only ...#IdentityImportDeps). Optional was rejected: its absent-branch fallback would be the 16-char defect for any host that forgets it. Worktree: the harness branch was checked out in the shared checkout, so that checkout was detached and the branch re-pointed at origin/main 05f7758f in a dedicated worktree (no unique commits existed).",
"tests": "Red first at 0c9118a4 (new admin-import-users-password-length.test.ts, real AuthManager + SQLite + the plugin's real mounted route): ① temporary min 24 'expected ... length of 24 but got 16'; ② auto, no email service, same 16; ④ max 14 row failed created 0; ③ default 16 green. After fix (71668cb6): 4/4 green, ①②④ sign-in/email 200. New unit pin: context read throws, 500 INTERNAL_ERROR, createUser and engine.update never called. Ablation via scripts/ablation-replace.mjs (anchor generateTemporaryPassword(temporaryLength) to generateTemporaryPassword(), 1 hit, blob a7809b32 to e056f1db): ①②④ red, ③ green; restored blob == HEAD, git diff HEAD empty; tests import src so no build leg. plugin-auth full vitest at 425cc7db code-identical: 196 files, 3726 passed, 10 skipped, VERDICT command-exit 0. plugin-auth typecheck exit 0 (after building plugin-auth, which tsconfig.examples reads), check:test-typecheck OK. eslint --no-inline-config packages/plugins/plugin-auth exit 0. test:repo not run locally (diff touches no repo-project test), declared to CI.",
"gates": "Local: dispatch-gates --commands at 425cc7db, 73 commands, each run with exit captured before any pipe; --ran reconciliation: 73 derived, 72 run (all exit 0), 1 NOT MEASURED: check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (needs every package dist; CI Lint and Repo Gates runs it). First-pass reds re-run green, all prerequisites: check-plugin-teardown-shape --self-test exit 3 (shallow clone; fetched pinned fixture 621a4876, rerun exit 0); check:dts-closure exit 1 (concurrent dogfood closure build rewriting dist; rerun exit 0); check:type-check-debt exit 3 (lock queue-timeout; rerun under lock exit 0, 26 raw errors, none above record). CI check-runs on #26018: in_progress at report time, not waited for.",
"dogfood_battery": "425cc7db · VERDICT command-exit 1 · Test Files 295/296 (3 skipped) · Tests 2522/2523 (18 skipped) · one failure: test/authz-probe-blind-spot.test.ts, packages/runtime/src/domains/i18n.ts control handleI18nRequest expected 3 got 4. Not this diff (packages/runtime untouched; the base 05f7758f file already holds 4, since 69fa23d6), so red on main's own battery; see out_of_scope_findings.",
"mcp_calls": "0",
"api_writes": "3 — POST /repos/objectstack-ai/objectstack/pulls (relay pr_create, draft), POST /repos/objectstack-ai/objectstack/issues/26018/assignees (same stroke), POST /repos/objectstack-ai/objectstack/issues/25932/comments (this report via post-stamped). Write budget: git push x3 to the harness branch (red test, fix, changeset), pr_create x1, report comment x1.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: main's push battery (Dogfood Regression Gate, packages/qa/dogfood test/authz-probe-blind-spot.test.ts) answers red: control handleI18nRequest in packages/runtime/src/domains/i18n.ts expected 3, got 4, at base 05f7758f, after 69fa23d6 (PR #25956) · evidence: dogfood run above at 425cc7db, file untouched by this diff · dedupe words: authz-probe-blind-spot handleI18nRequest i18n.ts census control moved"
]
}
Activity
objectstack-fleet commentedon Oct 26, 2026
Triage: first grade —
bug·security·priority:p2·domain:server·area:identity·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-26T04:54Z⛔ Not a claim, ⛔ not a dispatch.
securitymarks it as a credential-policy control.admin-import-users.tsreads the deployment'sminPasswordLength(and max), the same reader create-user'sgeneratePassworduses after PR fix(plugin-auth)!: admin create-user refuses a password outside the deployment's length range #25927. It can follow fix(plugin-auth)!: admin create-user refuses a password outside the deployment's length range #25927 in the same lane.Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Claim: PM loop round 6 · card thread
Session:
session_01R1q2xdtYgU6FKrZ7LgjPCC(the seat); the card thread issession_01A5wzhKHa98KsGQNHM9Z3TqAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-brnfvi(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#1Ruling-ref: the import door sizes a generated temporary password
min(max(16, minPasswordLength), maxPasswordLength)from the same auth context PR #25927 reads for create-user; ⛔ no second reader of the range; the default range keeps 16File surface:
packages/plugins/plugin-auth/**(admin-import-users.tsand the deps it is handed), its tests, one changeset. ⛔ Nopackages/spec/**(stop on breach; explain in the report)Container & model:
M,mode:thread, default model and effort (set at thread open)Clause-②: yes (expected; the dev measures)
Metadata-protocol: none
Responsibility:
POST /api/v1/auth/admin/import-usersmints a 16-character temporary password under a configured minimum of 24, and the user signs in with it (card #25932, measured on PR #25927 headab706f70)Thread-read: none
Premise: holds (read 2026-10-26T08:09Z at
origin/main05f7758f05: card open, unassigned,pm:queue, no other seat's claim; the dev reproduces red first)Serial constraints cleared: no open PR names #25932; the dev re-measures open PRs on its file surface before
pr_createobjectstack-fleet commentedon Oct 26, 2026
os-dev-report
{ "issue": 25932, "status": "done", "branch": "claude/project-thread-brnfvi", "pr": "https://github.com/objectstack-ai/objectstack/pull/26018", "session": "session_01A5wzhKHa98KsGQNHM9Z3Tq", "premise_still_valid": true, "summary": "IdentityImportDeps gains a required getAuthContext() (better-auth $context, the reader create-user holds); AuthPlugin's import route wires it to AuthManager.getAuthContext(). runAdminImportUsers reads password.config once before any write and sizes each temporary password min(max(16, min), max); an unreadable context is refused via mapAuthApiError with nothing created. temporaryPasswordLength moved from admin-user-endpoints.ts to the internal temporary-password-length.ts (not re-exported) so both doors share one sizing. Measured: the auto policy's no-channel fallback takes the same path (16 chars on main), and on main a maxPasswordLength below 16 fails the row outright. Deviation from the claim: Clause-② measures no (narrowing), not the expected yes, because a required member on an exported deps interface narrows what a host may pass; the title therefore carries ! and the changeset a BREAKING banner with ADR-0087 not-required (runtime-interface-only ...#IdentityImportDeps). Optional was rejected: its absent-branch fallback would be the 16-char defect for any host that forgets it. Worktree: the harness branch was checked out in the shared checkout, so that checkout was detached and the branch re-pointed at origin/main 05f7758f in a dedicated worktree (no unique commits existed).", "tests": "Red first at 0c9118a4 (new admin-import-users-password-length.test.ts, real AuthManager + SQLite + the plugin's real mounted route): ① temporary min 24 'expected ... length of 24 but got 16'; ② auto, no email service, same 16; ④ max 14 row failed created 0; ③ default 16 green. After fix (71668cb6): 4/4 green, ①②④ sign-in/email 200. New unit pin: context read throws, 500 INTERNAL_ERROR, createUser and engine.update never called. Ablation via scripts/ablation-replace.mjs (anchor generateTemporaryPassword(temporaryLength) to generateTemporaryPassword(), 1 hit, blob a7809b32 to e056f1db): ①②④ red, ③ green; restored blob == HEAD, git diff HEAD empty; tests import src so no build leg. plugin-auth full vitest at 425cc7db code-identical: 196 files, 3726 passed, 10 skipped, VERDICT command-exit 0. plugin-auth typecheck exit 0 (after building plugin-auth, which tsconfig.examples reads), check:test-typecheck OK. eslint --no-inline-config packages/plugins/plugin-auth exit 0. test:repo not run locally (diff touches no repo-project test), declared to CI.", "gates": "Local: dispatch-gates --commands at 425cc7db, 73 commands, each run with exit captured before any pipe; --ran reconciliation: 73 derived, 72 run (all exit 0), 1 NOT MEASURED: check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (needs every package dist; CI Lint and Repo Gates runs it). First-pass reds re-run green, all prerequisites: check-plugin-teardown-shape --self-test exit 3 (shallow clone; fetched pinned fixture 621a4876, rerun exit 0); check:dts-closure exit 1 (concurrent dogfood closure build rewriting dist; rerun exit 0); check:type-check-debt exit 3 (lock queue-timeout; rerun under lock exit 0, 26 raw errors, none above record). CI check-runs on #26018: in_progress at report time, not waited for.", "dogfood_battery": "425cc7db · VERDICT command-exit 1 · Test Files 295/296 (3 skipped) · Tests 2522/2523 (18 skipped) · one failure: test/authz-probe-blind-spot.test.ts, packages/runtime/src/domains/i18n.ts control handleI18nRequest expected 3 got 4. Not this diff (packages/runtime untouched; the base 05f7758f file already holds 4, since 69fa23d6), so red on main's own battery; see out_of_scope_findings.", "mcp_calls": "0", "api_writes": "3 — POST /repos/objectstack-ai/objectstack/pulls (relay pr_create, draft), POST /repos/objectstack-ai/objectstack/issues/26018/assignees (same stroke), POST /repos/objectstack-ai/objectstack/issues/25932/comments (this report via post-stamped). Write budget: git push x3 to the harness branch (red test, fix, changeset), pr_create x1, report comment x1.", "open_questions": [], "out_of_scope_findings": [ "class: a · reach: main's push battery (Dogfood Regression Gate, packages/qa/dogfood test/authz-probe-blind-spot.test.ts) answers red: control handleI18nRequest in packages/runtime/src/domains/i18n.ts expected 3, got 4, at base 05f7758f, after 69fa23d6 (PR #25956) · evidence: dogfood run above at 425cc7db, file untouched by this diff · dedupe words: authz-probe-blind-spot handleI18nRequest i18n.ts census control moved" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 26, 2026
Landed
PR #26018 merged at 2026-10-26T10:24Z by the merge queue as
e3e936e5a1onmain(Fixes #25932; ACCEPT6412625702).git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26018-*'origin/mainby contentgit cat-file -e origin/main:packages/plugins/plugin-auth/src/temporary-password-length.tsgit merge-base --is-ancestor e3e936e5a1 origin/mainState: this card closed by
Fixes(completed);pm:dispatchedremoved.Generated by Claude Code