🛂 feat: Filter Model-Bound Content by Source - #14425
Conversation
|
@codex review |
There was a problem hiding this comment.
Pull request overview
This PR refactors the existing optional messageFilter.pii behavior by introducing an internal “content protection” seam in packages/api, keeping the current default behavior and response contract while making inspection source-aware via structured content fragments.
Changes:
- Introduces new internal protection types and a legacy-compatible PII inspector backed by a compiled-pattern detector with config-identity caching.
- Adds adapters that extract/normalize inspectable text fragments from chat submissions and external message arrays (including quote normalization with source indices and merged quote+text inspection).
- Updates the existing
messageFilterPiimiddleware to use the new protection seam while preserving status codes and response payload shape.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| packages/api/src/utils/quotes.ts | Adds quote-entry normalization that preserves original source-array indices for downstream adapters while keeping existing string-quote normalization behavior. |
| packages/api/src/protection/types.ts | Defines internal content fragment and finding metadata (source/provenance/path/format/treatment) for source-aware inspection. |
| packages/api/src/protection/legacy.ts | Provides legacy-compatible inspector factory + finding→legacy match conversion to preserve existing middleware/public behavior. |
| packages/api/src/protection/legacy.spec.ts | Adds tests for caching behavior, invalid-pattern warning behavior, first-match ordering, and avoiding reads after first finding. |
| packages/api/src/protection/detectors/pattern.ts | Implements the legacy-pattern detector with starter/custom regex compilation and WeakMap caching by config identity. |
| packages/api/src/protection/adapters/messages.ts | Adds a generator adapter to extract inspectable text fragments from external chat message arrays (string content and text-bearing parts). |
| packages/api/src/protection/adapters/messages.spec.ts | Tests message adapter extraction rules and declared-role non-trust behavior. |
| packages/api/src/protection/adapters/chat.ts | Adds adapter extracting chat submission fields (text/quotes/merged quote-text/answer/decisions) into ordered fragments matching legacy inspection order. |
| packages/api/src/protection/adapters/chat.spec.ts | Tests chat adapter field ordering, quote index retention, merge behavior, and robustness around unstringifiable edited arguments. |
| packages/api/src/middleware/messageFilterPii.ts | Switches the middleware’s inspection logic to the new protection seam while preserving the 400 response payload contract. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Codex Review: Didn't find any major issues. 🎉 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
@codex review |
1 similar comment
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 59627b23f7
ℹ️ 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".
|
@codex review |
1 similar comment
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8358e1d654
ℹ️ 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".
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a6b4304b2f
ℹ️ 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".
|
@codex review |
1 similar comment
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 277e062ac2
ℹ️ 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".
|
@codex review |
|
Codex Review: Didn't find any major issues. Can't wait for the next one! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 009a0bb493
ℹ️ 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".
|
@codex review |
1 similar comment
|
@codex review |
|
Codex Review: Didn't find any major issues. What shall we delve into next? Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
@codex review |
1 similar comment
|
@codex review |
|
Codex Review: Didn't find any major issues. Already looking forward to the next diff. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
@codex review |
170e125 to
dacae85
Compare
|
@codex review Please review the current PR head dacae85. Confirm that this exact commit is the reviewed commit and ignore findings that apply only to earlier heads. The prior findings are addressed by fail-closed transcript scoping across both snapshot boundaries and non-blank canonical derived-text evidence. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dacae85f9f
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7c3cf0a4b7
ℹ️ 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".
|
Codex Review: Didn't find any major issues. Can't wait for the next one! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7758e71cc5
ℹ️ 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".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9d92331388
ℹ️ 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".
| getModelBoundFileProjection() { | ||
| return projectModelBoundSourceFiles({ | ||
| messageFilesBySourceMessageId: this.message_file_map, | ||
| sourceMessages: this.modelBoundStoredMessages, | ||
| steerFileIdsBySourceMessageId: this.modelBoundSteerFileIdsBySourceMessageId, | ||
| replayHistoricalFiles: this.options.resendFiles !== false, | ||
| historicalFiles: this.authorizedHistoricalFiles, | ||
| processedCurrentFiles: Array.isArray(this.options.attachments) | ||
| ? this.options.attachments | ||
| : [], | ||
| canonicalCurrentFiles: Array.isArray(this.modelBoundCurrentFiles) | ||
| ? this.modelBoundCurrentFiles | ||
| : [], | ||
| initiallyOverflowed: this.modelBoundHistoricalFileIdsOverflowed === true, | ||
| }); |
There was a problem hiding this comment.
Move model-bound client orchestration into TypeScript
Move this model-bound projection and admission orchestration into packages/api and leave BaseClient as a thin caller. These new legacy-JavaScript methods decide which stored messages and canonical files contribute to the provider payload and whether traversal overflow fails closed, while the same file also implements deferred persistence around that admission; this security-sensitive backend logic therefore bypasses the repository-required TypeScript boundary. CLAUDE.mdL21-L24
Useful? React with 👍 / 👎.
| const prepareCodeOutputForInspection = async ({ | ||
| req, | ||
| id, | ||
| name, | ||
| session_id, |
There was a problem hiding this comment.
Move code-output inspection preparation into TypeScript
Move this generated-artifact inspection implementation into packages/api and keep the legacy service as a thin transport adapter. prepareCodeOutputForInspection and its surrounding helpers choose the trusted Code API route, enforce aggregate download limits, sanitize names, sniff MIME types, decode raw content, and decide whether extracted-text evidence is complete; those decisions directly control whether file policy fails closed, but they currently bypass the repository-required TypeScript boundary. CLAUDE.mdL21-L24
Useful? React with 👍 / 👎.
| const isStreaming = request.stream === true; | ||
| const summarizationConfig = appConfig?.summarization; | ||
|
|
||
| const uninspectableField = getBlockedOpaqueFileField(appConfig?.filters, request.input); |
There was a problem hiding this comment.
Ignore discarded Responses file payloads in fail-close checks
When files.pii.uninspectable is block for content, an Open Responses input_file containing only file_id or file_data is rejected here as opaque. This executor never forwards either value: convertInputToMessages in packages/api/src/agents/responses/service.ts converts an input_file into a text placeholder containing only its filename, so the selected file-content surface is not model-bound. Apply the opaque-file check to the actual converted/provider projection (while retaining the separate filename inspection) instead of rejecting discarded payload fields.
Useful? React with 👍 / 👎.
|
Codex Review: Didn't find any major issues. What shall we delve into next? Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
* feat: introduce optional content protection seam * feat: enforce source-aware content filters * feat: complete source-aware content enforcement * test: activate skill file-text fail-close fixtures * fix: harden source-aware content filters * fix: harden model-bound content filtering * fix: preserve legacy filters and generated files * fix: inspect shared scalar metadata * test: align mocks with current dev dependencies * feat: add persisted content filter safeguards * feat: complete source-aware content filter enforcement * fix: move resume content preflight into TypeScript * fix: close content inspection edge cases * fix: harden content protection boundaries * fix: complete content protection safeguards * test: align persisted memory filter coverage * fix: reconcile content protection with current dev * fix: reconcile content protection with latest dev * fix: close content protection review gaps * fix: enforce source-aware provider boundaries * fix: preserve legacy PII preflight semantics * test: stabilize stored branch preflight fixture * fix: defer agent writes until protected model admission * perf: harden source-aware model-bound filtering * fix: canonicalize provider lineage before validation * fix: satisfy model-bound callback type checks * perf: Bound content protection filtering work * fix: Bound submission array traversal * fix: Stabilize bounded content snapshots * fix: Scope model-bound traversal overflows * fix: Preserve scoped content inspection * fix: Accumulate aggregate traversal scopes * fix: centralize content policy boundaries * test: align deferred tool policy context * test: align controller policy mocks * style: normalize content protection imports * fix: close content policy review gaps * fix: narrow active skill policy config * fix: address content protection review boundaries * fix: retain exact provenance overflow sentinel * fix: preserve literal and scoped provenance updates * fix: narrow persisted edit provenance * fix: isolate exact overflow attribution * fix: centralize stored prompt protection * fix: fail closed on incomplete transcript evidence * fix: align canonical transcript routing * refactor: centralize content policy preflights * fix: isolate upload policy error typing * style: sort policy preflight imports * refactor: centralize content policy boundaries
* 🗂️ fix: Keep a Running Attachment Chat in the Sidebar Since #15694, a turn with model-bound attachments defers its user-message write until the model admits the payload. That write was also what created a new conversation's row, so for the whole first model call, which with a long generated script or extended thinking can run for minutes, the conversation did not exist. Any conversation-list refetch in that window (starting another chat, deleting one, reloading) dropped the running chat from the sidebar until its run finished, and GET /api/convos/:id answered 404 for it. When attachments are the only reason to defer, the new conversation's row is now written as the run starts, carrying the same fields the message write would set but no message. The message itself stays deferred until admission, and its write waits for a seed still in flight so the two upserts never race. A content policy still holds back every write, the row included, as #14425 intended. The conversation half of saveMessageToDatabase moves into saveConversationToDatabase so the seed and the message write share one path. * 🧱 refactor: Own Turn Conversation Writes in packages/api The conversation half of a turn's writes (the row a message save upserts, the retention context it stamps, and the new seed ahead of a deferred first message) moves into `conversations/save.ts`, which takes `getConvo`/`saveConvo` from the caller. BaseClient keeps only the wiring: the retention lookup that must stay synchronous for Stop, the message save, and the calls into the module. The seed passes an empty `appendMessageIds`, which tells `saveConvo` the row holds no messages yet, so it no longer reads the message list before the upsert. It also resolves the conversation once and leaves it on the request, so the deferred message save reuses it instead of looking the conversation up again. save.spec.ts drives the real data-schemas methods against an in-memory MongoDB: the seed creates an empty row without reading messages, the message save appends to it without a second lookup, an existing chat and a subagent thread are left alone, a temporary chat keeps its retention, and a failed lookup settles instead of rejecting.
Summary
I added source-aware content filtering across model-bound data, including persisted conversation context, provider payloads, files, agent resources, and PII policy transitions.
@librechat/agents3.6.9 contract.assembled_context, while retaining directcontent_partinspection.The companion provenance contract is ready in agents PR #442. It should land and publish as
@librechat/agents3.6.10 before a separate dependency bump; this PR remains safe against 3.6.9 through its conservative fallback.Change Type
Testing
@librechat/apisuccessfully.Test Configuration
CI=1 E2E_CHROMIUM_CHANNEL=chrome npx playwright test --config=e2e/playwright.config.mock.ts content-filters.persisted.spec.ts --reporter=linenpx jest src/middleware/modelBoundContent.spec.ts src/middleware/messageFilterPii.spec.ts src/protection/provenance.spec.ts src/protection/adapters/nested.spec.ts src/protection/adapters/submissions.spec.ts src/protection/adapters/messages.spec.ts src/protection/files.spec.ts src/protection/detectors/pattern.spec.ts src/protection/runtime.spec.ts --runInBandChecklist