Moderation Guardrails
Moderation & Guardrails
Overview
- The chat subsystem supports configurable guardrails for inputs and outputs with global settings, per-user overrides, optional categories, and an admin UI.
- Supports non-streaming and streaming modes (SSE). Streaming yields a final data: [DONE] on normal completion and emits an SSE error + [DONE] when a block occurs mid-stream.
Key Capabilities - Global policy with input/output enablement and default actions. - Blocklist with literals or regex and per-pattern actions/replacements. - Per-user overrides for toggle/actions/redaction and categories. - Optional categories and built-in PII redaction rules. - Runtime overrides (admin-controlled) with optional persistence to file. - Admin UI for blocklist management, overrides listing/editing, runtime settings, and a policy tester.
Configuration ([Moderation] in tldw_Server_API/Config_Files/config.txt)
- enabled (bool): master switch.
- input_enabled, output_enabled (bool): phase toggles.
- input_action, output_action: block | redact | warn.
- redact_replacement (str): default replacement when redacting.
- blocklist_file (path): default tldw_Server_API/Config_Files/moderation_blocklist.txt.
- user_overrides_file (path): JSON mapping of user_id -> overrides.
- Paths are resolved relative to the project root when not absolute.
- per_user_overrides (bool): enable per-user overrides.
- pii_enabled (bool): include built-in PII redaction rules (defaults off).
- categories_enabled (csv): categories to permit globally (empty = allow all).
- runtime_overrides_file (path): default tldw_Server_API/Config_Files/moderation_runtime_overrides.json.
- Performance/Safety (optional):
- max_scan_chars (int): scan chunk size per text (default 200000; full text is scanned in chunks).
- max_replacements_per_pattern (int): replacement limit per pattern (default 1000).
- match_window_chars (int): lookahead window to catch matches spanning chunk boundaries (default 4096).
- blocklist_write_debounce_ms (int): debounce window for blocklist writes in milliseconds (default 0=disabled). Useful to coalesce rapid edits from the Web UI.
- ENV overrides: MODERATION_* keys mirror the above.
Blocklist Grammar
- Literal: confidential project
- Regex: /secret\s+token/ (case-insensitive by default)
- Regex with flags: /secret\s+token/imsx (supported flags:
- i case-insensitive (default already applied)
- m multiline
- s dot matches newline
- x verbose)
- With action:
- forbidden term -> block
- /leak(\d+)/ -> redact:[MASK]
- /minor issue/ -> warn
- With categories (comma-separated suffix; requires whitespace before #):
- /ssn\b\d{3}-\d{2}-\d{4}/ -> redact:[SSN] #pii
- internal code name #confidential
- To include a literal # in a pattern or literal term, escape it as \#.
Per-user Overrides (user_overrides_file)
- Keys mirror [Moderation] defaults: enabled, input_enabled, output_enabled, input_action, output_action, redact_replacement.
- categories_enabled: comma-separated string or list of category names. If set, only rules with intersecting category are active.
- To explicitly clear category gating for a user (allow all categories), set categories_enabled to an empty string or empty list.
Categories Behavior
- When categories_enabled is provided (globally or per-user), only rules whose categories intersect with the enabled set will apply.
- Rules without any categories are ignored when a categories_enabled set is present. This applies uniformly to input checks, output redaction, and action evaluation.
- Built-in PII rules are tagged with {"pii", <pii_subtype>}; enabling either pii or a specific subtype (e.g., pii_email) will activate those rules.
Runtime Overrides (Admin)
- Endpoints:
- GET /api/v1/moderation/settings → runtime overrides + effective.
- PUT /api/v1/moderation/settings → body {pii_enabled?: bool, categories_enabled?: string[], persist?: bool}.
- Persistence:
- When persist=true, service writes to runtime_overrides_file and reloads policy.
- Overrides load on startup and POST /api/v1/moderation/reload.
Admin API Endpoints
- GET /api/v1/moderation/policy/effective?user_id=U → effective policy snapshot.
- POST /api/v1/moderation/reload → reload config + overrides.
- Blocklist (managed):
- GET /api/v1/moderation/blocklist/managed → {version, items} (sets ETag).
- POST /api/v1/moderation/blocklist/append (requires If-Match) → append line.
- DELETE /api/v1/moderation/blocklist/{id} (requires If-Match).
- PUT /api/v1/moderation/blocklist (replace entire file).
- POST /api/v1/moderation/blocklist/lint (dry-run validation) → validate one line or many without persisting.
- Request: { line: string } or { lines: string[] }
- Response: { items: [{ index, line, ok, pattern_type: 'literal'|'regex'|'comment'|'empty', action?, replacement?, categories?, error?, warning?, sample? }], valid_count, invalid_count }
- Notes: Use lint to pre-check regex safety (catastrophic patterns are rejected) and parse per-pattern actions (block|redact|warn) and #categories before appending or saving.
- Per-user Overrides:
- GET /api/v1/moderation/users → list all.
- GET /api/v1/moderation/users/{user_id} → get.
- PUT /api/v1/moderation/users/{user_id} → upsert.
- DELETE /api/v1/moderation/users/{user_id} → delete.
- Tester:
- POST /api/v1/moderation/test → {flagged, action, sample, redacted_text?, effective, category?}.
- Note: sample is a sanitized snippet (not the raw match or regex pattern). It redacts the matched portion using the effective redaction replacement to avoid exposing sensitive content.
- Regex tester honors /regex/flags and category gating.
Web UI
- /moderation: Moderation Review queue. This is the reviewer workflow for sanitized items that were captured from moderation outcomes. Reviewers can filter/search/sort the queue, inspect sanitized context and policy snapshots, record decisions, undo recent eligible decisions, review decision history, and apply bulk decisions with partial-failure feedback.
- /moderation/rules: Content Rules configuration. This is the administrator workflow for runtime settings, managed blocklist, per-user overrides, and the tester sandbox.
- /moderation-playground: legacy redirect to /moderation/rules.
Moderation Review
- Purpose: queue moderation outcomes that need human review without exposing raw unsafe content.
- Capture gate:
- MODERATION_REVIEW_CAPTURE_ENABLED: when truthy, supported moderation outcomes are captured into the review queue.
- MODERATION_REVIEW_DB_PATH: optional SQLite path for review queue persistence. Defaults to tldw_Server_API/Databases/moderation_review.db.
- Review item data is intentionally sanitized:
- excerpt, context, effective_policy, and matches are the only content-bearing fields surfaced to reviewers.
- safe_fields tells the UI which fields are allowed to render.
- Raw rule patterns and raw model/user text are not exposed through review item detail.
- Redacted review items keep top-level metadata and audit records, but replace excerpt, context, and match samples with safe placeholders.
- Decision auditability:
- Item detail includes sanitized decision history: actor id, action, resulting status, reason, timestamps, undo eligibility, undo expiry, and redaction state.
- Undo tokens are returned only at decision time, are stored hashed, expire, are single-use, and fail if a later decision superseded the original decision.
- GET /api/v1/moderation/review/audit lists sanitized audit events and supports filtering by item, decision, actor, action, date range, cursor, and limit.
- Review endpoints use moderation review permissions rather than the content-rules SYSTEM_CONFIGURE permission:
- MODERATION_REVIEW_READ: list and inspect review items.
- MODERATION_REVIEW_DECIDE: record and undo single-item decisions.
- MODERATION_REVIEW_BULK_DECIDE: record bulk decisions.
- MODERATION_AUDIT_READ: list sanitized review audit events.
- Known unsupported producer states:
- Review capture currently covers moderation outcomes wired through the review capture helper. Additional producers should call capture_moderation_review_item with sanitized payloads rather than writing directly to the review database.
- There is no standalone audit export endpoint yet. Use filtered audit listing until an export contract is designed.
Streaming Behavior
- Streaming SSE always ends with data: [DONE] on normal termination.
- When an output block occurs mid-stream, an SSE error payload is emitted, followed by data: [DONE].
Metrics
- chat_moderation_input_flag_total{user_id,action,category}
- chat_moderation_output_redact_total{user_id,category,streaming}
- chat_moderation_output_block_total{user_id,category,streaming}
- chat_moderation_stream_block_total{user_id,category}
- Category label prefers a more specific subtype (e.g., pii_email) over generic pii when available.
Audit
- SECURITY_VIOLATION events on moderation actions with metadata: {phase, action, pattern, streaming?}.
- Blocks are recorded with result=failure, redactions with result=success.
Best Practices
- Prefer literals when possible; use bounded regexes.
- Avoid catastrophic patterns. The service rejects dangerous regex (nested quantifiers, excessive groups) and applies scan budgets.
- Use categories to enable optional rules (e.g., pii) selectively.
Testing
- Unit tests cover:
- Input block 400, output redaction (non-stream), streaming redaction, streaming block with SSE error + [DONE].
- Categories gating for PII (test_moderation_categories.py).
- Run:
- python -m pytest -q tldw_Server_API/tests/Chat_NEW/integration/test_moderation.py
- python -m pytest -q tldw_Server_API/tests/Chat_NEW/integration/test_moderation_categories.py
Notes - Runtime overrides are non-destructive and can be removed by deleting keys or the overrides file.