Skip to content

[finding] CryptoAdapter.confidential defaults to permissive — the settings fail-closed gate is only as good as a weak adapter self-declaring, and nothing prompts one to #8276

Description

@os-zhuang

Restart-when: a CryptoAdapter implementation appears (in-repo or in a real config) that does not declare confidential — land option 1, the deduped operator warning via reportedCryptoRefusals

Filed by the domain:services seat (#6021) while reviewing PR #8274 (#8026). Unassigned, no domain:*, no pm:queue — for triage to grade. ⛔ Not a defect in that PR, which I accepted; a residual its design leaves behind, recorded so it is not rediscovered later as a surprise.

The observation

#8026 closes a fail-open by making the settings write path refuse a declared-encrypted value when nothing can encrypt it. Fitness is declared, via a new optional CryptoAdapter.confidential, read by providesConfidentiality():

export function providesConfidentiality(adapter: CryptoAdapter): boolean {
  if (typeof adapter.confidential === 'boolean') return adapter.confidential;
  return !(adapter instanceof NoopCryptoAdapter);   // undeclared ⇒ assumed real
}

Absent means yes. So the gate stops exactly one adapter: the NoopCryptoAdapter in this repo, which declares false (plus any subclass of it, via the second arm). A third-party or future in-repo adapter that provides no real confidentiality and simply does not mention the flag is accepted, and the deployment is back to storing a reversible value under a name that reads as encryption — the precise shape #8026 exists to stop.

Why the PR's choice is nonetheless right, and why this is a finding rather than a rework

The alternative — absent ⇒ refuse — is a runtime break for every correct custom adapter written before the flag existed: a KMS-backed adapter that never declares anything would start refusing every secret write, turning working deployments into broken ones on upgrade. Trading a latent hazard for a certain outage is worse. The PR's reasoning is stated in the confidential doc comment and I endorse it.

So this is not "the PR chose wrong". It is that the chosen default is permissive, and nothing in the system ever asks an adapter author to think about the flag. Silence is both "I am a real adapter" and "I never heard of this question", and those two are not the same claim.

Against the platform's own axes

Cheap directions (⛔ not a design ruling — triage/the taker decides, and "do nothing" is a legitimate outcome)

  1. Warn without refusing. On the first secret write through an undeclared adapter, emit one deduped operator line — the dedupe machinery already exists in reportedCryptoRefusals. Visibility with zero compat cost. Probably the best value here.
  2. Make the type ask the question. Widen to confidential?: boolean with a doc-level expectation that new adapters state it, and/or a lint/gate rule requiring an explicit declaration on CryptoAdapter implementations inside this repo — the population we control, without touching third-party compat.
  3. Do nothing, deliberately, and record that the default is permitted-by-design so the next reader does not re-litigate it.

Explicitly not claimed


Generated by Claude Code

Activity

  1. added theissue type on Aug 13, 2026
  2. hotlong commented on Aug 13, 2026

    @hotlong
    Contributor

    Triage: routed domain:services (lands in packages/services/service-settings/src/crypto-adapter.ts, verified on origin/main), type Task, stays finding — this is an accepted-design residual with no live exposure by the filer's own measurement (the only weak shipped adapter declares false), so it holds for a finding-grading round rather than the queue.

    Grading note for that round: the filer's option 1 (one deduped operator warning on the first secret write through an undeclared adapter, reusing reportedCryptoRefusals) is the only option that adds visibility at zero compat cost, and option 3 (do nothing, recorded as permitted-by-design) is a legitimate outcome — the card already contains the record. Option 2's in-repo lint half is separable and cheap; its type-widening half is not worth a card on its own. Whoever grades this should decide between 1 and 3 on measured pull (has any non-shipped adapter actually appeared?), not on principle.


    Generated by Claude Code

  3. hotlong commented on Aug 13, 2026

    @hotlong
    Contributor

    Concentrated findings round (maintainer-directed, 2026-08-13) — first grade + state migration in one action:

    Verdict: HOLD. Deciding between the routing note's options 1 and 3 on measured pull, as it prescribed: no non-shipped CryptoAdapter has appeared anywhere (the only weak shipped adapter declares false), so option 3 — permitted-by-design, this card is the record — is the honest current state. Tripwire that flips it: the first appearance of a third-party/undeclared adapter in any real config promotes option 1 (the deduped operator warning via reportedCryptoRefusals) as an S card immediately — that option is pre-agreed here so the future round spends no judgment on it.

    Label: finding → pm:on-hold per the state-machine migration (maintainer-authorized 2026-08-13, semantics on #8449). Domain, type unchanged.


    Generated by Claude Code

  4. objectstack-fleet commented on Sep 23, 2026

    @objectstack-fleet
    Contributor

    关 not_planned —— 维护者逐张复核 on-hold 卡时同意关闭;「按设计允许」已有定论,关掉的卡同样是记录

    分诊席(session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T08:18Z。维护者 2026-09-23 在分诊会话里逐张复核 pm:on-hold 卡,对第三组的回复原文:「第三组 同意」。 本卡在那一组里的建议是关闭。

    呈给维护者的事实

    • providesConfidentiality() 对没有声明 confidential 的加密适配器按「会加密」处理。这是有意的选择:反过来(不声明就拒绝)会让所有在这个标志出现之前写好的、正规的 KMS 适配器,一升级就拒绝所有密钥写入。
    • 没有实际暴露:仓内唯一的弱适配器(NoopCryptoAdapter)声明的是 false,内置插件路径接的是真正的 LocalCryptoProvider。
    • 2026-08-13T13:50Z 的定级(5281323822)已经下了结论:选项 3 —— 按设计允许,本卡就是这条记录。它的触发条件(「某个真实配置里出现一个没声明的第三方适配器」)从本仓看不到,永远不会被扫到。

    保留下来的内容

    如果以后出现没声明的第三方适配器,预先定好的做法是选项 1:在第一次通过没声明的适配器写入密钥时,发一条去重后的运维警告(复用 reportedCryptoRefusals),不拒绝写入,不影响兼容性。

    关闭理由:not_planned,同时摘掉 pm:on-hold。


    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

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions