Moderation-Guardrails.md
May 13, 2026 · View on GitHub
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): defaulttldw_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): defaulttldw_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:icase-insensitive (default already applied)mmultilinesdot matches newlinexverbose)
- 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] #piiinternal 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_enabledto an empty string or empty list.
Categories Behavior
- When
categories_enabledis provided (globally or per-user), only rules whosecategoriesintersect with the enabled set will apply. - Rules without any categories are ignored when a
categories_enabledset is present. This applies uniformly to input checks, output redaction, and action evaluation. - Built-in PII rules are tagged with
{"pii", <pii_subtype>}; enabling eitherpiior 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 toruntime_overrides_fileand reloads policy. - Overrides load on startup and
POST /api/v1/moderation/reload.
- When
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}(setsETag).POST /api/v1/moderation/blocklist/append(requiresIf-Match) → append line.DELETE /api/v1/moderation/blocklist/{id}(requiresIf-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#categoriesbefore appending or saving.
- Request:
- 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:
sampleis 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/flagsand 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 totldw_Server_API/Databases/moderation_review.db.
- Review item data is intentionally sanitized:
excerpt,context,effective_policy, andmatchesare the only content-bearing fields surfaced to reviewers.safe_fieldstells 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/auditlists 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_CONFIGUREpermission: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_itemwith 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.
- Review capture currently covers moderation outcomes wired through the review capture helper. Additional producers should call
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 genericpiiwhen available.
Audit
- SECURITY_VIOLATION events on moderation actions with metadata:
{phase, action, pattern, streaming?}. - Blocks are recorded with
result=failure, redactions withresult=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).
- Input block 400, output redaction (non-stream), streaming redaction, streaming block with SSE error +
- Run:
python -m pytest -q tldw_Server_API/tests/Chat_NEW/integration/test_moderation.pypython -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.