|
31 | 31 | from log import get_logger |
32 | 32 | from utils import checks |
33 | 33 | from utils.mcp_auth_headers import resolve_authorization_headers |
| 34 | +from utils.types import CompiledPatterns |
34 | 35 |
|
35 | 36 | logger = get_logger(__name__) |
36 | 37 |
|
@@ -2347,6 +2348,102 @@ class QuestionValidityConfig(ConfigurationBase): |
2347 | 2348 | ) |
2348 | 2349 |
|
2349 | 2350 |
|
| 2351 | +class RedactionRule(ConfigurationBase): |
| 2352 | + """A single regex-based redaction rule. |
| 2353 | +
|
| 2354 | + Attributes: |
| 2355 | + pattern: Raw regex pattern string to match sensitive data. |
| 2356 | + replacement: Text to substitute for each match. |
| 2357 | + case_sensitive: Per-rule override for case sensitivity. |
| 2358 | + When None, the global ``RedactionConfig.case_sensitive`` |
| 2359 | + flag applies. |
| 2360 | + """ |
| 2361 | + |
| 2362 | + pattern: str = Field( |
| 2363 | + ..., |
| 2364 | + title="Pattern", |
| 2365 | + description="Regex pattern to match sensitive data", |
| 2366 | + ) |
| 2367 | + replacement: str = Field( |
| 2368 | + ..., |
| 2369 | + title="Replacement", |
| 2370 | + description="Replacement string for matched text", |
| 2371 | + ) |
| 2372 | + case_sensitive: bool | None = Field( |
| 2373 | + None, |
| 2374 | + title="Case sensitive", |
| 2375 | + description=( |
| 2376 | + "Per-rule case sensitivity override. " |
| 2377 | + "When None, the global config flag applies." |
| 2378 | + ), |
| 2379 | + ) |
| 2380 | + |
| 2381 | + |
| 2382 | +class RedactionConfig(ConfigurationBase): |
| 2383 | + """Configuration for PII redaction with regex-based rules. |
| 2384 | +
|
| 2385 | + Rules are validated and compiled at construction time. Invalid |
| 2386 | + regex patterns raise a ``ValueError`` immediately. |
| 2387 | +
|
| 2388 | + Attributes: |
| 2389 | + rules: Ordered list of redaction rules applied sequentially. |
| 2390 | + case_sensitive: When False, patterns are compiled with |
| 2391 | + ``re.IGNORECASE``. Defaults to False. |
| 2392 | + """ |
| 2393 | + |
| 2394 | + rules: list[RedactionRule] = Field( |
| 2395 | + default_factory=list, |
| 2396 | + title="Redaction rules", |
| 2397 | + description="Ordered list of PII redaction rules", |
| 2398 | + ) |
| 2399 | + case_sensitive: bool = Field( |
| 2400 | + False, |
| 2401 | + title="Case sensitive", |
| 2402 | + description=("When False, patterns are compiled with re.IGNORECASE"), |
| 2403 | + ) |
| 2404 | + |
| 2405 | + _compiled_patterns: CompiledPatterns = PrivateAttr( |
| 2406 | + default_factory=list, |
| 2407 | + ) |
| 2408 | + |
| 2409 | + @model_validator(mode="after") |
| 2410 | + def compile_patterns(self) -> Self: |
| 2411 | + """Compile regex patterns and reject invalid ones. |
| 2412 | +
|
| 2413 | + Per-rule ``case_sensitive`` overrides the global flag when set. |
| 2414 | +
|
| 2415 | + Raises: |
| 2416 | + ValueError: If any rule contains an invalid regex pattern. |
| 2417 | +
|
| 2418 | + Returns: |
| 2419 | + The validated configuration instance. |
| 2420 | + """ |
| 2421 | + global_case_sensitive = self.case_sensitive |
| 2422 | + compiled: CompiledPatterns = [] |
| 2423 | + for rule in self.rules: |
| 2424 | + effective = ( |
| 2425 | + rule.case_sensitive |
| 2426 | + if rule.case_sensitive is not None |
| 2427 | + else global_case_sensitive |
| 2428 | + ) |
| 2429 | + flags = 0 if effective else re.IGNORECASE |
| 2430 | + try: |
| 2431 | + pattern = re.compile(rule.pattern, flags) |
| 2432 | + except re.error as e: |
| 2433 | + raise ValueError(f"Invalid regex pattern: {rule.pattern}: {e}") from e |
| 2434 | + compiled.append((pattern, rule.replacement)) |
| 2435 | + self._compiled_patterns = compiled |
| 2436 | + return self |
| 2437 | + |
| 2438 | + @property |
| 2439 | + def compiled_patterns(self) -> CompiledPatterns: |
| 2440 | + """Pre-compiled (regex, replacement) pairs. |
| 2441 | +
|
| 2442 | + Returns a shallow copy to prevent mutation of internal state. |
| 2443 | + """ |
| 2444 | + return list(self._compiled_patterns) |
| 2445 | + |
| 2446 | + |
2350 | 2447 | class Configuration(ConfigurationBase): |
2351 | 2448 | """Global service configuration.""" |
2352 | 2449 |
|
|
0 commit comments