|
| 1 | +--- |
| 2 | +"@objectstack/spec": major |
| 3 | +"@objectstack/example-showcase": patch |
| 4 | +--- |
| 5 | + |
| 6 | +feat(spec)!: refuse inline credentials at publish — driver `config.password` / `config.authToken` and connector `authentication` on authored entries (#7990) |
| 7 | + |
| 8 | +`sys_metadata.metadata` is served back by the ordinary data API, and a datasource or |
| 9 | +connector artefact is persisted whole — so any schema that *accepted* an inline |
| 10 | +credential stored that credential in cleartext at rest. The maintainer-ruled fix |
| 11 | +(#7990, Option A: per-artefact contract closure) makes the two measured surfaces |
| 12 | +refuse the inline form at publish and divert to the mechanisms that already exist. |
| 13 | + |
| 14 | +**Driver config (postgres / mysql / mongo / turso).** `config.password` (SQL/mongo) |
| 15 | +and `config.authToken` (turso) are now declared-unwritable: writing one fails `tsc` |
| 16 | +(the input type is `never`) and fails the parse with a prescription naming the |
| 17 | +replacement. The former alias spellings (`passwd`, `pwd`, `token`, `jwt`, |
| 18 | +`auth_token`, `authtoken`) carry the same refusal. The connection form's masked |
| 19 | +secret input is unaffected — it never wrote `config`; it feeds the datasource secret |
| 20 | +binder, which encrypts into `sys_secret` and stores only an opaque handle. |
| 21 | + |
| 22 | +**Connector authoring door.** `DeclarativeConnectorEntrySchema` (behind |
| 23 | +`defineStack({ connectors })` and `PUT /meta/connector/:name`) now refuses a |
| 24 | +non-`none` `authentication` on **every** authored entry — catalog descriptors |
| 25 | +included. Until now only provider-bound instances were covered (ADR-0097 §3), so a |
| 26 | +descriptor could publish an inline `token`/`key`/`password`/`clientSecret`. The |
| 27 | +runtime shape is unchanged: a plugin handing resolved secrets to |
| 28 | +`registerConnector` keeps working. |
| 29 | + |
| 30 | +## FROM → TO |
| 31 | + |
| 32 | +```ts |
| 33 | +// before — accepted, stored in cleartext in sys_metadata |
| 34 | +defineDatasource({ |
| 35 | + name: 'warehouse', driver: 'postgres', |
| 36 | + config: { database: 'analytics', username: 'ro', password: 'hunter2' }, |
| 37 | +}) |
| 38 | + |
| 39 | +// after — the secret lives in the secret store; config carries no credential |
| 40 | +defineDatasource({ |
| 41 | + name: 'warehouse', driver: 'postgres', schemaMode: 'external', |
| 42 | + config: { database: 'analytics', username: 'ro' }, |
| 43 | + external: { allowWrites: false, credentialsRef: 'sys_secret:<handle>' }, |
| 44 | +}) |
| 45 | +// (Setup → Datasources binds the secret for you: its password field encrypts into |
| 46 | +// sys_secret and writes external.credentialsRef — it never wrote config.) |
| 47 | +``` |
| 48 | + |
| 49 | +```ts |
| 50 | +// before — descriptor published an inline credential |
| 51 | +defineConnector({ |
| 52 | + name: 'erp', label: 'ERP', type: 'saas', |
| 53 | + authentication: { type: 'api-key', key: '…', headerName: 'X-API-Key' }, |
| 54 | +}) |
| 55 | + |
| 56 | +// after — descriptor: no live credentials (document the scheme in prose); |
| 57 | +defineConnector({ name: 'erp', label: 'ERP', type: 'saas', |
| 58 | + description: 'Authenticates with an API key in the X-API-Key header.' }) |
| 59 | +// instance: reference the credential (ADR-0097 §3) |
| 60 | +defineConnector({ name: 'erp', label: 'ERP', type: 'saas', provider: 'openapi', |
| 61 | + providerConfig: { spec: './erp-openapi.json' }, |
| 62 | + auth: { type: 'api-key', credentialRef: 'ERP_API_KEY' } }) |
| 63 | +``` |
| 64 | + |
| 65 | +There is deliberately **no automatic rewrite**: moving a cleartext credential into |
| 66 | +`sys_secret` requires encrypting it through a running secret binder, which a |
| 67 | +source-file transform cannot do — auto-deleting the key would silently drop a live |
| 68 | +credential instead. `os migrate meta` surfaces both changes as structured TODOs |
| 69 | +(semantic entries `datasource-config-inline-credential-refused`, |
| 70 | +`connector-inline-authentication-publish-refused`). The migration story for |
| 71 | +**already-stored** cleartext rows is programme scope, tracked as a follow-up card |
| 72 | +under #7990 — this release closes the doors that keep writing new ones. |
| 73 | + |
| 74 | +<!-- adr-0087: registered datasource-config-inline-credential-refused, connector-inline-authentication-publish-refused --> |
0 commit comments