Skip to content

🚦 feat: Gate Provider Text Before SDK Release - #593

Open
lia-by-librechat[bot] wants to merge 12 commits into
mainfrom
lia/pii-provider-text
Open

lia-by-librechat[bot] wants to merge 12 commits into
mainfrom
lia/pii-provider-text

Conversation

@lia-by-librechat

@lia-by-librechat lia-by-librechat Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Implements AI-2214 / PII B1. Default-off, injected provider-text protection runs before native aggregation/callback publication and SDK state/reuse. An onChunk filter is too late.

  • Await canonical accept/redact/block decisions on bounded per-attempt prose.
  • Bypass read-ahead smoothing only for protected attempts. Share concurrent budgets with child graphs. Fail closed on missing/failed handlers, incompatible contracts, timeout, overflow and Stop. Quarantine late completion.
  • Preserve validated reasoning, signatures, tool arguments, IDs, lifecycle fields and usage. Reject unchecked aliases.
  • Certify one terminal provider plus unchanged SDK instruction prefixes. Reject arbitrary sequences, shell overrides/config factories and internally streaming invoke before production.
  • Export capability version 1. No detector dependency, universal-hook redesign, activation or package publication.

Evidence

A1 corpus from LibreChat #16726 drives real SDK dispatch tests: every two-chunk split, allowed controls, OpenAI/Anthropic HTTP and Bedrock transports, native callbacks, registered/local dispatch, primary/fallback/retry, parallel budgets, Stop/late completion and model/tool reuse. Native Anthropic nonstreaming controls and both built package entries are exercised. OpenRouter cumulative replay is inspected once, including fallback/retry and parallel candidates, registered/local dispatch and next-model reuse.

Checks at 5b7f922a

  • 828 focused tests passed across 29 suites. One pre-existing tool-stream benchmark is skipped.
  • Exact-head tsc --noEmit, CJS/ESM/declaration build, touched-file ESLint/import order and circular checks passed.
  • Built ESM/CJS streaming/native local/registered mixed turns publish canonical prose exactly once. Stop during awaited step creation prevents late prose publication.
  • Full local suites, live-provider tests and D1 trace-sink certification were not run. Exact-head independent review completed with no findings; all GitHub CI checks passed. The reviewer ran source and dependency-free lifecycle checks, not SDK Jest/types/builds; those checks were completed locally by the implementation lane.

SDK source baseline: main@bc5e34b411a500953d4cd745b1439e06780f77a6, package 4.0.2. LibreChat's A1 snapshot locks 4.0.1 and is separate evidence.

Review ledger

Finding Severity Disposition
R1 Preserve SDK provider controls P1 Fixed in b2187d33
R2 Remove completed-wait cancellation retention P1 Fixed in b2187d33
R3 Certify every sequence producer P1 Fixed in 3da55e97
R4 Prevent invoke-path raw streaming aggregation P1 Fixed in 3da55e97
R5 Preserve native nonstreaming lifecycle metadata P2 Fixed in 3da55e97
R6 Reject custom terminal-provider transforms P1 Fixed in 6bfed9d1
R7 Check effective internal-streaming request parameters P1 Fixed in 6bfed9d1
R8 Preserve admitted Anthropic input fragments P1 Fixed in 9a531c7d
R9 Enforce elapsed synchronous-policy deadlines P2 Fixed in 9a531c7d
R10 Stop queued consumers after protection trips P2 Fixed in 7a0c5a70
R11 Stop approved ToolNode batches after protection trips P1 Fixed in 615c447a
R12 Propagate foreground child protection failure P1 Fixed in 615c447a
R13 Preserve protected cooperative restart control flow P2 Hardened in 615c447a
R14 Preserve native malformed-tool diagnostics and recovery P2 Fixed in bbb5b1c8
R15 Honor captured inherited trips before eager child dispatch P1 Fixed in 2239626f
R16 Retain raw producer leases beyond smoother close grace P2 Fixed in 2239626f
R17 Normalize bounded OpenRouter cumulative prose before policy P2 Fixed in 454ad7c0
R18 Publish delayed prose after tool-call controls P2 Fixed in 5b7f922a

Swept producer admission, alternate generation routes, callback/state ordering, cancellation/leases, recovery and policy inheritance. Queued consumer regressions include real Run publication paused across a policy rejection, parsed and raw-seal eager dispatch, inherited parent trips and fallback preparation. Independent review completed with no findings for exact head 5b7f922a72a430ce242d5fec5a29f4fa567605d3. R1–R18 were rechecked. Earlier findings: 10 P1 and 8 P2, all fixed or hardened as recorded above; none rejected. All GitHub CI checks passed at this head.

Ownership and release

C1 owns tool-result/ToolNode enforcement. B1 leaves shared callback/hook utilities unchanged, adds a provider-specific Run/graph field and recognizes its safety errors in existing stream-breaker guards. C1 retains tool-result release and completion ownership. ToolNode changes only add the provider safety error to existing admission/pass-through predicates; no result hooks are altered. D1 independently certifies trace exports. See docs/provider-text-protection.md for supported shapes and ownership.

B2 needs an approved SDK release containing this contract, a capability-version check, a release pin and its classification/policy adapter. Unsupported routes stay gated. No npm publication or app/YAML activation.

@lia-by-librechat

Copy link
Copy Markdown
Contributor Author

Head: 86129f9. B1 adds default-off awaited provider-text release, bounded shared attempt budgets and A1 dispatch/lifecycle tests. Exact-head checks and independent review are running. SDK publication and consumer activation are not included.

@lia-by-librechat

Copy link
Copy Markdown
Contributor Author

Head: b2187d3. Fixes both initial P1 findings: preserve validated provider controls; remove completed-wait cancellation retention and bound empty/control overhead. Includes fail-closed adapter/configuration admission fixes. Focused gate tests: 43 passed. Independent review and exact-head regression checks are running. No release or activation.

@lia-by-librechat

Copy link
Copy Markdown
Contributor Author

Verified head b2187d3: 335 focused tests across 18 suites passed, including 43 B1 dispatch/lifecycle tests and tracing/subagent regressions. Typecheck, CJS/ESM/declaration build, touched-file ESLint and import-order checks passed. The 30,000-wait GC check retained zero completed results and zero abort listeners before cancellation. Both initial P1 findings are fixed. Exact-head independent review is still running.

@lia-by-librechat

Copy link
Copy Markdown
Contributor Author

Head: 3da55e9. Fixes R3/R4 (P1) and R5 (P2) after a producer-invariant sweep: reject uncertified compositions and internal-streaming invoke before production; preserve native nonstreaming controls. Gate tests: 53 passed. Types, CJS/ESM/declaration build, touched-file lint/imports and built ESM/CJS dispatch checks passed. Exact-head focused regressions and independent review are running. No release or activation.

@lia-by-librechat

Copy link
Copy Markdown
Contributor Author

Head: 6bfed9d. Fixes R6/R7 (P1): reject terminal-provider custom transform routes and check effective facade/delegate invocation parameters before original generation. Regressions cover real Run publication/state, modelKwargs, concurrent admission and per-call overrides. Exact-head focused checks and independent review are running. No release or activation.

@lia-by-librechat

Copy link
Copy Markdown
Contributor Author

Head: dc60d38. Fixture-only correction explicitly selects the guarded streaming route; the prior fixture correctly hit disabled-streaming rejection. All 58 B1 tests pass. R1-R7 remain fixed. Exact-head independent review, focused regressions and CI are running. No release or activation.

@lia-by-librechat

Copy link
Copy Markdown
Contributor Author

Head: 9a531c7. Fixes R8 (P1): preserve validated Anthropic untyped argument fragments, including interleaved calls in real registered/local Run dispatch. Fixes R9 (P2): monotonic elapsed-time checks prevent synchronous policy work from releasing after its deadline. Coalesced fragment/tool-index bookkeeping is bounded. All 68 B1 tests, types/build and touched-file checks passed. Exact-head independent review and focused regressions are running. No release or activation.

@lia-by-librechat

Copy link
Copy Markdown
Contributor Author

Head: 7a0c5a7. Fixes R10 (P2): queued and post-await stream consumers honor protection trips before eager tool dispatch. Real Run regression pauses tool-step publication across a required-policy rejection; parsed/raw-seal, inherited parent-entry and fallback-preparation checks pass. Shared callback/hook contracts are unchanged; C1 retains tool-result ownership. Exact-head independent review and focused checks are running. No release or activation.

@lia-by-librechat

Copy link
Copy Markdown
Contributor Author

Head: 615c447. Fixes R11/R12 (P1): existing ToolNode admission/pass-through guards honor provider safety errors; foreground child failures abort the captured parent breaker. Hardens R13 (P2) with SDK-owned restart provenance, preserving protected steering while Stop and host-policy failures remain terminal. C1 result hooks and shared callback contracts are unchanged. Exact-head independent review and focused regressions are running. No release or activation.

@lia-by-librechat

Copy link
Copy Markdown
Contributor Author

Head: bbb5b1c. Fixes R14 (P2): native malformed-tool diagnostics survive copying and canonical assembly instead of being reset by the chunk constructor/concat. Real OpenAI empty/prose invalid-only and mixed-valid/invalid turns preserve paired errors and next-model recovery. ESM/CJS native diagnostic checks passed. Exact-head independent review and focused checks are running. No release or activation.

@lia-by-librechat

Copy link
Copy Markdown
Contributor Author

Head: 2239626. Fixes R15 (P1): capture inherited constructor/call signals and honor trips at existing eager dispatch guards. Fixes R16 (P2): protected attempt signals bypass SDK read-ahead smoothing; raw producers retain their charged lease until actual settlement. Configured delays and unprotected smoothing are unchanged. Real Bedrock and generic adapter tests cover retention beyond smoother close grace. Exact-head independent review and focused checks are running. No release or activation.

@lia-by-librechat

Copy link
Copy Markdown
Contributor Author

Head: 454ad7c. Fixes R17 (P2): marked OpenRouter cumulative prose is normalized against bounded fragments before policy inspection. Only new suffixes append; signatures, unmarked repetition and other providers are unchanged. Real HTTP callback/state tests, fallback/retry, parallel isolation and next-model/tool reuse pass. Exact-head independent review and focused checks are running. No release or activation.

@lia-by-librechat

Copy link
Copy Markdown
Contributor Author

Head: 5b7f922. Fixes R18 (P2): delayed canonical prose gets a message-creation step after tool controls instead of being dropped from live deltas. Real OpenAI streaming/native local/registered turns publish once; Stop during awaited step creation suppresses the subsequent delta. Tool controls remain timely and exact. ESM/CJS mixed-turn publication checks passed. Exact-head independent review and focused checks are running. No release or activation.

@lia-by-librechat

Copy link
Copy Markdown
Contributor Author

Delivered head: 5b7f922. Independent exact-head review: complete, no findings. R1-R18 rechecked; earlier 10 P1 and 8 P2 findings fixed/hardened, none rejected. All GitHub CI checks passed. Local verification: 828 focused tests across 29 suites, workspace types, CJS/ESM/declaration build, touched ESLint/imports, circular check and built-entry release/publication/replay/retention checks passed. One pre-existing tool-stream benchmark is skipped. Full local suites, live-provider tests and D1 sink certification were not run. B2 requires an approved SDK release, release pin, capability version 1 check and host policy adapter. No package publication or activation.

@danny-avila

Copy link
Copy Markdown
Collaborator

@codex review the latest head, final review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-04T13:26:03.607335Z 5b7f922 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5b7f922a72

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +135 to +139
for (const key of Object.keys(message.additional_kwargs)) {
if (additionalKeys.has(key)) continue;
const validate = additionalControls[key];
if (validate == null || !validate(message.additional_kwargs[key])) {
throw new ProviderTextProtectionError('unsupported');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Admit standard Gemini control metadata

Protected Google calls fail before inspection because the built-in Gemini converter always adds __gemini_function_call_thought_signatures__ to additional_kwargs (even when the map is empty), while this allowlist rejects that key. Consequently every streamed and non-streamed Providers.GOOGLE response throws unsupported instead of releasing approved prose; safely validate and preserve the SDK-owned signature map and other certified Gemini controls.

AGENTS.md reference: AGENTS.md:L5-L5

Useful? React with 👍 / 👎.

Comment on lines +116 to +119
valid = metadataKeys.has(key) && (value == null || typeof value === 'string' ||
(typeof value === 'number' && Number.isFinite(value)));
}
if (!valid) throw new ProviderTextProtectionError('unsupported');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Admit the final OpenAI Responses metadata

When ChatOpenAI selects the Responses API, its mandatory response.completed chunk copies standard response fields such as output, status, text, and tools into response_metadata; this branch rejects those keys and structured values. Thus every protected Responses API request reaches completion and then fails with unsupported, discarding otherwise-approved output, so the boundary needs a validated allowlist for the SDK's Responses metadata shape.

AGENTS.md reference: AGENTS.md:L5-L5

Useful? React with 👍 / 👎.

Comment thread src/graphs/Graph.ts
primaryError instanceof InvalidModelToolCallError ||
primaryError instanceof ProviderTextProtectionError
) {
if (primaryError instanceof ProviderTextProtectionError) attemptBreaker.abort(primaryError);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Propagate protection trips through summarization

When one agent's policy trips while a parallel agent is compacting, this aborts the shared breaker with ProviderTextProtectionError, but findStreamLimitAbortReason and the recovery catches in src/summarization/node.ts recognize only StreamLimitExceededError. The summarization branch can therefore treat the safety abort as an ordinary provider failure, attempt fallbacks, emit completion hooks, or commit a periodic metadata stub before the run rejects; extend those summary guards to recognize and immediately rethrow the protection reason.

Useful? React with 👍 / 👎.

Comment thread src/llm/invoke.ts
Comment on lines +798 to +802
if (policy != null && config?.signal?.aborted === true) {
const reason: unknown = config.signal.reason;
const cancelled = new ProviderTextProtectionError(reason instanceof ProviderTextProtectionError ? reason.code : 'cancelled');
prepared?.finish(preparedAttempt, undefined, cancelled);
throw cancelled;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve existing safety-breaker errors

In a protected run, any aborted invocation is converted here to ProviderTextProtectionError('cancelled') unless the signal reason is already a provider-protection error. This also converts StreamLimitExceededError and PreparedSubagentError: for example, throwIfBreakerTripped throws the stream-limit reason, this catch replaces it, and parallel execution can surface a misleading cancellation instead of the actionable breaker failure. Preserve known safety reasons and only normalize generic provider abort errors.

Useful? React with 👍 / 👎.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants