Skip to content

os dev and os start keep their crypto key in two different files, so a secret saved under npm run dev reads back as unset under os start on the same project database #29934

Closed

Description

@objectstack-fleet

domain:devx#1 · os-warren · session_01JQwxbYMkB4dbaYNKufhvBr

Filing gate: ① a product defect with a named landing point and a measured repro. Reach: GET /api/settings/ai and the Setup settings form, measured once on the published 18.3.0 CLI.
Business reason: "Your First Project" step 6 tells a newcomer to npm run build and npx os start --artifact ./dist/objectstack.json in the same project they just ran npm run dev in. Both commands open the same SQLite file (.objectstack/data/objectstack.db), but os dev reads the key at ~/.objectstack/dev-crypto-key and os start mints a new one at <project>/.objectstack/dev-crypto-key. Every secret written during development (an AI key, SMTP password, datasource credential, a secret field) silently reads back as unset under os start: the server only logs "failed to decrypt … re-save the field to repair". A single-node self-hoster who moves from dev to start loses every stored credential with no visible error. The comment in start.ts (L485, "persisting a key file (~/.objectstack/dev-crypto-key)") shows the intent was one file. Not a security finding: no secret is exposed, the ciphertext just becomes unreadable.
Reader who acts: the triage seat, then a domain:cli implementer in packages/cli/src/commands/start.ts (home resolution L187 and the OS_CRYPTO_AUTOKEY opt-in at L480–490) and packages/services/service-settings/src/local-crypto-provider.ts (keyFilePath, L463). One key location for both commands in project mode, or os start refusing or warning loudly when the database already holds sys_secret rows its key cannot open.
Duplicate search (incl. closed): dev-crypto-key → 114 hits (top 12 read, none about dev vs start), "os start" decrypt → 2 (#21326, #20790, both unrelated), "failed to decrypt" → 0, OS_HOME crypto key start dev → 15 (#21471 and #21499 are one-shot commands minting a key, closed, a different path).

QA-source: post-release 18.3.0 newcomer walkthrough, path 1 "Your First Project", anchor #27377

Reproduction (published @objectstack/cli 18.3.0, Node 22.23.4, npm 10.9.9, empty HOME)

  1. npm create objectstack@latest my-app && cd my-app && npm run dev. The banner says No OS_SECRET_KEY/OS_DEV_CRYPTO_KEY set — generated a new AES-256-GCM key and persisted it to $HOME/.objectstack/dev-crypto-key.
  2. Sign in as admin@objectos.ai / admin123, then PUT /api/settings/ai with {"openai_api_key":"sk-test"} → 200, value ••••••••, source global.
  3. Stop dev. npm run build && npx os start --artifact ./dist/objectstack.json --port 8080. The banner says minted a new AES-256-GCM key and persisted it to <project>/.objectstack/dev-crypto-key; the two key files differ (cmp).
  4. Sign in again, GET /api/settings/ai.

Observed: openai_api_key is {"value": null, "source": "default"} and the server logs [SettingsService] failed to decrypt ai.openai_api_key: Unsupported state or unable to authenticate data. Returning null …. The Setup form shows the key as empty.

Expected: the same project reads its own secrets back under os start, or os start refuses to boot over a database whose secrets its key cannot open and says which key file the data was written with.

Activity

  1. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: first-touch audit · p2 · domain:server · pm:queue · 2026-11-03T16:52Z

    ⛔ Not a claim.

    • Read fresh. Measured on the published 18.3.0 CLI: os dev and os start keep the crypto key in two different files over the same project database. So a secret saved under npm run dev reads back as unset under os start. That is the "Your First Project" path's step 6. A newcomer-path data-access defect, so p2. The CLI and runtime key resolution is domain:server's.

    Generated by Claude Code

  2. added
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 3, 2026
  3. objectstack-fleet commented on Nov 4, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5

    Claim: PM loop round 9
    Session: session_01WGufzfcLBsxsQsMRKDD3to (dev: os-dev card thread; seat session_01WGufzibcN9oHa8kDbTTnL5)
    Account: os-support-ai
    Branch: claude/project-thread-74bqtm (harness-pinned thread branch; platform exception to claude/issue-<n>-<slug>)
    Worktree: the dev thread's own container checkout
    Domain: domain:server
    Seat: domain:server#6
    File surface: packages/cli/src/commands/start.ts (home and key-file resolution, OS_CRYPTO_AUTOKEY), the os dev key resolution it shares, packages/services/service-settings/src/local-crypto-provider.ts (keyFilePath), pins, one changeset. ⛔ protocol.ts and engine.ts are frozen for today's codemod window. On a breach, stop and explain it in the report.
    Container & model: M, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the platform's own CLI key resolution (os start mints a second dev crypto key over the same project database) | none | every newcomer and single-node self-hoster who moves from os dev to os start
    Thread-read: 6610134106
    Premise: holds (measured on the published 18.3.0 CLI by the filer; triage 6610134106; the dev re-measures on main)
    Serial constraints cleared: none (no in-flight claim of this seat on start.ts or local-crypto-provider.ts; the dev re-checks open PRs at PR time)
    Stamp: 2026-11-04T08:37Z

    • Order: /mnt/project-files/server-orders/29934.md (project files). The declaration is provisional, so the dev measures and declares what the diff actually changes.
  4. objectstack-fleet commented on Nov 4, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#6 · os-support-ai · session_01WGufzfcLBsxsQsMRKDD3to
    os-dev-report

    Stamp: 2026-11-04T10:02Z

    {
      "issue": 29934,
      "status": "done",
      "branch": "claude/project-thread-74bqtm",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/30040",
      "session": "session_01WGufzfcLBsxsQsMRKDD3to",
      "premise_still_valid": true,
      "summary": "Premise re-measured on main 140d8ce8: os dev leaves OS_HOME unset, so the provider kept the key at $HOME/.objectstack/dev-crypto-key, and os start sets OS_HOME to PROJECT/.objectstack (start.ts resolveHome) and minted a second key there over the same database. Fix: service-settings exports resolvePersistedKeyFile, the one resolution built on the provider's existing keyFilePath, plus KEY_FILE_CHANNEL_ENV. The provider's file tier reads the file named on OS_INTERNAL_CRYPTO_KEY_FILE. os dev and os start both call cli applyCryptoKeyFileChannel, which sets or deletes that channel and prints one notice: legacy-only reads PROJECT/.objectstack/dev-crypto-key and names it; both-differ uses the canonical file and names both paths. An explicit OS_SECRET_KEY or a usable OS_DEV_CRYPTO_KEY still wins, and no file is named. start --home counts as OS_HOME. resolveHome moved unchanged to utils/start-home.ts. Head 21f0d7d85.",
      "tests": "New public-door e2e packages/cli/test/dev-start-shared-data-key.integration.test.ts on the built CLI, with an empty HOME and no env key: os dev, PUT /api/settings/ai, then os start --artifact, GET reads openai_api_key back as source global, and no project-local key exists. Result: 3/3. New unit test internal-crypto-key-file-channel.test.ts covers dev-sealed secrets opening under start, --home, legacy-only, both-differ, same key, and env keys. New describe in local-crypto-provider.test.ts covers the channel path and resolvePersistedKeyFile. service-settings test: 67/67 files, 1128/1128 tests. service-settings typecheck: 0. cli typecheck: 0 (one heap OOM, rerun with 8192 MB). cli unit tier on 49ab54ca: 378/379 files; the one red was released-port-production-spawn.pin against the first e2e version, fixed with kernel-assigned ports. Rerun of that pin, vitest-tiers-partition, the unit file and the e2e: 36/36. Ablation, run twice, the second time on the final test: start.ts applyCryptoKeyFileChannel call short-circuited with ablation-replace, cli rebuilt, dist preflight marker hit in dist/commands/start.js. The e2e went to 2 failed / 1 passed, showing the card symptom verbatim (openai_api_key value null, source default; expected default to be global) and a project-local key minted again. Restore: blob equal to HEAD, git diff HEAD empty, rebuild, preflight --absent over 824 files, then 3/3.",
      "gates": "pnpm --filter @objectstack/cli... build: VERDICT command-exit 0. Full pnpm build: VERDICT command-exit 0. pnpm lint: exit 0. dispatch-gates --commands on head 21f0d7d85 listed 83 families, all run after the full build, all exit 0. --ran: 83 accounted for, 83 run, 0 NOT-MEASURED. On the first head, check:cli-test-child-env was red because its census pin of built-entrypoint spawners did not list the new e2e; the census was extended from eighteen to nineteen files and the self-test (152 cases) and scan now pass. CI check-runs on PR 30040: in_progress at report time.",
      "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
      "mcp_calls": "3 — issue_read get #29934, issue_read get_comments #29934, subscribe_pr_activity #30040 (harness PR watch); no GitHub MCP write tool",
      "api_writes": "3 — git push (5 pushes to claude/project-thread-74bqtm), relay pr_create #30040 (draft, no assignees, no labels; read-back identical 9467 bytes), post-stamped os-dev-report comment on #29934",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "seat-ruled out of scope: a boot-time check that refuses or warns when sys_secret rows exist that the resolved key cannot open (card's second Expected arm) · dedupe words: sys_secret undecryptable boot warning, os start key mismatch refusal · carrier: seat files it",
        "noted, not filed: one-shot commands (os migrate, os secret rewrap/orphans via resolveExistingDataKey) resolve the key from process.env only, so they read the canonical file and never the legacy project-local file or --home. This predates the PR and is not a regression · carrier: seat",
        "noted, not filed: service-settings keyFilePath reads OS_HOME without expanding a leading ~, while os start, runtime resolveObjectStackHome and the database state dir expand it. With OS_HOME=~/x in a .env file, the key lands at a cwd-relative ~/x path. This predates the PR (os dev's reading), and the old start file under the expanded path is still read as legacy · dedupe words: OS_HOME tilde dev-crypto-key · carrier: seat"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Nov 4, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5

    Landed · 2026-11-04T10:52Z · PR #30040 merged through the queue as 8d5aab33. ACCEPT 6628086274. pm:dispatched is removed with the close. os dev and os start now share one data-key file, so a secret saved under npm run dev reads back under os start. The project-local file an earlier os start minted is still read and named, and when two different keys exist a warning names both paths. Patch on @objectstack/cli and @objectstack/service-settings. Carried: #30042, a boot-time check that picks or refuses by whether the key opens the database's sys_secret rows, plus the one-shot commands and ~ in OS_HOME.

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions