Repository navigation
feat(lint,spec): a credential typed as a literal into a served flow position draws an author-time advisory that routes it to a connector (#20654) - #20698
Conversation
…a literal into a served flow position - @objectstack/lint gains one exported predicate, isCredentialShapedLiteral, the single home of the name/value rule, and one advisory rule that uses it, lintFlowCredentialLiterals (flow-credential-literal), registered tier advisory on os validate / os build / os lint and the runtime publish gate for flow writes. It never refuses and never echoes the value. - The spec describes of an http node's headers and a connector node's input say the definition is served to every flow reader and route an outbound credential to a declarative connector's auth.credentialRef. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
…he save door, os validate and os lint Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
…ges from the new describes Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
…egistry now derives (48) Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
…dvisory Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
…edential-literal-advisory
…in its message, which the CLI text faces print Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
…e connector schema really has A header credential goes to a bearer or header api-key connector auth, a query-string key to api-key with paramName, a connector input to the connector's own credentialRef. No text promises a variant that carries a secret in a url path. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
…ders describe Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
…edential-literal-advisory
📓 Docs Drift CheckThis PR changes 2 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 137 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 76f8f6f8d2c82245193675d6cf1b6d4cae031f28 && git checkout 76f8f6f8d2c82245193675d6cf1b6d4cae031f28
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 14f80e23957165f6fb23c2b3d59bdc7668652dfb ae5d8af6f81050dcf56c8893698df875d2f9839d && git checkout -B drift-repro 14f80e23957165f6fb23c2b3d59bdc7668652dfb && git merge --no-ff ae5d8af6f81050dcf56c8893698df875d2f9839d
node scripts/docs-audit/affected-docs.mjs --json 14f80e23957165f6fb23c2b3d59bdc7668652dfb
|
Contract reviewServed-tier: Inputs read: card #20654 (body, triage ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
…#20682) (objectstack-ai#20704) Fixes objectstack-ai#20682 Clause-②: no Docs-only, `content/docs/automation/flows.mdx`, two spots. 1. The `http` node callout no longer sends every credential to a declarative connector's `auth.credentialRef`. It routes by shape: a header credential to `bearer`/`basic`/`api-key`; a query key to `api-key` with `paramName`; a secret in the url path has no `credentialRef` variant, so it goes to a token-authenticated connector such as `slack`, whose bot token is supplied by host code (the Slack connector is plugin-registered, not a declarative `connectors:` provider; only `rest`, `openapi`, `mcp` register providers). The first sentence and the `signingSecret` / start-node `secret` sentence are unchanged. 2. The retired-shapes row for `actionType: 'slack'` keeps the `connector_action` + Slack connector and drops the incoming-webhook `http` alternative. Wording is in step with objectstack-ai#20672's `http` descriptor and objectstack-ai#20698's describes. ## Acceptance notes - `pnpm --filter @objectstack/spec run check:skill-examples` (client dist not built) and `check:gitlink-declared` were not run: NOT MEASURED. The other derived gates ran green (33 of 40 derived; the rest are checker self-tests), `doc-security-posture` and `docs-spec-enumerations` included. - No changeset: docs-only. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv Co-authored-by: Claude <noreply@anthropic.com>
Fixes #20654
Clause-②: no
This PR is the
packages/spec+packages/lintface of #20590's direction A (triage ruling5891721503, carried by the card and restated in5891910265). The services face (thehttpdescriptor text and the showcase) is #20590's own claim, and the docs and skill faces are #20655 and #20657. #20590 is not closed by this PR.What changes
A flow definition is served, as authored, to every member who can read flows. The read path withholds the credential slots the spec declares, but an open map or a url cannot be withheld: an ordinary value there is indistinguishable from a credential. So this PR steers and warns. Nothing is withheld and nothing is refused.
@objectstack/lintgainsisCredentialShapedLiteral(name, value)inpackages/lint/src/credential-literal.ts. It is the measured scanner's R1/R2 rule, unchanged: a credential-named key (R1) or an auth-scheme value (R2), on a non-blank string that carries no{…}template. The name lists are module-private. A test pins that no other shipped source file in the package holds them.lintFlowCredentialLiterals(rule idflow-credential-literal,packages/lint/src/lint-flow-credential-literals.ts) reports onewarningper credential-shaped literal in three positions: anhttpnode'sconfig.headersentry, a query parameter of anhttpnode'sconfig.url, and a node'sconnectorConfig.inputat any depth. Nodes nested intry_catch/loop/parallelregions are included. The finding names the key and the position, and it never carries the value.authisbeareror a headerapi-key. A query-string key goes toapi-keywithparamName. A connector input goes to the connector's ownauth.credentialRef. The route is in the message, which is what theos validateandos linttext faces print; the hint carries the full declaration. No text promises a variant that carries a secret in a url path. This follows the wording guard the services seat relayed in5894445934.AUTHORING_RULESentry,tier: 'advisory', on all three commands, withsurfaces: ['cli', 'runtime-publish']andruntimeTypes: ['flow']. The wiring guard's "every advisory rule really is advisory" check reads the rule's source for anerrorseverity. The finding type isseverity: 'warning'.HttpConfigSchema.headersand a flow node'sconnectorConfig.inputsay the definition is served to every flow reader, and route an outbound credential as above. The wording changes; no shape changes.content/docs/references/automation/{flow,io-node-config}.mdxare regenerated withgen:docs.@objectstack/lintminor (new exports and a new advisory),@objectstack/specpatch (describe text only). Both packages'distcarry the change: measured by grep offiles[]after the build, with a positive control.Mechanism hypotheses, measured
f4ce10c89d): confirmed. A flow with a credential-shaped literal in anhttpheader, a url query key and a nested connector input got these answers:evaluateRuntimeAuthoringGate,state: 'active') returnederror: null, advisories: []. The dark control (the same flow with{var}templates) returned the same.try_catchwith nocatch, drew exactly one advisory,flow-try-catch-without-catch.os validate --jsonexited 0 withvalid: true. Its only warning wasNo apps or plugins defined — this stack may not do much, lit and dark alike.os lint --jsonexited 0 withpassed: trueandissues: [], lit and dark alike.packages/cli/src/commands/*andruntime-authoring-gate.tsare untouched.packages/linthas no api-surface or export-origins shards. The.entry gainsisCredentialShapedLiteral,lintFlowCredentialLiterals,FLOW_CREDENTIAL_LITERALand the typeFlowCredentialLiteralFinding../runtimeexports nothing new.check:docs-transcript-drift: the four CLI transcripts printRunning author-time rules (48), up from 47. There is no generator; each line is set to the value the gate derives fromauthoringRulesFor(cmd).check:docs: the two reference pages above, viagen:docs.check:generatedreports all 15 artifacts up to date.f7fe981913throughscripts/ablation-replace.mjs(WRAP mode). It made the predicate always return false, then rebuilt@objectstack/lint.runAuthoringRulesfor each command, reach throughrunRuntimeAuthoringRules). The negatives, the draws-nothing controls, the registry entry, the export and the one-home pin stayed green, 21 in all.os validateandos lintpins both went red.git diff HEADwas empty, the marker was absent from all 14 dist files and the tree was clean. Then lint 54/54, save door 3/3 and CLI doors 2/2 all passed.Verification record (HEAD
ae5d8af6f8, which includes currentmain)os validateexits 0 withvalid: true. Lit prints fourflow-credential-literalwarnings: the header, the url query key, the header inside thetryregion and the nested connector input. Dark prints none.os lintexits 0 withpassed: true. Lit reports the same four warnings; dark reports none.error: null. Lit carries one advisory per literal on the advisory channel; dark and draft carry none.packages/lint: the pin set is the measured scanner's positive-control fixture (literal arm, doc-block arm, probe flow), carried over as the evaluated stack. It draws exactly 12 advisories, and the template, ordinary-value andsigningSecretcontrols draw none.packages/metadata-protocol/src/runtime-authoring-gate.flow-credential-literal.test.ts.os validate/os lint:packages/cli/test/flow-credential-literal-doors.e2e.test.ts. It spawns the CLI, so it sits in the nightly tier by name; the per-PR half is the per-commandrunAuthoringRulespin in lint.@objectstack/lint: 117 files, 5433 tests passed, typecheck exit 0.@objectstack/metadata-protocol: 190 files passed and 3 skipped (2786 tests passed, 19 skipped), typecheck exit 0.@objectstack/cli: unit project 234 files, 3355 tests passed, typecheck exit 0. The door pin passes 2/2 underOS_TEST_TIERS=nightly. The per-PR integration project is left to CI.os lintdraws 0flow-credential-literalfindings onapp-showcase,app-crmandapp-todo.dispatch-gates --commandsderives 111 gates atae5d8af6f8. All 111 ran with their exit codes captured before any pipe, and all exited 0.--ranreconciliation: 111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN.check:type-check-debtran under the verify lock, because it runs its own closure build.**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}.--format jsonover the 10 changed files in it reports 0 errors and 0 warnings. The config enables no type-aware linting, so this diff cannot move the verdict on any untouched file.Acceptance notes
httpnode'sbody.X-Authorizationis missed by R1 unless its value opens with an auth scheme.session…orauth…key holding a non-secret string) draws a false positive. That costs a line of reading, which is the ruling's trade.--strictonos validate/os lintpromotes this warning to a failure, exactly as it promotes every warning. No door treats it as an error otherwise.httpdescriptor'sconfigSchematext inservice-automationis [security] closeout: two stored-credential positions outside #20552's projection still reach a served read unredacted (a flow http node's signingSecret; the /meta list read's raw fallback) #20590's face (PR fix(service-automation): the http node's url and headers describes route an outbound credential to a connector's credentialRef, and the showcase says its webhook url is served (#20590) #20672), and it is being tightened to the same shape routing. The landed flows-guide callout is filed separately for its url-path over-reach, and this PR does not copy it.httpexecutor's interpolation token (a single-brace{…}span with no brace inside, astemplate.tsinterpolates it). The module cites it; no test pins it against that file.Generated by Claude Code