LibreChat/api
Danny Avila 09da878a47
🚰 fix: Throttle Shared Link Reads and Aggregate Audit Findings (#15605)
* 🪵 fix: Aggregate Audit Findings and Rate Limit Shared Link Reads

Audit-mode content filter rules wrote one `logger.info` per matching
fragment, and inspection deliberately continues past every finding. A
shared conversation full of distinct matching strings therefore produced
one log write per fragment, regenerated on every retrieval of
`GET /api/share/:shareId` — a public, unauthenticated route with no
limiter. The 4,096-entry inspection dedupe set is a memory bound, not an
event budget: once full, further values are inspected and logged again.

Audit metadata carries no inspected text — only rule, source, field and
provenance — so thousands of those writes were byte-identical lines.
Findings are now counted inside an aggregation scope and reported once
per distinct key with an `occurrences` count when the scope ends:

- `assertModelBoundContent` opens a scope when audit rules are configured,
  bounding one inspection pass (a single message's fragments).
- `assertConversationImportContentAllowed` opens the outer scope for
  import and shared-link snapshots, so per-message inspections nest into
  one report instead of one per message.
- `createShareContentPreflight` spans the shared-file metadata pass too.

Nested scopes reuse the outermost aggregation, and outside any scope the
previous immediate write is kept.

`GET /api/share/:shareId` re-inspects the whole snapshot on every request,
so it now carries IP and user limiters like the neighbouring fork route
(`SHARE_IP_MAX`/`SHARE_IP_WINDOW`, `SHARE_USER_MAX`/`SHARE_USER_WINDOW`,
`SHARE_VIOLATION_SCORE`); the user limiter skips anonymous viewers.

* 🔒 fix: Address Codex Findings on Share Audit Aggregation

- The aggregation key joined rule fields with a space, but the filter
  schema allows spaces in custom pattern ids and labels, so
  `{id: 'a', label: 'b c'}` and `{id: 'a b', label: 'c'}` collided on the
  same source/field/provenance and merged two rules' counts under one
  identity. Serialize the tuple instead.
- `SHARE_VIOLATION_SCORE` left unset reached `logViolation` as
  `undefined`, which selects that function's default score of 1 rather
  than the documented zero. With default ban settings a viewer refreshing
  a rate-limited shared link would be banned after 20 rejections. Default
  the score to 0 in code, not only in `.env.example`.
- `removePorts` returns a full IPv6 address, so an attacker holding a
  prefix could rotate the host portion for a fresh bucket on every
  request — and for anonymous viewers the IP limiter is the only bound.
  Derive the key through `ipKeyGenerator` so IPv6 clients group by /56.
2026-09-04 12:38:34 -04:00
..
app 🫙 fix: Drop Blank Content Blocks From Promptless Sends (#15530) 2026-09-02 20:15:00 -04:00
cache 🚰 fix: Throttle Shared Link Reads and Aggregate Audit Findings (#15605) 2026-09-04 12:38:34 -04:00
config 🫆 chore: Remove Published Credential Defaults (#14680) 2026-08-07 07:25:05 -04:00
db 🐇 refactor: Decouple Meilisearch From Document Saves (#15109) 2026-08-21 22:26:44 -04:00
models
server 🚰 fix: Throttle Shared Link Reads and Aggregate Audit Findings (#15605) 2026-09-04 12:38:34 -04:00
strategies 🔐 fix: Keep OpenID Metadata Out of Logs and Traces (#15470) 2026-09-01 13:22:12 -04:00
test 🔐 fix: Redirect OAuth Browser Navigations Instead of Rendering JSON (#15440) 2026-08-31 21:13:47 -04:00
utils ⚡ feat: Add Gemini 3.8 Flash Support (#15519) 2026-09-02 15:06:40 -04:00
jest.config.js 🎨 feat: Theme-Adaptive SVG Support for Custom MCP and Group Icons (#13817) 2026-09-04 10:49:43 -04:00
jsconfig.json
package.json 🎨 feat: Theme-Adaptive SVG Support for Custom MCP and Group Icons (#13817) 2026-09-04 10:49:43 -04:00
typedefs.js