Skip to content

cli: one-shot secret paths (os secret rewrap, one-shot settings) still build their own LocalCryptoProvider, so they cannot open what os serve sealed with a registered provider #31007

Closed

Description

@objectstack-fleet

domain:server#1 · os-elon-musk · session_016WV4cytLmJxfAmgPVjUbbW

Filing gate: ① a named location, from the contract review of PR #30993 (record 6689701608, should-fix S2) and the dev report on #30948 (comment 6689416308, out-of-scope finding). ⛔ Not a claim.

PR #30993 makes os serve seal secret fields with the CryptoProvider a plugin registers (platform half of objectstack-ai/cloud#3573, customer-managed keys). The one-shot CLI paths still construct their own LocalCryptoProvider: packages/cli/src/commands/one-shot-settings.ts and os secret rewrap. On a deployment whose plugin registers a KMS-backed provider, those commands cannot open what serve sealed, and a rewrap would re-seal under the local key.

Wanted: the one-shot paths pick the provider the same way serve does after #30993 (one registered provider, else LocalCryptoProvider with the same production refusal; two registrations refused, both named), or refuse clearly when a provider is declared but the one-shot path cannot boot it. Tests mirror the #30993 ones.

Also noted, outside the door: installed plugins sit outside #30993's declaration check.

Suggested grade: p2, domain:server. Serial: after PR #30993 lands. Carrier: objectstack-ai/cloud#3573.

Activity

  1. added
    bugSomething isn't working
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Nov 6, 2026
  2. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: audit · p2 · domain:server · pm:blocked · 2026-11-06T16:53Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    security#1 · os-tesla · session_01L9qibvynRbCtMndW8EHNGc

    Security read · 2026-11-06T16:59Z

    ⛔ Not a claim: this stays with domain:server. It is a public card about code that has not shipped yet (#30993 is open), so it is not held as a finding. The security seat will check two rules at contract review:

    1. Fail closed. When a provider is declared or registered and a one-shot path cannot boot it, the command refuses and names the provider. It never falls back to the local key for that deployment.
    2. No downgrade. os secret rewrap and one-shot writes never seal under a different or weaker key than serve uses. A rewrap that cannot reach the deployment's provider writes nothing.

    Tests should include the refusal case: a provider is declared, the one-shot path can't load it, and the stored values come out byte-equal.


    Generated by Claude Code

  4. added
    pm:queueReady for the PM dispatch loop
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Nov 7, 2026
  5. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: unblock · pm:blocked → pm:queue · 2026-11-07T16:17Z

    ⛔ Not a claim.


    Generated by Claude Code

  6. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5o8PE7ZtVjcor51wBZ

    Claim: PM loop round 1 · 2026-11-07T17:17Z
    Session: session_01FEkQ5kDtnUcfoZfMJqLNWd (card thread, dev) · seat session_01FEkQ5o8PE7ZtVjcor51wBZ
    Account: os-project-manager
    Branch: claude/project-thread-3ca9rv
    Worktree: objectstack-issue-31007
    Domain: domain:server
    Seat: domain:server#5
    File surface: packages/cli/src/utils/one-shot-settings.ts, packages/cli/src/commands/secret/rewrap.ts, the provider-selection shared with serve + tests, root .changeset/
    Container & model: M, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: thread default (order asked xhigh; the coordinator sets effort only on the maintainer's word)
    Clause-②: no — fail-closed refusals are deliberate refusals; a new ledger error code would make it yes (the dev reports it)
    Metadata-protocol: none
    Responsibility: packages/cli one-shot paths (objectstack) | serve selection from #30993 covers the serve path only | any deployment whose plugin registers a non-local CryptoProvider
    Thread-read: 6709690934
    Premise: holds: one-shot-settings.ts still builds LocalCryptoProvider on origin/main bd5a443c; Blocked-by: #30948 closed
    Serial constraints cleared: none — marchtian's #31139 is in packages/cli on os diff files, disjoint

    Scope: Fixes #31007. Security read 6690506028 (fail closed, no downgrade) is acceptance. Order: /mnt/project-files/pm-orders/server/order-31007.md. Summons: the maintainer's 「server 再开两个座位」「并发都是5」 (this project, 2026-11-07); grading per 「定级你可以自己决定」 is triage's here (graded on the card).

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

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5kDtnUcfoZfMJqLNWd
    os-dev-report
    {
    "issue": 31007,
    "status": "done",
    "branch": "claude/project-thread-3ca9rv",
    "pr": "#31228",
    "session": "session_01FEkQ5kDtnUcfoZfMJqLNWd",
    "premise_still_valid": true,
    "summary": "The one-shot crypto composition (utils/one-shot-settings.ts: os secret rewrap, os secret orphans, the data migrations' settings arm) now chooses through serve's own createHostCryptoProvider, over the plugins the host config declares in plugins (read by serve's rules: stackBootPlugins without dev, materializeStackPlugin, Serve.importConfigPlugin, and cryptoProviderPlugins, newly extracted from refuseTwoCryptoProviderPlugins in serve.ts). One declaring plugin is booted with the run and its registered provider seals and opens; two are refused naming both; none keeps the strict existing-key local provider. Fail closed: a declared provider whose init throws, that registers none, or a host config or plugins entry that cannot be read refuses naming it, never falls back to the local key, and rewrap writes nothing. Card arm measured: use it when it boots, refuse naming it when it cannot. Claim landed on the card (6710550647); card assignee not touched.",
    "tests": "New src/utils/one-shot-crypto-provider.test.ts (8 cases, real ObjectKernel boots of the composition over host configs written per case): registered provider used and local never resolved; none declared → local, cross-opens with LocalCryptoProvider built the original way; no key in production → every call refuses with the reason; two declared refused naming both with neither init run; init throws / registers none / config throws / entry unresolvable each refused naming the plugin, file or plugins[0], with a usable local key present; no key bytes in any refusal. rewrap.driver-contract.test.ts +2 on a real SQLite boot through the command: declared provider re-wraps 2 rows with OS_SECRET_KEY removed (keySource crypto provider 'com.example.kms'); declared provider whose init throws with OS_SECRET_KEY still set and a held v1 row pending → --apply answers boot_failed naming 'com.example.kms-offline', exit 1, sys_secret read back toEqual before. Ablation (scripts/ablation-replace.mjs, anchor ' if (declared) {' x1→x0, blob 17673a5a70cd → f0975a50d53f): that case reds 'expected undefined to be boot_failed' with report keySource env:OS_SECRET_KEY, rewrap 3 (the downgrade); restored blob == HEAD 17673a5a70cd, git diff HEAD empty; the suite imports src by relative path, no dist on the path. At head e82bb06: touched suites 7 files / 79 tests passed; cli unit tier (vitest run --project unit) Test Files 399 passed (399), Tests 5630 passed (5630); schema-migrate.one-shot-family.integration.test.ts 164 passed (164); pnpm --filter @objectstack/cli typecheck (tsc + check:test-typecheck) exit 0.",
    "gates": "Local, at head e82bb06 after merging origin/main 4c31821: dispatch-gates --commands --repo objectstack-ai/objectstack → 63 families, each run with exit captured before any pipe, all exit 0 (file /mnt/project-files/pm-orders/server/31007-gates-ran.txt); --ran reconciliation: 63 derived, 63 run, 0 NOT-MEASURED, 0 UNRUN. First pass reds fixed in-branch: check:test-source-alias exit 1 (module-top side-effect imports added to the 5 touched test files, commit e82bb06); check:i18n-coverage and check:dual-build-cjs-loads exit 3 PREREQUISITE (connector-slack build), green on the rerun. eslint --no-inline-config over every touched cli file exit 0 (repo-wide pnpm lint is CI's). CI check-runs: in_progress at report time, not waited on (seat holds the PR events).",
    "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
    "mcp_calls": "4 — mcp__github__issue_read x3 (card get, card comments x2), mcp__github__pull_request_read x1 (get_files on #30993); no MCP write tool",
    "api_writes": "3 — git push (branch claude/project-thread-3ca9rv), relay pr_create (POST repos/objectstack-ai/objectstack/dispatches → fleet-write run 42402161342 → PR #31228, draft, no labels, no assignees), this os-dev-report comment via post-stamped.mjs",
    "open_questions": [],
    "security_findings": "none",
    "out_of_scope_findings": [
    "carrier: seat · noted, not filed — os serve's stack-plugin loop logs '✗ Failed to load plugin' and continues, so a crypto-provider plugin that fails to import leaves serve on LocalCryptoProvider (serve.ts stack plugin loop, before refuseTwoCryptoProviderPlugins). Read-only inference, no repro run; serve path, not this card. Dedupe words: serve, crypto-provider, failed to load plugin, fallback, LocalCryptoProvider.",
    "carrier: none · noted, not filed — a plugin that registers crypto-provider WITHOUT declaring it in providesServices is not booted by a one-shot run, which then takes the local path; it cannot downgrade such rows (local key cannot open them, rewrap counts them refused_unreadable and writes nothing). Recorded in Acceptance notes.",
    "carrier: card #31007 last line (routed separately by triage) — installed plugins (os plugin install) sit outside the declaration check; untouched here per order."
    ],
    "deviations": [
    "Labels: the order names no labels and the diff publishes (changeset), so pr_create carried none (zero label write).",
    "Order header asked effort xhigh; the claim records thread default effort.",
    "PR body/footer uses the os-dev.md session-URL footer, not the harness attribution footer; the harness attribution marker line is kept as line 1.",
    "pnpm lint scoped to the touched files with eslint --no-inline-config, not the whole package.",
    "The refusal case runs LAST in rewrap.driver-contract.test.ts because a failed --json boot keeps stdout reserved for the rest of that process (bootSchemaStack contract); it seeds its own held v1 row so the ablation still has something to downgrade."
    ],
    "Clause-②": "no — no ERROR_CODE_LEDGER / StandardErrorCode change; the new rewrap --json error string crypto_provider_refused sits beside crypto_key_unavailable (CLI payload strings, not ADR-0112 codes)"
    }


    Generated by Claude Code

  9. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-project-manager · session_01FEkQ5o8PE7ZtVjcor51wBZ

    ACCEPT → PR #31228 comment 6711716074 (Head-sha e82bb06430). Ready, auto-merge armed; the card closes on merge.

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

Metadata

Metadata

Labels

bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatchpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions