fix(core)!: redact function-valued config off the public __config (P0)#477
Open
rejifald wants to merge 1 commit into
Open
fix(core)!: redact function-valued config off the public __config (P0)#477rejifald wants to merge 1 commit into
rejifald wants to merge 1 commit into
Conversation
The enumerable `__config` — the public, JSON view of a stitch that `.report()`, traces, `mcp`, `diagram`, and `stitch export --openapi` all read — leaked every function an author configured: url/baseUrl thunks, `transform`, `hooks.*`, `paginate.next`/`items`, the predicate forms of `acceptStatus`/`retry.on`/`throttle.on`, and the `idempotency`/`cache` `keyOf`. Thirteen live closures on a surface that is meant to be plain JSON data (CONTRACT.md P0). Two consequences: exfil-at-rest (a closure can capture secrets — ADR 0002 §4/§6) and silent corruption (a function drops on `JSON.stringify`, so every serialised view of the stitch was lossy). `redactConfig` now strips every function-valued field. The engine already reads all that sugar off the non-enumerable `__rawConfig` (which also backs fragment composition), so nothing runtime-relevant changes. Each nested slot is copied FRESH so `__rawConfig` is never mutated; a dynamic `stripFns` drops the derivation fns without naming a field, so it removes both a canonical name and its @deprecated alias (`key`/`keyOf`) and won't rot on a rename. Two consumers that introspected those functions off `__config` are rerouted: - `toOpenApi`: a thunk endpoint no longer survives redaction, so an absent endpoint now subsumes "thunk endpoint" — folded into the single skip-with-warning branch (the warning still names the thunk case). - `pipelineStages` (diagram / mcp): drops the `transform` stage — a live closure can't be reported from the redacted view. Its two tests are updated to match. Adds `contract-p0.spec.ts`: zero fn-paths on `__config`, a lossless JSON round-trip, field-by-field redaction, and the sugar retained on `__rawConfig` (verified RED without the fix). The type still carries the fn forms (loose); #470 additionally narrows `RedactedStitchConfig`. Carved out of the #470 contract-freeze sweep so this load-bearing redaction fix lands and is reviewed on its own. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug (CONTRACT.md P0)
__configis the public, JSON view of a stitch — the redacted surface that.report(),trace sinks,
mcp,.diagram(), andstitch export --openapiall read. It is supposed tobe plain data (ADR 0005 Decision 11 — it round-trips as JSON). It wasn't: it carried every
function an author configured. A probe on
mainfinds 13 live closures on it:redactConfigonly stripped the four capability handles (store/auth/adapter/clock)and did a shallow spread, so every author function rode straight onto the public view.
Two consequences:
the public config is exactly the surface that boundary exists to keep clean.
JSON.stringify, so any serialized view ofthe stitch (trace, report, MCP payload) was lossy and could not round-trip.
The fix
redactConfignow strips every function-valued field so__configis plain JSON. Theengine already reads all that sugar off the non-enumerable
__rawConfig(which alsobacks fragment composition —
asConfig), so nothing runtime-relevant changes. Details:__rawConfigis never mutated).stripFnsdrops function-valued fields by value, not by name — so it removesboth a canonical derivation fn and its
@deprecatedalias (idempotency/cachekey/keyOf) and won't rot on a future rename; a scalar-shorthand slot (cache: '1m')passes through untouched. A
number[]on(status list) stays; only the predicate form goes.Two consumers that introspected those functions off
__configare rerouted:toOpenApidetected a thunk endpoint viatypeof cfg.url === 'function'. A thunk nolonger survives redaction, so "absent endpoint" now subsumes "thunk endpoint" — the two
branches fold into one skip-with-warning (the warning still names the thunk case).
pipelineStages(feeds.diagram()andmcp) drops thetransformstage: a liveclosure can't be reported from the redacted view. Its two tests are updated to match.
Test
New
contract-p0.spec.tspins the invariant: zero fn-paths on__config, a losslessJSON round-trip, field-by-field redaction (incl. the canonical
cache.keyOf, which aname-based strip would miss), and the sugar retained on
__rawConfig. Verified red onmain(3/4 fail, including the JSON round-trip) and green with the fix.Scope note
The runtime leak is fixed here; the type
RedactedStitchConfigstill structurallycarries the fn forms (it's
Omit<ResolvedStitchConfig, caps>), so the public view is typedlooser than it now behaves — #470 additionally narrows the type. One localized alias in
redactConfigbridges the still-loose type; it's commented to retire on that narrowing.Carved out of the #470 contract-freeze sweep — the single highest-value semantic change in
it — so this load-bearing redaction fix lands and is reviewed on its own.
Verification
pnpm --filter stitchapi test(1218 pass) +check:types+eslintclean; full-workspacepre-push suite green.
🤖 Generated with Claude Code