Skip to content

Security bootstrap's first-user carve-out promotes the oldest authenticable user in an unordered 50-row sys_user window: with seeded credentials the winner depends on the database driver (sqlite promoted the job seeker) #40

Description

@os-sam

Found while delivering #27 on claude/issue-27-signable-personas. The PR ships a workaround; the defect is upstream (@objectstack/plugin-security 17.3.0) and should be relayed to objectstack-ai/objectstack — filed here the way #18 was filed for objectstack#16518, because I could not verify write access to the upstream tracker from this seat.

The rule as implemented

Under the single tenancy posture the security bootstrap's seedDefaultPermissionSets still runs the first-user carve-out (plugin-security dist/index.js, the block ending in first user promoted to platform admin):

  1. find('sys_user', {}, 50) — an unordered window of at most 50 users;
  2. filter out system rows, sort by created_at ascending;
  3. the first user in that window that has a sys_account row receives the legacy unscoped admin_full_access grant, and claimSeedOwnership hands it ownership of every seeded row.

Until #27, no seeded user had a sys_account, so step 3 found nobody ("human user rows exist but none can authenticate — platform admin NOT promoted"). The moment a seed provisions credentials, whoever the driver returns first in that window wins.

Measured

Same artifact, OS_PLATFORM_OWNER_EMAIL=admin@objectos.ai, seven credentialed users, the platform owner inserted first with id usr_ats_platform_owner:

driver window order promoted
memory insertion order admin@objectos.ai (owner)
sqlite (default) by id — usr_ats_c01 … usr_ats_c50, owner not in the window candidate01@mail.example — the job-seeker persona

Log line on sqlite, verbatim: [security] first user promoted to platform admin: candidate01@mail.example. That seeker then also owned every seeded row through claimSeedOwnership.

Workaround shipped in #27's PR

The owner row is pinned so that it wins under every ordering at once: id usr_ats_0_owner (collates before every other user id), inserted first, created_at a year older than every other user. Re-measured on both drivers: the owner is promoted on both. This is a seed-side pin of a rule the seed should not have to know about, and it holds only as long as no other seeded user sorts first on all three axes.

What upstream should probably do (one of)

  • Prefer the configured OS_PLATFORM_OWNER_EMAIL address when it is set and registered, instead of promoting anyone else — the config anchor already exists for the walled posture.
  • Order the query by created_at server-side across all users rather than sorting a 50-row window client-side.
  • Skip the legacy promotion entirely when a configured owner has standing.

Filed without a label for triage; no Blocked-by: — the workaround lands with #27.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions