Repository navigation
Commit 01e0f71
docs(drivers): say what the read path withholds from a plugin driver's config (#21953)
Fixes #21950
Clause-②: no
## What
One paragraph in `content/docs/data-modeling/drivers.mdx`: the
plugin-contributed-driver paragraph that #21927 landed. It said a plugin
driver's `config` is "stored and served to administrators as written".
The stored half holds. The served half did not: the read path withholds
a fixed, name-based set for every driver, contracted or not. The
paragraph now names that set and says everything else is served as
written. It keeps #21927's intent: the config is unvalidated, keeping
secrets out of it is the plugin author's job, and the credential belongs
in `external.credentialsRef`.
Docs only, no code change. The ruling on #21921 stands (no heuristic, no
registration API): 「我觉得不需要协议,也不需要改动这么多代码」 and 「同意作废,文档补一句」.
**Before** (`drivers.mdx:157`-`:160` at `3c7785d4`):
> The platform also does not guess which of its keys hold credentials:
`config` is stored and served to administrators as written. Keeping
secrets out of it is the plugin author's responsibility; put the
credential in the bound secret (`external.credentialsRef`) instead.
**After:**
> ... so its `config` is left unvalidated rather than judged against a
shape the platform does not have, and is stored as written. The platform
also does not guess which of its keys hold credentials. On read it
withholds only what it withholds for every driver: a key named exactly
`password` or `authToken`, or one of their former aliases
(`FORMER_CREDENTIAL_ALIASES`), and, in a URL-shaped string value, the
password in its userinfo and its credential query parameters
(`CREDENTIAL_URL_QUERY_PARAM_NAMES`, such as `?password=`). Both apply
at every depth of nested objects, but not inside arrays. Everything else
in `config` is served to administrators as written
(`redactDatasourceConfig` in `datasource-credential-redaction.ts`), and
a withheld value still sits in the stored row. Keeping secrets out of
`config` is the plugin author's responsibility; put the credential in
the bound secret (`external.credentialsRef`) instead.
## The code the new text rests on (`3c7785d4`)
Every clause was checked against the code, not taken from the card.
- **Stored as written.**
- `packages/spec/src/data/driver/config-registry.zod.ts:435`: `return id
? DRIVER_CONFIG_SCHEMAS[id] : undefined;`
- `config-registry.zod.ts:522`: `if (!schema) return { known: false };`
- `packages/spec/src/data/datasource.zod.ts:693` runs
`reportDriverConfigIssues(ctx, ds.driver, ds.config, ['config']);`, and
`:485` returns early on `if (!result.known) return;`.
- The admin write doors reach the same two judges:
`datasource-admin-service.ts:1031` (`validateDriverConfig`) and `:1061`
(`DatasourceSchema.safeParse(record)`).
- **Which names are withheld.**
- `packages/spec/src/data/datasource-credential-redaction.ts:372`:
`const canonical = derived.length > 0 ? derived :
[...CANONICAL_CREDENTIAL_KEYS];`. For a driver with no contract,
`derived` is empty.
- `:374`: `return [...new Set([...canonical,
...FORMER_CREDENTIAL_ALIASES, ...stillWritable])];`
- `packages/spec/src/data/driver/common.zod.ts:437`: `export const
CANONICAL_CREDENTIAL_KEYS = ['password', 'authToken'] as const;` The
alias list starts at `:448`.
- **Exact name, every object depth, not inside arrays.**
- `datasource-credential-redaction.ts:520` builds `const hidden = new
Set(redactableConfigKeys(driver));`, and `:527` tests `if
(hidden.has(key))`, an exact-case match.
- `:546`-`:547` recurse only into `value && typeof value === 'object' &&
!Array.isArray(value)`. An array falls through to `:550`, `out[key] =
value;`.
- **URL credentials.**
- `:537` runs `redactUrlCredentials(value)` on every string value.
- That function (`:451`) composes `redactUrlPassword` (`:405`, which
keeps the username) with `redactUrlCredentialQueryParams` (`:436`). The
second filters on `CREDENTIAL_URL_QUERY_PARAM_NAMES`
(`common.zod.ts:311`-`:312`, the union of `:299`-`:300`), matched
case-insensitively (`common.zod.ts:621`).
- **Nothing positional for an unknown driver.** `passthroughSecretPaths`
returns `[]` when the id does not resolve (`:227`).
`refusedCredentialPaths` reads a schema that is `undefined`
(`config-registry.zod.ts:435`).
- **Where it is served.** `datasource-admin-service.ts:554`
(`getDatasource`) and
`packages/spec/src/kernel/metadata-type-redaction.ts:93` (the built-in
`datasource` redactor for the metadata read exits) both call
`redactDatasourceConfig`.
- **The module says the same.**
`datasource-credential-redaction.ts:66`-`:71` ("canonical spellings are
therefore redacted by NAME for unknown drivers too") and `:88`-`:94`
("strips it for EVERY driver").
**Measured, not only read.** A scratch probe (not committed) ran
`redactDatasourceConfig('com.vendor.snowflake', …)` and
`DatasourceSchema.safeParse` from `src` at `3c7785d4`:
- The write door accepted the config and stored it byte-equal.
- The read withheld `password`, `authToken`, `pwd`, `token`, a nested
`token`, a nested URL's userinfo password and `?password=` /
`?AuthToken=` query pairs.
- The read served `Password` (case variant), `privateKey`, `apiKey`,
`clientSecret`, a nested `secret` and a `password` inside an array
element as written.
- `getMetadataTypeRedactor('datasource')` reported the same paths under
`config.`.
## Checks
The diff is one `.mdx` file and touches no package, so there is no
dependency-closure build step for the diff itself. `@objectstack/lint`
and `@objectstack/client` / `@objectstack/client-react` were built (with
their closures) only because three of the gates read built output.
- Gate list derived by `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` on the actual diff at `809a8641`. It
gives the same 40 commands the dispatch named.
- All 40 exit 0 at `809a8641`. Reconciliation: `✓ dispatch-gates --ran:
40 derived famil(ies) accounted for — 40 run, 0 NOT-MEASURED (a DERIVED
zero — all 40 recorded an exit code and none of them is 3).`
- `pnpm --filter @objectstack/spec run check:skill-examples` first
exited 3 with `PREREQUISITE NOT MET` (no `client-react` declarations).
That exit is not a measurement. After building
`@objectstack/client-react` and `@objectstack/client` it exited 0: `✅
262 prose examples type-check across 3 surface(s)`.
- These were not run locally and are left to CI: the artifact-roster,
wide-population and type-check lanes that `dispatch-gates` lists outside
its 40, and the `Build Docs` job.
## Changeset
None (`skip-changeset`). No published package's `files[]` ships
`content/docs`. As a probe, the new sentence's text was searched for in
every package `dist/`, in `packages/spec/llms.txt`, in
`packages/spec/prompts` and in `skills/`: 0 hits. Positive control:
`redactDatasourceConfig` in `packages/spec/dist`, 10 files.
## Acceptance notes
- **Out of scope, handed to the seat for filing** (code, not docs): the
read-path still-writable table (`STILL_WRITABLE_CREDENTIAL_KEYS`) is
indexed by the raw `driver` spelling at
`datasource-credential-redaction.ts:373`. Its sibling
`passthroughSecretPaths` resolves aliases through `resolveDriverId`
(`:226`-`:227`), and this lookup does not. A function-level probe shows
two symptoms:
- The `turso` \| `libsql` row of this page's at-rest table
(`drivers.mdx:185`) holds for one spelling only.
- A driver id that names an `Object.prototype` member makes the lookup
throw (`stillWritable is not iterable`). This fails closed.
One line, one fix. This PR does not touch it.
- This PR does not change the at-rest-risk section below the paragraph.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01VF48aw8RPG6wzDnMgp6rtw)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 76fec88 commit 01e0f71
1 file changed
Lines changed: 12 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
154 | 154 | | |
155 | 155 | | |
156 | 156 | | |
157 | | - | |
158 | | - | |
159 | | - | |
160 | | - | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
161 | 169 | | |
162 | 170 | | |
163 | 171 | | |
| |||
0 commit comments