Repository navigation
fix(sse): stop PII sanitizer splicing OpenRouter metadata into content (#13488) - #13792
Merged
diegosouzapw merged 2 commits intoSep 16, 2026
Conversation
#13488) createSseTextTransform's sanitizeObject classified any string field not in a hard-coded METADATA_KEYS deny-list as "content" (getFieldCategory default), so OpenRouter's top-level `provider`, `native_finish_reason`, and `reasoning_details[].format` fell into the same per-choice sliding-window buffer in streamingPiiTransform.ts as the actual delta.content text. Both files carried independent copies of METADATA_KEYS that had already drifted. Added those three keys to a single shared METADATA_KEYS set (exported from sseTextTransform.ts, consumed by both files) and gave classifyField a parentKey parameter so `reasoning_details[].text` classifies as "reasoning" instead of the ambiguous default "content" — closing the specific splice without breaking the existing behavior of routing genuinely unrecognized JSON shapes' string fields through the content buffer. Regression test: tests/unit/issue-13488-pii-openrouter-metadata-splice.test.ts (RED on unfixed code: reassembled content came back scrambled with provider-name fragments; GREEN after the fix, plus redact-mode and a second concurrent metadata field coverage).
…router-metadata (base-red fix #13747)
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
diegosouzapw#13488) (diegosouzapw#13792) Merged in the 2026-09-16 sweep of the maintainer's own open PRs, at the owner's explicit instruction. No push was made to the PR branch: the merge took the head as the owning session left it (verified OPEN, non-draft and MERGEABLE against the release tip immediately before merging).
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.
Closes #13488
Root cause
createSseTextTransform'ssanitizeObject(src/lib/sseTextTransform.ts) classified anystring field not in a hard-coded
METADATA_KEYSdeny-list as"content"viagetFieldCategory's default branch. OpenRouter SSE chunks carry a top-levelproviderstring and
delta.reasoning_details[].text/.formatalongsidedelta.contenton the samechoice index — since neither
providernornative_finish_reasonnorreasoning_details[].formatwere in the deny-list, all of them fell into the sameper-choice
"content"sliding-window FIFO buffer instreamingPiiTransform.tsas the actualanswer text, producing interleaved/scrambled output on both sides. The two files carried
independent copies of
METADATA_KEYSthat had already drifted (one hadsystem_fingerprint, neither hadprovider).Fix
METADATA_KEYSlists into one sharedSet, exported fromsrc/lib/sseTextTransform.tsand imported bysrc/lib/streamingPiiTransform.ts; addedprovider,native_finish_reason, andformatto it.classifyField(key, parentKey)helper (replacinggetFieldCategoryat theinternal call sites) that also disambiguates
reasoning_details[].text— which isreasoning text, not answer content — from ordinary
text/content/deltafields, bytracking the enclosing object's key through the recursive walk.
classifyField/METADATA_KEYSlogic tostreamingPiiTransform.ts'sonFlushgeneric-fallback branch (clearDeltas/populateRemaining) so metadata fieldsaren't blanked to
""or repopulated with buffered answer text there either.fallback (rather than switching to a strict allow-list) —
tests/unit/piiReproduction.test.ts("THEORY-004: Data Loss in Unknown Stream Fallbacks") already encodes that non-standard/
unrecognized JSON shapes must not silently lose their text, and a strict allow-list broke
that existing, intentional behavior.
Regression test
tests/unit/issue-13488-pii-openrouter-metadata-splice.test.ts— 3 cases: the exactOpenRouter repro from the issue (mode
warn), the same shape under moderedact, and asecond concurrently-streamed metadata field (
native_finish_reason) to close the familyrather than just the one field named in the report.
RED on unfixed code:
GREEN after the fix:
Gates run
npx eslint --suppressions-location config/quality/eslint-suppressions.json src/lib/sseTextTransform.ts src/lib/streamingPiiTransform.ts tests/unit/issue-13488-pii-openrouter-metadata-splice.test.ts→ clean, exit 0.npm run typecheck:core→ exit 0, no errors.node scripts/check/check-file-size.mjs→ the only✗isopen-sse/utils/stream.ts(pre-existing, untouched by this branch — confirmed unchanged vsorigin/release/v3.8.51).node scripts/check/check-complexity.mjs→ OK — 2824 violations (baseline 3218).node scripts/check/check-cognitive-complexity.mjs→ OK — 1278 violations (baseline 1437).node scripts/check/check-test-discovery.mjs→ OK — new test file discovered.tests/unit/sseTextTransform.test.ts(16/16),tests/unit/streamingPiiTransform.test.ts(28/28),
tests/unit/streamingPiiInitialization.test.ts(1/1),tests/unit/pii-opt-in-default.test.ts(5/5),tests/unit/piiReproduction.test.ts(5/5),tests/unit/adversarialPii.test.ts(11/11),tests/unit/piiSanitizerIpv6.test.ts(18/18)— all green.
Existing tests aligned
None weakened or deleted.
tests/unit/piiReproduction.test.ts's "THEORY-004" case (arbitrarymsgkey in a non-standard JSON shape must still be treated as content, not silentlydropped) directly shaped the fix's design — kept the deny-list-plus-explicit-additions
approach instead of switching to a strict allow-list, specifically so that test's existing,
intentional contract keeps passing unmodified.