Skip to content

feat(auth)!: refuse to boot multi-replica without an explicit OS_AUTH_SECRET (#3814 F3e) #31600

Closed

Description

@objectstack-fleet

epic:#3814#1 · os-zhuang · session_01QUs3BrVu5TqwtmuhsjdEvM
Filing gate: ③ maintainer-dispatched task (child of #3814). Maintainer, (2026-11-08, 16:25 UTC): 「继续,现在派第二批」 (gate 1 after the card 0 inventory, cloud#3868 comment 6723322368).

Parent: #3814 (rulings R0-R6 on comment 6722625146).

Problem (smoke, card 0)

With OS_SECRET_KEY set but no OS_AUTH_SECRET, a third replica booted and minted /data/auth-secret on its own volume; the session cookie from r1 was UNAUTHENTICATED on r3. OS_SECRET_KEY is already refused when missing (LocalCryptoProvider); the auth secret is not. The kit's install.sh and the Helm secret carry it, so only hand-rolled deploys are exposed.

Do

  1. When replicas>1 is declared on the Redis driver and OS_AUTH_SECRET is unset, refuse the boot with one line naming the variable and how to generate it, mirroring the data-key refusal.
  2. Single node keeps auto-minting exactly as today.
  3. Tests: declared multi-replica + Redis + no auth secret = refused with the message; single node = minted as today.

Rules that bind every #3814 child (verbatim rulings on the parent)

R1 peer round-robin. R2 Redis required for multi-replica only. R3 「同意:每张卡都只在"声明多副本并接上 Redis 驱动"时才生效」: the single-node default path is unchanged, multi-replica behaviour switches on only behind declared replicas>1 AND a connected Redis cluster driver, the split-brain guard is never weakened, and the deploy README "multi-node unsupported" line stays until the final gate.

Done when

PR merged; a multi-replica boot without the auth secret refuses; single node unchanged.

Activity

  1. added
    enhancementNew feature or request
    pm:queueReady for the PM dispatch loop
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Nov 8, 2026
  2. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: confirmed · domain:server · p2 · pm:queue · 2026-11-08T17:01Z

    ⛔ Not a claim.


    Generated by Claude Code

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

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmPF1skfu2kx89BEXZi

    Claim: PM loop round 1 · #31600 · 2026-11-08T21:34Z · feature (p2; #3814 F3e)
    Session: session_01ADHkmPF1skfu2kx89BEXZi
    Account: os-steve
    Branch: claude/project-thread-0ndnrp (the card thread's harness-pinned branch; platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-31600
    Domain: domain:server
    Seat: domain:server#1
    File surface: the auth-secret mint site (plugin-auth / runtime, measured in step 0), its tests, a changeset (stop on breach; explain in the report)
    Container & model: S/M, mode:thread, model: default tier, effort: high
    Clause-②: no (narrowing)
    Metadata-protocol: none
    Responsibility: n/a — the multi-replica path is new under #3814
    Thread-read: none
    Premise: holds (read at 5d1382a5: no refusal reads OS_AUTH_SECRET on a declared multi-replica boot)
    Serial constraints cleared: it reuses PR #31624's multi-replica predicate (merged). Seat 4's #31599 / #31602 share that predicate but not this file. R3 binds: single node keeps auto-minting.
    Order: standard order + server-seat1/31600-delta.md (project files).

  5. objectstack-fleet commented on Nov 8, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmNwSCrFC9AxLfZsBxf
    os-dev-report

    {
     "issue": 31600,
     "status": "done",
     "branch": "claude/project-thread-0ndnrp",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/31718",
     "session": "session_01ADHkmNwSCrFC9AxLfZsBxf",
     "premise_still_valid": true,
     "summary": "os start now refuses, before generating anything, to mint its own sign-in secret when the deployment declares several replicas (OS_EXPECT_MULTI_NODE=true / OS_CLUSTER_REPLICAS>1) over the redis cluster driver and no OS_AUTH_SECRET is set: exit 1 on one line naming OS_AUTH_SECRET and openssl rand -hex 32, no value in it. Step 0: the only mint site is os start (readOrCreateAuthSecret, home/auth-secret), not plugin-auth or runtime; on base fc4c3072 the OS_EXPECT_MULTI_NODE=true + redis path booted and minted (narrowing measured). The predicate is the shared multi-replica rule in its existing pre-boot form, sharesCloudBindingCredential(env, OS_CLUSTER_DRIVER) from @objectstack/cloud-connection (a CLI dependency already), lazily imported; single node and replicas over any other driver are unchanged. Three doc sentences that said os start always generates a secret are corrected.",
     "tests": "head c96d6e73. cli vitest start-deployment-durability.test.ts + test/start-deployment-durability.integration.test.ts + serve-cluster-host-resolution.test.ts: 3 files 67 passed (VERDICT command-exit 0). CLI unit tier: 405 files 5728 passed (VERDICT 0). cli typecheck VERDICT 0. Ablation (refusal return made false, marker ABLATION_31600 via ablation-replace.mjs): anchor 1->0, blob e9d80387->d55df8e4, rebuilt, dist-preflight marker in 1 built file; 2 failed/33 passed (unit 'refuses when several replicas are declared over the redis driver' expected false to be true; spawn 'OS_AUTH_SECRET unset: exit 1' got the split-brain guard instead); restored blob==HEAD, git diff HEAD empty; rebuilt, --absent over 904 files, clean tree, 35/35. Real Redis (7.0.15) probes: head no secret -> exit 1 + line + 0 files; head secret set -> redis driver + redis cache + ready, 0 files; two head replicas, one secret, shared sqlite -> r1 cookie reads the session on r1 and r2; differing secrets -> r2 does not.",
     "gates": "dispatch-gates --commands on c96d6e73: 99 derived, all 99 exit 0 (check:type-check-debt first exit 3 = lock queue-timeout behind dogfood, then exit 0 under the lock); --ran: 99 run, 0 NOT-MEASURED (DERIVED zero). check-adr-0087-registration: 1 declared-breaking changeset, [BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription). Lint: cli has no lint script; npx eslint --no-inline-config packages/cli exit 0. CI check-runs: in_progress at report time (not awaited).",
     "dogfood_battery": "c96d6e73 · VERDICT command-exit 0 (held 32m17s) · Test Files 440 passed, 3 skipped (443) · Tests 3652 passed, 41 skipped (3693) · no failure",
     "mcp_calls": "5 — reads only: mcp__github__issue_read x3 (31600 get, get_comments x2), mcp__github__pull_request_read x2 (31624 get, 31718 get)",
     "api_writes": "2 — POST /repos/objectstack-ai/objectstack/dispatches (relay pr_create, PR 31718, assignees os-steve, read-back 12186 bytes identical); this os-dev-report comment via post-stamped.mjs. git push is not a REST write.",
     "open_questions": [
      {
       "question": "The pre-boot predicate is read from cloud-connection's sharesCloudBindingCredential, a function named for the cloud binding credential. Keep it, or lift a neutrally named pre-boot form?",
       "options": [
        "A keep: the same rule (declaresMultiNode AND redis), already the one pre-boot form, no new dependency or export",
        "B lift a neutral pre-boot predicate (e.g. in service-cluster) and re-point cloud-connection and os start at it; the CLI still cannot import a cluster package directly (serve-cluster-host-resolution.test.ts), so it needs a re-export from a package the CLI declares"
       ],
       "recommendation": "A, because B adds a public export (a widening) for a naming concern only; revisit if seat 4's #31599/#31602 need the same pre-boot form."
      }
     ],
     "security_findings": "withheld",
     "out_of_scope_findings": [
      "class: b · Seam: runtime:service-cluster checkMultiNodeAllowed (no gate registered) -> runtime:cli serve.ts gate call · contract: multi-node-gate.ts says a DECLARED multi-node topology with no gate registered is refused outright, but serve.ts asks the gate with Number(OS_CLUSTER_REPLICAS) only, so a topology declared by OS_EXPECT_MULTI_NODE=true (declaresMultiNode's other spelling) reaches the gate as undeclared and is allowed · reach: os start on open core with OS_EXPECT_MULTI_NODE=true OS_CLUSTER_DRIVER=redis booted the redis driver multi-node with no gate (measured on fc4c3072 and c96d6e73), while OS_CLUSTER_REPLICAS=2 is denied and then refused by the split-brain guard · dedupe words: multi-node gate, OS_EXPECT_MULTI_NODE, checkMultiNodeAllowed, fail closed, declared topology",
      "carrier: none · noted, not filed"
     ],
     "files_changed": [
      ".changeset/31600-multi-replica-auth-secret-refusal.md",
      "content/docs/deployment/cli.mdx",
      "content/docs/deployment/self-hosting.mdx",
      "packages/cli/src/commands/start.ts",
      "packages/cli/src/commands/start-deployment-durability.test.ts",
      "packages/cli/test/start-deployment-durability.integration.test.ts"
     ],
     "line_budget": "+163 / -4 over 6 files (S/M card)",
     "deviations": [
      "File surface: the claim named plugin-auth / runtime; step 0 measured the only mint site in packages/cli/src/commands/start.ts, so the change and its tests live there.",
      "Docs outside the claimed surface: cli.mdx (2 places) and self-hosting.mdx (1) said os start always generates a secret; this change made that false, so they are corrected in the same PR.",
      "A first head (fd977a59) declared @objectstack/service-cluster on the CLI; serve-cluster-host-resolution.test.ts refused it and the dependency was dropped (net zero in package.json and pnpm-lock.yaml). The first ablation ran on that implementation; the reported ablation is the second, on the final code.",
      "A dogfood run started on the stale head fd977a59 was stopped (own PIDs) and re-run once on the final head.",
      "check:type-check-debt answered exit 3 (lock queue-timeout) in the gate sweep and was re-run under the lock: exit 0.",
      "The cli package has no lint script: lint = root eslint on packages/cli with --no-inline-config."
     ]
    }

    Stamped 2026-11-08T23:22Z.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

domain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions