🩹 fix: Redact req.body.text before block-mode denyRequest

In block mode the messagePiiFilter middleware called denyRequest
before mutating req.body.text, so the original credential still
reached denyRequest. That helper builds a user message from
req.body.text and emits a `created` SSE event for it; for an existing
conversation with a parent message id it also persists the user
message to MongoDB. The assistant turn was refused but the credential
itself surfaced in the chat-history bubble and the database row.

Moved the req.body.text mutation ahead of the block branch so every
downstream code path sees the redacted text. denyRequest now builds
its user message and any persisted row from the scrubbed string, the
warn and silent paths are unchanged, and the post-handlers
(moderateText, the agent run) continue to receive redacted input.
This commit is contained in:
Dustin Healy 2026-06-07 22:13:58 -07:00
parent 432b70cd8f
commit 4772de2f0a

View file

@ -31,6 +31,13 @@ async function messagePiiFilter(req, res, next) {
if (result.matches.length === 0) {
return next();
}
// Mutate req.body.text FIRST so every code path below (block's
// denyRequest, warn/silent's downstream middleware + controller)
// sees the redacted text. denyRequest emits a `created` SSE event
// built from req.body.text and conditionally persists that user
// message; running it on the original would leak the credential
// even though the assistant turn is refused.
req.body.text = result.text;
if (config.onMatch === 'block') {
const labels = result.matches.map((m) => m.patternLabel).join(', ');
logger.info(
@ -42,7 +49,6 @@ async function messagePiiFilter(req, res, next) {
message: `Message blocked by PII filter: ${labels}. Edit and retry.`,
});
}
req.body.text = result.text;
if (config.onMatch === 'warn') {
req._piiPreRedactMatches = result.matches;
}