Repository navigation
feat(lint): warn on a credential-shaped literal in a connector's header or env map - #29424
Conversation
…header/env maps Claude-Session: https://claude.ai/code/session_01ADHkmPZY72rbsdkcLx2ZoH Co-authored-by: Claude <noreply@anthropic.com>
…; changeset Claude-Session: https://claude.ai/code/session_01ADHkmPZY72rbsdkcLx2ZoH Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 4 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 cc1ba3d9958c3bda2134b9e043c01460c0179356 && git checkout cc1ba3d9958c3bda2134b9e043c01460c0179356
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d93acbbe552989632d22269e90841ed44860b589 e7f39bdf04e24fac9e42b47335eeef2fe76e5e02 && git checkout -B drift-repro d93acbbe552989632d22269e90841ed44860b589 && git merge --no-ff e7f39bdf04e24fac9e42b47335eeef2fe76e5e02
node scripts/docs-audit/affected-docs.mjs --json d93acbbe552989632d22269e90841ed44860b589
|
…public-surface widening Claude-Session: https://claude.ai/code/session_01ADHkmPZY72rbsdkcLx2ZoH Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67 Contract-review-claim: session_01ADHkmQJdgarUjPLNgFBY67 · head e7f39bd · 2026-11-02T14:48Z |
|
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67 Contract reviewServed-tier: ① Derived judgmentsRead: card #29341 (body, triage 6577787052, claim 6578032174, dev report 6579919803, seat ruling 6579967665), the PR #29424 body, its 13-file list, and the net diff
② Semver levelClause-②: yes (widening) — this is correct for the diff. It publishes three new names on the
③ Boundary flags
Implemented-by: VERDICT: PASS |
|
domain:server#3 · os-steve · session_01ADHkmQJdgarUjPLNgFBY67 席位核对 · ACCEPT · PR #29424 · 2026-11-02T14:55ZHead-sha: 维护者速读(席位终稿)
核对六项
|
Requested by Steve · project thread
Fixes #29341
Clause-②: yes (widening)
Three new exports on the
@objectstack/lintentry and a new advisory on the connector save door; nothing is refused or narrowed.Metadata-protocol: none
维护者速读
providerConfig.headers、defaultHeaders、transport.headers、transport.env)里写死的疑似凭据,os validate/os build/os lint与连接器保存都会给出警告,指向auth.credentialRef。packages/lint/src/lint-connector-credential-literals.ts(规则与措辞,尤其 stdio 那一句)、packages/lint/src/authoring-rules.ts新条目、packages/lint/src/runtime-gate.ts新增的connector映射行。pnpm --filter @objectstack/lint exec vitest run src/lint-connector-credential-literals.test.ts,预期 16 个用例全绿。What changes
connector-credential-literal(packages/lint/src/lint-connector-credential-literals.ts). It asks the existingisCredentialShapedLiteralpredicate (credential-literal.ts, unchanged, no second name list) over each declarative connector entry's four maps:providerConfig.headers,providerConfig.defaultHeaders,providerConfig.transport.headers,providerConfig.transport.env. Onewarningper credential-shaped literal, located by path (connectors[i].providerConfig.defaultHeaders["X-Api-Key"]), never echoing the value.header 'NAME' in MAP holds a credential-shaped literal, kept in metadata, not the secrets layer; move it to the connector's auth.credentialRef, with a fix line naming thebearer/api-keyauth variants. Thetransport.envmap gets a different steer, because a stdio MCP server takes noauth(see Acceptance notes):… a stdio server takes no auth; auth.credentialRef reaches it only over http.AUTHORING_RULES:tier: 'advisory', all three commands (validate,build,lint),surfaces: CLI_AND_RUNTIME,runtimeTypes: ['connector'].TYPE_TO_STACK_KEYgainsconnector: 'connectors', so aPUT /meta/connector/:namepublish runs the rule through the existing runtime authoring gate and the findings ride the save response'sadvisories(the channelflow-credential-literalalready uses). No new channel was built.os explain connector-credential-literalentry inrule-explanations.ts: the four maps, why withholding on read does not move the value out of metadata, the auth variants, the stdio limit, why it only warns.lintConnectorCredentialLiterals,CONNECTOR_CREDENTIAL_LITERAL,ConnectorCredentialLiteralFinding..changeset/29341-connector-credential-literal.md(@objectstack/lintpatch).Forced edits (not tests): the four CLI transcripts that print
Running author-time rules (N)go 60 → 61 (content/docs/deployment/cli.mdx,deployment/validating-metadata.mdx,getting-started/build-with-claude-code.mdx,ui/react-pages.mdx), because the registry gained one entry (check:docs-transcript-drift). No existing test was edited.Not touched: the connector redactor (
connector-credential-redaction.ts), the package export path (#29342),credential-literal.ts, anything inpackages/spec.Tests
packages/lint/src/lint-connector-credential-literals.test.ts(16): aBearer …literal in each of the four maps warns at its own path; a credential name alone warns;Accept: application/json, anauth.credentialRef-routed connector, a template, a blank and a non-string value draw nothing; values outside the four maps are not this rule's; malformed stacks never throw; the four maps equal the connector redactor's withheld maps, read through the publicgetMetadataTypeRedactor('connector'); registry entry shape; CLI commands and runtime gate report warnings only;stackKeyForType('connector') === 'connectors'; barrel;os explainnames every map.packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts, new block (2), through the realsaveMetaItem: LIT saves of arest, anmcphttp and anmcpstdio connector each land (rows stored, literal at rest unchanged) and carry onewarningadvisory per map on the 2xx, with no sentinel in the response; DARK (credentialRef-routed,Acceptonly) carries none.packages/cli/test/connector-credential-literal-doors.e2e.test.ts(2, nightly integration tier like its flow sibling):os validate --jsonandos lint --jsonexit 0 on LIT with the four warnings, and print none on DARK.Acceptance notes
auth.credentialRefroute". Measured: the MCP provider folds a resolvedauthinto an http transport's headers only (connector-mcp/src/mcp-provider.tsapplyAuthToHeaders, "Not applied to stdio transports"), the stdio client receivestransport.envas its environment, and the connector guide says "Credentials for a stdio server ridetransport.env, notauth". A plain "move it toauth.credentialRef" on an env entry would point at a route the runtime does not deliver for stdio. So the env warning namesauth.credentialRefwith its condition (an http transport). Flagged to the seat as an open question.env: { TOKEN: process.env.TOKEN }for a stdio server is evaluated to a literal before the rule reads it, so it warns atos validatetoo. That is the heuristic's accepted false-positive cost (a line of reading); nothing is refused.CONNECTOR_CREDENTIAL_MAP_PATHS, which the spec does not export (ruling ③A keeps it off the spec export contract). The equality is pinned by test through the public redactor API instead of an import.Verification
All readings on head
28b45f8e(this branch withorigin/mainmerged), container-local.Tests
pnpm --filter @objectstack/lint test: Test Files 190 passed (190), Tests 7846 passed (7846).pnpm --filter @objectstack/metadata-protocol test: Test Files 304 passed, 4 skipped (308); Tests 22155 passed, 26 skipped.pnpm --filter @objectstack/lint typecheckandpnpm --filter @objectstack/metadata-protocol typecheck: exit 0.pnpm --filter @objectstack/cli exec vitest run --project unit test/explain-rule-id.test.ts: 14/14 (everyos explainentry, the new one included).OS_TEST_TIERS=nightly … --project integration test/connector-credential-literal-doors.e2e.test.ts test/flow-credential-literal-doors.e2e.test.ts: 2 files, 4/4.pnpm --filter @objectstack/spec check:generated: all 14 generated artifacts up to date.pnpm lint: exit 0.Ablation (each leg: mutate through scripts/ablation-replace.mjs, rebuild lint, dist pre-flight, run, restore, rebuild, pre-flight --absent)
error. Lint unit: 3 red (warning assertions; runtime gate "never errors"). Save door: LIT red withconnector/vendor_api failed author-time validation: 2 issues — … [connector-credential-literal], the 422 the pin forbids. Restored: blob == HEAD, dist marker absent.connector: 'connectors'row renamed. Lint unit: 2 red (stackKeyForTypenull, rule not run at the gate). Save door: LIT red (expected [] to deeply equal [ …(4) ]). Restored.Gates
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 90 families on28b45f8e; all 90 ran, all exit 0. Five first answered exit 3 (prerequisite not met: a shallow-clone pinned commit, client-react and full package dists, and one queued behind my own build) and were re-run green aftergit fetchof the pinned commit and a fullturbo run build --filter=./packages/**.--ranreconciliation: 90 derived, 90 run, 0 NOT-MEASURED, 0 UNRUN.Dogfood battery: not owed (no
!in the title, no(narrowing)arm).Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01ADHkmPZY72rbsdkcLx2ZoH