fix(spec): redact mongo CSFLE kmsProviders key material on datasource reads - #13755
Conversation
… reads The five client-measured positions inside the options passthrough (aws.secretAccessKey, aws.sessionToken, azure.clientSecret, gcp.privateKey, local.key under options.autoEncryption.kmsProviders) join passthroughSecretPaths, with the mongodb@7.5.0 measurement quoted in the table docblock (both with and without the optional mongodb-client-encryption dependency). Pins: all five redacted with redactedKeys entries, unmeasured neighbours untouched (negative control), and an untouched CSFLE Save round-trips byte-identically through restoreRedactedConfig. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
📓 Docs Drift CheckThis PR changes 1 package(s): 3 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin b14d242215f66c8a2e6a1698f313d8f72210de7b && git checkout b14d242215f66c8a2e6a1698f313d8f72210de7b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d7ca6dc77edc2e158fb31b24176e6bf2341f9d1b 3520ba2dac649ab533c3525d7b89ca82dce37e4e && git checkout -B drift-repro d7ca6dc77edc2e158fb31b24176e6bf2341f9d1b && git merge --no-ff 3520ba2dac649ab533c3525d7b89ca82dce37e4e
node scripts/docs-audit/affected-docs.mjs --json d7ca6dc77edc2e158fb31b24176e6bf2341f9d1b
|
Fixes #13602
What
redactDatasourceConfig('mongodb', ...)served mongo CSFLE KMS key material inside theoptionspassthrough back in cleartext with noredactedKeysentry. Five client-measured positions joinpassthroughSecretPathsinpackages/spec/src/data/datasource-credential-redaction.ts, with the measurement quoted in the table docblock:options.autoEncryption.kmsProviders.aws.secretAccessKeyoptions.autoEncryption.kmsProviders.aws.sessionTokenoptions.autoEncryption.kmsProviders.azure.clientSecretoptions.autoEncryption.kmsProviders.gcp.privateKeyoptions.autoEncryption.kmsProviders.local.keyTable rows only. The #13405 mirrored-spelling derivation is untouched and no new credential name spellings were invented — these are the client-measured residue the table exists for.
Premise measurement (#9040 discipline — measured, never inferred from documentation)
Instrumented
new MongoClient(url, { ..., ...options })— the exact spread@objectstack/driver-mongodbperforms — againstmongodb@7.5.0, with the autoEncryption record, the kmsProviders record and every family object wrapped in read-logging Proxies. Both states of the optionalmongodb-client-encryptiondependency were measured, and the answers differ:mongodb-client-encryptioninstalled (7.2.1, inside the pin's^7.2.0optional-peer range): construction reads every one of the five leaves — the kmsProviders record is BSON-serialized into the native MongoCrypt. Observed reads:aws.secretAccessKey,aws.sessionToken,azure.clientSecret,gcp.privateKey,local.key, plus the identity halvesaws.accessKeyId,azure.tenantId,azure.clientId,gcp.email.connect()then proceeds into live CSFLE machinery (mongocryptd spawn attempt).MongoMissingDependencyErrorbefore reading any of the positions. The stored copy performs no function but sits cleartext at rest — the AWS_SESSION_TOKEN posture already on the table: a loud client failure does not make serving the secret back acceptable.Boundary kept deliberately narrow: the identity halves the client also reads (
aws.accessKeyId,azure.tenantId,azure.clientId,gcp.email) are not credential material (the #8876 asymmetry) and stay served; unmeasured neighbours (kmip.endpoint,keyVaultNamespace,schemaMap,bypassAutoEncryption) mirror no credential spelling, are not on the table, and are pinned untouched by a negative-control test.Round-trip answer (the card's third question)
restoreRedactedConfigmirrors the redactor structurally — it computes the served projection from the stored row and grafts every stored leaf back where the patch still matches — so the new rows are covered by construction, with nothing to forget. Pinned end-to-end at the admin service door: read serves the config with the five leaves withheld and named inredactedConfigKeys; an untouched update writes the stored config back deep-equal to the original.TypeError: Failed to parse KMS provider aws: expected UTF-8 secretAccessKey(azure and gcp equivalents measured identically);localserved as an empty object constructs, but encryption stays armed — the client proceeds into the mongocryptd path, and no silently-unencrypted operation was observed. Redaction therefore creates no silent-downgrade path, and no new write-back semantics were needed.Clause ②: no
Table entries plus tests only. No accept/reject behaviour changes: the write door refuses and accepts exactly what it did before (these positions were and remain writable — the binder's one secret slot is the login password, the proxyPassword posture), and no public export was added, removed or re-typed.
Verification (all runs at HEAD 3520ba2, after the final commit)
pnpm --filter @objectstack/spec exec vitest run src/data/datasource-credential-redaction.test.ts src/kernel/metadata-type-redaction.test.ts— Test Files 2 passed, Tests 47 passed.pnpm --filter @objectstack/service-datasource exec vitest run src/__tests__/datasource-config-redaction.test.ts— Tests 36 passed (includes the new CSFLE round-trip pin).pnpm --filter @objectstack/spec typecheckandpnpm --filter @objectstack/service-datasource typecheck— green; both edited test files confirmed inside their tsc programs via a listFiles probe (1 hit each).scripts/pm/dispatch-gates.mjs(no paths — changeset derived from merge-base; answer asserted for objectstack-ai/objectstack at commit 3520ba2) derived 46 families for this diff, path-matched plus convention-triggered test-file families. 44 PASS — includingcheck:type-check-debtre-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. 2 declared PREREQUISITE NOT MET by their own exit-3 text (NOT MEASURED, not red):check-test-completeness(grades a saved CI turbo test log that does not exist locally) andcheck-half-states(needs repo-scoped REST reads; this session's gate answers 403 — CI-owned)..changeset/csfle-kms-providers-redacted.md,@objectstack/specpatch.Measurement scripts and full logs are excerpted in the report comment on #13602. Session: https://claude.ai/code/session_01PBjwYLS6BciTQW3c9xQiD2
Generated by Claude Code
Generated by Claude Code