Skip to content

feat(spec)!: refuse inline credentials at publish — driver config + connector authoring door (#7990, spec half) - #8078

Merged
huangyiirene merged 8 commits into
mainfrom
claude/issue-7990-inline-credential-refusal
Aug 12, 2026
Merged

feat(spec)!: refuse inline credentials at publish — driver config + connector authoring door (#7990, spec half)#8078
huangyiirene merged 8 commits into
mainfrom
claude/issue-7990-inline-credential-refusal

Conversation

@huangyiirene

Copy link
Copy Markdown
Collaborator

Part of #7990 — the spec half only of the maintainer-ruled Option A split (comment 5266068845, 「接受你的全部建议。」). The services write/read-path half (including the false "credential-stripped" claim at datasource-admin-service.ts) and the existing-cleartext-rows migration are deliberately NOT here; the PM files them as Blocked-by: sub-cards at ACCEPT per the claim comment (5266425167). Merging this PR must not close the card.

What changes

Driver config family (postgres / mysql / mongo / turso).config.password and turso's config.authToken become declared-unwritable: the key stays in the shape as z.never() carrying the refusal prescription, so the removal is audible in tsc (input type never), in the parse (message names the key, the datasource secret binder → sys_secret, and external.credentialsRef — never a bare unrecognized_keys), and in the authorable-surface ratchet ([RETIRED] flip + 4 registered retired-key entries). Former alias spellings (passwd/pwd, turso token/jwt/auth_token/authtoken) carry the refusal directly via guidance — an alias row pointing at a tombstoned key would be the documented two-step-rejection trap.

Why tombstone instead of deleting the key: the Studio connection form renders its masked secret input from the format: 'password' marker in the JSON-Schema projection and routes the value to the top-level secret (the binder's door, never config). A hand-tailored never-key keeps { "not": {}, "format": "password" } in the projection, so the wizard keeps the very input the refusal diverts authors to. Pinned both halves in driver-credential-refusal.test.ts.

Connector authoring door.DeclarativeConnectorEntrySchema (behind defineStack({ connectors }) and PUT /meta/connector/:name) now refuses non-noneauthentication on every authored entry — catalog descriptors included; previously only provider-bound instances were covered (ADR-0097 §3), which was the ①-d hole. The runtime shape is untouched: plugins handing resolved secrets to registerConnector (slack/rest/openapi/mcp connectors, engine.ts:1948/2027) keep working, and shared/connector-auth.zod.ts now documents the runtime-vs-authored split explicitly.

Ledger. 4 retired-key entries + 2 D3 semantic entries (structured TODOs). Deliberately no D2 conversion: a cleartext credential cannot be mechanically rewritten into an encrypted sys_secret row at load — auto-deleting the key would silently drop a live credential. Regenerated: migration registry, spec-changes.json, upgrade guide, authorable-surface, api-surface, export-origins, reference docs.

Changeset bump reasoning

@objectstack/spec: major, following the acceptance-narrowing precedent (#4583datasource-capabilities-retired: authorable-key removal = major + FROM → TO + registered ledger disposition; same route here, with semantic entries instead of a conversion because no lossless mapping exists). @objectstack/example-showcase: patch for the migrated descriptor. ADR-0087 disposition: registered datasource-config-inline-credential-refused, connector-inline-authentication-publish-refused — gate verified green on this diff.

Census (dispatch-mandated): in-repo sites writing an inline credential today

  • examples/app-showcase/src/system/connectors/index.tsErpCatalogConnector — descriptor with placeholder authentication: { type: 'api-key', key: 'SET_AT_INSTALL_TIME' }migrated (auth scheme moved to description prose; comment documents the instance form with auth.credentialRef).
  • packages/spec test fixtures (postgres/mongo/turso/datasource tests) → triaged individually (re-spelled credential-free, or replaced with refusal pins; the ${DB_PASSWORD} placeholder tests re-scoped to non-credential keys with a measured note: nothing resolves ${…} placeholders — they reached the client verbatim).
  • packages/qa/downstream-contractDcConnector (oauth2, env: strings) — deliberately NOT migrated: it pins ConnectorSchema, the runtime shape that legitimately carries resolved secrets; migrating it would weaken the runtime-shape pin.
  • No example/app writes an inline datasource credential; the showcase's commented warehouse example already models external.credentialsRef.

Verification (real numbers; consumer sweep direction = DOWNSTREAM consumers of @objectstack/spec, enumerated by name)

  • spec: full suite 10115 tests / 382 files green (after triage), typecheck + test-layer typecheck green, check:generated all 13 current on the merged tree.
  • Consumers: service-datasource 339, objectql 3356 (includes the /meta door 422 envelope pins), runtime 2171, metadata 603, service-automation 940, platform-objects 347, connector-rest/slack/mcp/openapi 16/8/23/32, downstream-contract 14 — all green. Examples validate: crm / showcase / todo all ✓.
  • Gates: check:driver-conformance, check:adr-anchors, check:changeset-gate-self-tests, check:cross-package-test-inputs, check:doc-formula-expressions, check:docs-audit-scope, check:i18n, check:merge-driver, check:release-body, check:spec-parsed-alias, check:nul-bytes, check:adr-0087-registration — all PASS locally. Strictness ledger and test-typecheck-debt.json untouched (no spec: close memory driver's persistence sub-shapes against unknown keys (#4001 batch B) #7985 collision; its memory-driver face is disjoint).
  • Reverse verification (fix committed first; predicted directions stated before running): restoring the pre-[security] sys_metadata.metadata is a general cleartext sink: any authored artefact whose schema permits an inline credential lands it there (datasource config.password, connector authentication) #7990 postgres limb went red in BOTH predicted channels — exactly the 6 postgres-facing refusal pins failed (23 others stayed green), and the authorable-surface gate failed from the other side (check (b2): "RETIRED_KEYS_BY_MAJOR entry names a key that is still LIVE — data/PostgresConfig:password"). Restore proven byte-identical by git hash-object.
  • Same-day churn: origin/main merged twice (through 22f0daa) via the mechanized os-regen-merge.sh path; both sides' ledger entries verified present after regeneration.

Open questions (recorded, not expanded on — see the report on #7990)

  1. URL-embedded credentials (postgresql://user:pass@host in config.url) are a live, unruled door — measured and pinned as a FACT in driver-credential-refusal.test.ts, not refused (PM mechanism note 3).
  2. turso encryptionKey stays writable: credential-shaped, but neither ruled mechanism (binder secret → password/authToken injection; credentialsRef) can carry it — refusing it would leave local-file encryption unconfigurable.
  3. Managed datasources have no in-contract ref mechanism: external is refused when schemaMode: 'managed' (datasource.zod.ts:550), so a code-defined managed datasource now has no spec-level credential slot at all (the wizard path and URL-embedding remain). Belongs to the services sub-card / maintainer.

Out-of-scope census finding filed as #8075 (two more schemas permitting inline credentials — third-surface input for the parked Option-B reopen trigger).


Generated by Claude Code

…onnector authoring door (#7990)
Driver family (postgres/mysql/mongo/turso): config.password / config.authToken
are declared-unwritable (z.never with the refusal prescription) so tsc, the
parse and the connection form's format:'password' secret input all stay wired
to the datasource secret binder (sys_secret + external.credentialsRef). Former
alias spellings (passwd/pwd/token/jwt/auth_token/authtoken) carry the refusal
directly via guidance.
Connector: DeclarativeConnectorEntrySchema now refuses non-none authentication
on EVERY authored entry — catalog descriptors included (was: provider-bound
instances only, ADR-0097 §3). Runtime registerConnector keeps the resolved
inline shape.
Ledger: 4 retired-keys entries + 2 D3 semantic entries (no D2 conversion — a
cleartext credential cannot be mechanically rewritten into an encrypted
sys_secret row); regenerated registry, spec-changes, upgrade guide, authorable
surface ([RETIRED] flips), api-surface, export-origins, reference docs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123k4cam2jEAkPmbJeoaY3r
… descriptor migration (#7990)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123k4cam2jEAkPmbJeoaY3r
…egen step 4)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0123k4cam2jEAkPmbJeoaY3r
@vercel

vercelBot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 12, 2026 3:10pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx(via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx(via @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/spec)
  • content/docs/api/environment-routing.mdx(via @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx(via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx(via @objectstack/spec)
  • content/docs/api/index.mdx(via @objectstack/spec)
  • content/docs/automation/approvals.mdx(via @objectstack/spec)
  • content/docs/automation/connectors.mdx(via @objectstack/spec)
  • content/docs/automation/flows.mdx(via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx(via packages/spec)
  • content/docs/automation/hooks.mdx(via @objectstack/spec)
  • content/docs/automation/index.mdx(via @objectstack/spec)
  • content/docs/automation/webhooks.mdx(via @objectstack/spec)
  • content/docs/automation/workflows.mdx(via @objectstack/spec)
  • content/docs/concepts/architecture.mdx(via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx(via packages/spec)
  • content/docs/concepts/index.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx(via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx(via packages/spec)
  • content/docs/concepts/north-star.mdx(via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx(via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx(via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx(via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx(via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx(via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx(via @objectstack/spec)
  • content/docs/data-modeling/index.mdx(via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx(via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx(via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx(via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx(via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx(via @objectstack/spec)
  • content/docs/deployment/cli.mdx(via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx(via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx(via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx(via @objectstack/spec)
  • content/docs/getting-started/examples.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx(via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx(via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx(via @objectstack/spec)
  • content/docs/kernel/cluster.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx(via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx(via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx(via @objectstack/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/kernel/services.mdx(via @objectstack/spec)
  • content/docs/permissions/authorization.mdx(via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx(via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx(via @objectstack/spec)
  • content/docs/permissions/positions.mdx(via @objectstack/spec)
  • content/docs/permissions/rls.mdx(via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx(via @objectstack/spec)
  • content/docs/permissions/system-context.mdx(via packages/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx(via @objectstack/spec)
  • content/docs/plugins/development.mdx(via @objectstack/spec)
  • content/docs/plugins/index.mdx(via @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx(via @objectstack/spec)
  • content/docs/protocol/diagram.mdx(via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx(via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx(via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx(via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx(via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx(via @objectstack/spec)
  • content/docs/ui/actions.mdx(via @objectstack/spec)
  • content/docs/ui/apps.mdx(via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx(via @objectstack/spec)
  • content/docs/ui/dashboards.mdx(via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx(via @objectstack/spec)
  • content/docs/ui/forms.mdx(via @objectstack/spec)
  • content/docs/ui/index.mdx(via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx(via @objectstack/spec)
  • content/docs/ui/setup-app.mdx(via @objectstack/spec)
  • content/docs/ui/translations.mdx(via @objectstack/spec)
  • content/docs/ui/views.mdx(via @objectstack/spec)

7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx(via @objectstack/spec)
  • content/docs/releases/index.mdx(via @objectstack/spec)
  • content/docs/releases/v12.mdx(via @objectstack/spec)
  • content/docs/releases/v13.mdx(via @objectstack/spec)
  • content/docs/releases/v16.mdx(via @objectstack/spec)
  • content/docs/releases/v17.mdx(via @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationprotocol:datasize/lteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@huangyiirene@claude