RegexFilterProcessor
The RegexFilterProcessor uses regex pattern matching to filter, redact, or block content in agent messages. No LLM calls are made.
Supports built-in presets for common patterns (PII, secrets, URLs) and custom regex rules. Can be applied to input, output, or both phases.
Usage exampleDirect link to Usage example
Block PII in input messages:
import { RegexFilterProcessor } from '@mastra/core/processors'
const filter = new RegexFilterProcessor({
presets: ['pii'],
strategy: 'block',
phase: 'input',
})
Redact secrets in output:
import { RegexFilterProcessor } from '@mastra/core/processors'
const filter = new RegexFilterProcessor({
presets: ['secrets'],
strategy: 'redact',
phase: 'output',
})
Custom rules:
import { RegexFilterProcessor } from '@mastra/core/processors'
const filter = new RegexFilterProcessor({
rules: [{ name: 'internal-id', pattern: /INTERNAL-\d{6}/g, replacement: '[INTERNAL_ID]' }],
strategy: 'redact',
})
Raise the streaming carryover window for long custom matches (e.g. a fixed-length secret or a value that only matches once its closing delimiter arrives):
import { RegexFilterProcessor } from '@mastra/core/processors'
const filter = new RegexFilterProcessor({
rules: [
{
name: 'armored-key',
pattern: /-----BEGIN KEY-----[A-Z]+-----END KEY-----/g,
replacement: '[KEY]',
},
],
strategy: 'redact',
streamCarryoverSize: 256,
})
Attach to an agent:
import { Agent } from '@mastra/core/agent'
import { RegexFilterProcessor } from '@mastra/core/processors'
const agent = new Agent({
id: 'my-agent',
name: 'my-agent',
model: 'openai/gpt-5-nano',
inputProcessors: [
new RegexFilterProcessor({
presets: ['pii', 'secrets'],
strategy: 'block',
}),
],
})
Constructor parametersDirect link to Constructor parameters
rules?:
name:
pattern:
replacement?:
presets?:
strategy?:
phase?:
includeRedactedValues?:
streamCarryoverSize?:
ReturnsDirect link to Returns
id:
name:
processInput:
processOutputStream:
processOutputResult:
Error behaviorDirect link to Error behavior
When the block strategy is active (default), RegexFilterProcessor throws a TripWire error with retry: false when any pattern matches. The TripWire metadata includes:
processorId:'regex-filter'matches: Array of match objects withrule,match(redacted to'[REDACTED_MATCH]'), andindexstrategy:'block'
Built-in presetsDirect link to Built-in presets
| Preset | Patterns | Default replacement |
|---|---|---|
pii | Emails, phone numbers, SSNs, credit card numbers | [EMAIL], [PHONE], [SSN], [CREDIT_CARD] |
secrets | API keys, bearer tokens, AWS access keys | [API_KEY], [BEARER_TOKEN], [AWS_KEY] |
urls | HTTP/HTTPS URLs | [URL] |
Redaction behaviorDirect link to Redaction behavior
Every rule is matched independently, which lets two rules claim overlapping text. For example, a card number without separators matches both phone and credit-card. Overlapping matches are combined into one region and replaced once with the replacement from the longest match.
const filter = new RegexFilterProcessor({
presets: ['pii'],
strategy: 'redact',
})
// "Charge 4111111111111111 today" becomes "Charge [CREDIT_CARD] today"
A replacement string can use $1 or $& to reference capture groups for an independent single match. When matches form a combined region, the replacement string is inserted as written. The same applies when a rule relies on surrounding text through a lookbehind or lookahead. The region is redacted in either case.
Redaction reportingDirect link to Redaction reporting
Because the redact strategy rewrites text in place, downstream code can't determine what changed. Assign onViolation to record the change for each redacted message or message part and for each stream chunk, with offsets relative to that text. Async callbacks are awaited, while errors are caught so an unavailable audit sink can't fail the request.
import { RegexFilterProcessor, type RegexRedactionDetail } from '@mastra/core/processors'
const filter = new RegexFilterProcessor({
presets: ['pii'],
strategy: 'redact',
})
filter.onViolation = async ({ detail }) => {
const redaction = detail as RegexRedactionDetail
for (const entry of redaction.redactions) {
await auditLog.write({
phase: redaction.phase,
messageId: redaction.messageId,
rule: entry.rule,
offset: entry.index,
length: entry.length,
})
}
}
The callback is awaited, including in processOutputStream, where it runs for every chunk that contains a match. Keep the callback fast, or hand the work to a queue, so a slow audit sink doesn't stall a streaming response. With no callback attached, the redact path stays synchronous.
The block strategy reports through the same callback. There the processor runner invokes it when it catches the TripWire, so detail holds the tripwire metadata described under Error behavior rather than the shape below.
detail for a redaction is a RegexRedactionDetail:
strategy:
phase:
messageId?:
partIndex?:
redactions:
rule:
index:
length:
replacement:
overlappingRules?:
value?:
Values are left out by default. An audit trail that copies the data it protects widens the exposure it was added to narrow. Set includeRedactedValues only when the destination is as protected as the original, and note that the block strategy also withholds matched text from its TripWire metadata for the same reason.