Skip to content

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

Closed

Description

@objectstack-fleet

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.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    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.


    Generated by Claude Code

  2. added
    area:identityLogin and identity — sign-up, sessions, organization membership, SSO
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    on Oct 26, 2026
  3. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    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/main 05f7758f05: 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

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 26, 2026
  5. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  6. objectstack-fleet commented on Oct 26, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed

    PR #26018 merged at 2026-10-26T10:24Z by the merge queue as e3e936e5a1 on main (Fixes #25932; ACCEPT 6412625702).

    Reading Probe Result
    Queue membership git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26018-*' 0 refs
    origin/main by content git cat-file -e origin/main:packages/plugins/plugin-auth/src/temporary-password-length.ts present
    Ancestry git merge-base --is-ancestor e3e936e5a1 origin/main holds

    State: this card closed by Fixes (completed); pm:dispatched removed.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

area:identityLogin and identity — sign-up, sessions, organization membership, SSObugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions