Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 157 additions & 4 deletions docs/site/docs/filters.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,11 @@
block: false, // Set true to block the request
payload: "", // Modified JSON payload (or empty for no change)
messages: [], // Alternative: modified message array
message: "" // Optional reason/log message
message: "", // Optional reason/log message
compliance_events: [] // Optional: compliance audit events (see Compliance Event Reporting)
}
```

Check warning on line 175 in docs/site/docs/filters.md

View check run for this annotation

probelabs / Visor: security

security Issue

The documentation for the `compliance_events` feature does not specify any limits on the number or size of events that can be emitted from a single script execution. A malicious or buggy script could generate a very large array of compliance events, leading to high memory consumption and load on the analytics pipeline, which could be a vector for a denial-of-service (DoS) attack.
Raw output
The backend should enforce a reasonable limit on the number and total size of compliance events per request. This limit should be clearly documented where the `compliance_events` field is introduced to guide script authors.
---

#### **Example Script 1: Blocking Filter (PII Detection)**
Expand Down Expand Up @@ -767,6 +768,157 @@

---

## Compliance Event Reporting

Filter scripts can emit **compliance events** for non-blocking governance reporting. These events are recorded in the analytics pipeline and stored for compliance auditing. Compliance events never affect the filter's block/allow decision -- they are purely informational.

This is useful for tracking:
- PII redactions performed by filters
- Content rewrites or modifications
- Policy violations detected (even when not blocking)
- Silent failures or degraded processing

### Compliance Event Object

Each compliance event has the following fields:

| Field | Type | Required | Description |
|-------|------|----------|-------------|

Check warning on line 786 in docs/site/docs/filters.md

View check run for this annotation

probelabs / Visor: security

security Issue

The documentation for the `description` and `metadata` fields in compliance events does not warn about the security and privacy risks of including raw user or LLM-generated content. Including raw input in these fields can lead to: 1. Stored XSS vulnerabilities if the content is rendered without proper escaping in an analytics UI. 2. Sensitive data leaks (e.g., PII, credentials, private conversations) being stored in the compliance event log, which may violate data protection policies and regulations.
Raw output
Update the documentation to explicitly warn developers not to include raw, un-sanitized user or LLM content in compliance events. A security note should be added to the description of both the `description` and `metadata` fields. For example: 'Human-readable description of what happened. **Security Note:** Avoid including raw user or LLM content to prevent Stored XSS vulnerabilities and sensitive data leaks. Log matched rule identifiers or sanitized data instead.'
| `event_type` | string | Yes | Free-form event type (e.g., `"pii_redacted"`, `"policy_violation"`, `"content_rewritten"`) |
| `severity` | string | No | `"info"`, `"warning"`, or `"critical"` (defaults to `"info"` if omitted or invalid) |
| `description` | string | No | Human-readable description of what happened |
| `metadata` | map | No | Arbitrary key-value data for additional context |

**Validation rules:**
- Events without an `event_type` are silently skipped
- Invalid `severity` values default to `"info"`

### Example: Request Filter with Compliance Events

```tengo
tyk := import("tyk")

// Redact email addresses
modified_payload := tyk.redact_pattern(
input,
"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}",
"[EMAIL_REDACTED]"
)

output := {
block: false,
payload: modified_payload,
message: "",
compliance_events: [
{
event_type: "pii_redacted",
severity: "info",
description: "Email addresses redacted from request",
metadata: { "redacted_types": ["email"] }
}
]
}
```

### Example: Conditional Compliance Events

When events should only be emitted under certain conditions, build the list before the output:

```tengo
text := import("text")

should_block := false
found_keyword := ""

for msg in input.messages {
if msg.role == "user" {
if text.contains(msg.content, "password") {
should_block = true
found_keyword = "password"
break
}
}
}

compliance_events_list := []
if should_block {
compliance_events_list = [
{
event_type: "sensitive_content_detected",
severity: "critical",
description: "Blocked: keyword '" + found_keyword + "' detected",
metadata: { "matched_pattern": found_keyword }
}
]
}

output := {
block: should_block,
payload: input.raw_input,
message: should_block ? "Blocked: contains sensitive content" : "",
compliance_events: compliance_events_list
}
```

### Example: Response Filter with Compliance Events

Response filters also support compliance events. This is useful for logging when harmful content is detected in LLM responses:

```tengo
text := import("text")

response_text := input.is_chunk ? input.current_buffer : input.raw_input

output := {
block: false,
message: ""
}

if !input.is_chunk || len(response_text) >= 150 {
harmful := ["instructions for making", "how to build a weapon"]
is_harmful := false
detected := ""

for pattern in harmful {
if text.contains(text.to_lower(response_text), pattern) {
is_harmful = true
detected = pattern
break
}
}

compliance_events_list := []
if is_harmful {
compliance_events_list = [
{
event_type: "harmful_content_detected",
severity: "critical",
description: "Harmful pattern detected: '" + detected + "'",
metadata: { "matched_pattern": detected }
}
]
}

output = {
block: is_harmful,
message: is_harmful ? "Response blocked: harmful content detected" : "",
compliance_events: compliance_events_list
}
}
```

### Querying Compliance Events

Compliance events are accessible via the admin API:

```
GET /api/v1/compliance/events?start_date=2025-01-01&end_date=2025-01-31&severity=critical
```

Query parameters: `start_date`, `end_date`, `app_id`, `event_type`, `severity`, `limit`, `offset`.

---

## Response Filters

**Response Filters** enable administrators to block LLM responses based on content analysis, providing governance controls on what LLMs can say to end users.
Expand Down Expand Up @@ -826,12 +978,13 @@
**Output Object:**
```tengo
output := {
block: false, // Set true to block/interrupt response
message: "" // Block reason (shown to user)
block: false, // Set true to block/interrupt response
message: "", // Block reason (shown to user)
compliance_events: [] // Optional: compliance audit events (supported)
}
```

**Note**: The `payload` and `messages` fields in output are **ignored** for response filters.
**Note**: The `payload` and `messages` fields in output are **ignored** for response filters. However, `compliance_events` are fully supported and will be processed and stored for audit purposes.

### Example 1: Block Refund Promises (Works for Streaming and Non-Streaming)

Expand Down
88 changes: 87 additions & 1 deletion examples/filter-scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ output := {
block: false, // Set true to block the request
payload: "", // Modified JSON payload (empty = no change)
messages: [], // Alternative: return modified message array
message: "" // Optional reason/log message
message: "", // Optional reason/log message
compliance_events: [] // Optional: compliance events for audit trail (see below)
}
```

Expand Down Expand Up @@ -332,6 +333,91 @@ modified := tyk.redact_pattern(input, "\\d{3}-\\d{2}-\\d{4}", "[SSN]")
7. **Check roles before modifying** - Different logic for system, user, assistant messages
8. **Handle empty arrays** - Check `len(input.messages)` before iterating

## Compliance Event Reporting

Filters can emit **compliance events** for non-blocking governance reporting. These events flow through the analytics pipeline and are stored for compliance auditing. They never affect the filter's block/allow decision.

### Compliance Event Object

```javascript
{
event_type: "pii_redacted", // Required - free-form string (events without this are skipped)
severity: "warning", // Optional - "info", "warning", or "critical" (defaults to "info")
description: "SSN pattern redacted", // Optional - human-readable description
metadata: { "pattern": "ssn" } // Optional - arbitrary key-value data
}
```

**Validation rules:**
- `event_type` is required. Events missing this field are silently skipped.
- `severity` must be `"info"`, `"warning"`, or `"critical"`. Invalid values default to `"info"`.

### Example: PII Redaction with Compliance Reporting

```javascript
tyk := import("tyk")

// Redact email addresses
modified_payload := tyk.redact_pattern(
input,
"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}",
"[EMAIL_REDACTED]"
)

output := {
block: false,
payload: modified_payload,
message: "",
compliance_events: [
{
event_type: "pii_redacted",
severity: "info",
description: "Email addresses redacted",
metadata: { "redacted_types": ["email"] }
}
]
}
```

### Example: Conditional Compliance Events

```javascript
text := import("text")

should_block := false
found_keyword := ""

for msg in input.messages {
if msg.role == "user" {
if text.contains(msg.content, "password") {
should_block = true
found_keyword = "password"
break
}
}
}

// Only emit a compliance event when something is detected
compliance_events_list := []
if should_block {
compliance_events_list = [
{
event_type: "sensitive_content_detected",
severity: "critical",
description: "Blocked: keyword '" + found_keyword + "' detected",
metadata: { "matched_pattern": found_keyword }
}
]
}

output := {
block: should_block,
payload: input.raw_input,
message: should_block ? "Blocked: contains sensitive content" : "",
compliance_events: compliance_events_list
}
```

## Debugging Tips

```javascript
Expand Down
15 changes: 14 additions & 1 deletion examples/filter-scripts/content-blocker.tengo
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,22 @@
}
}
}

Check warning on line 27 in examples/filter-scripts/content-blocker.tengo

View check run for this annotation

probelabs / Visor: quality

undefined Issue

The PR adds or significantly modifies several example filter scripts in the `examples/filter-scripts/` directory, but does not include automated tests to verify their correctness. These scripts contain business logic (e.g., conditionals, loops) and are provided to users as working examples. Without tests, there is a risk of regressions or shipping non-functional examples. This applies to all `.tengo` scripts modified in this PR.
Raw output
Implement automated tests for the example scripts. A test runner could execute each script with predefined inputs and assert that the output, including the `block` status and the structure of `compliance_events`, is correct for both positive and negative cases. This would ensure the examples remain valid and functional as the underlying scripting engine or libraries evolve.
compliance_events_list := []
if should_block {
compliance_events_list = [
{
event_type: "sensitive_content_detected",
severity: "critical",
description: "Blocked: sensitive keyword '" + found_keyword + "' detected",
metadata: { "matched_pattern": found_keyword }
}
]
}

output := {
block: should_block,
payload: input.raw_input,
message: should_block ? "Blocked: message contains '" + found_keyword + "'" : ""
message: should_block ? "Blocked: message contains '" + found_keyword + "'" : "",
compliance_events: compliance_events_list
}
10 changes: 9 additions & 1 deletion examples/filter-scripts/email-redaction.tengo
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,13 @@ modified_payload := tyk.redact_pattern(
output := {
block: false,
payload: modified_payload,
message: ""
message: "",
compliance_events: [
{
event_type: "pii_redacted",
severity: "info",
description: "Email addresses redacted",
metadata: { "redacted_types": ["email"] }
}
]
}
10 changes: 9 additions & 1 deletion examples/filter-scripts/pii-redaction.tengo
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,13 @@ final_payload := tyk.redact_pattern(
output := {
block: false,
payload: final_payload,
message: "PII redacted"
message: "PII redacted",
compliance_events: [
{
event_type: "pii_redacted",
severity: "warning",
description: "Email, phone, and SSN patterns redacted",
metadata: { "redacted_types": ["email", "phone", "ssn"] }
}
]
}
16 changes: 15 additions & 1 deletion examples/filter-scripts/response-block-refunds.tengo
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,23 @@ if !input.is_chunk || len(check_text) >= min_buffer_length {
}
}

// Build compliance events for audit trail
compliance_events_list := []
if should_block {
compliance_events_list = [
{
event_type: "refund_promise_detected",
severity: "critical",
description: "LLM attempted to promise a refund: '" + matched_pattern + "'",
metadata: { "matched_pattern": matched_pattern }
}
]
}

// Update output (use = not :=)
output = {
block: should_block,
message: should_block ? "Response blocked: Contains forbidden phrase '" + matched_pattern + "'" : ""
message: should_block ? "Response blocked: Contains forbidden phrase '" + matched_pattern + "'" : "",
compliance_events: compliance_events_list
}
}
15 changes: 14 additions & 1 deletion examples/filter-scripts/response-harmful-content.tengo
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,21 @@ if !input.is_chunk || len(response_text) >= min_evaluation_length {
}
}

compliance_events_list := []
if is_harmful {
compliance_events_list = [
{
event_type: "harmful_content_detected",
severity: "critical",
description: "Harmful pattern detected: '" + detected_pattern + "'",
metadata: { "matched_pattern": detected_pattern }
}
]
}

output = {
block: is_harmful,
message: is_harmful ? "Response blocked: Potentially harmful content detected (" + detected_pattern + ")" : ""
message: is_harmful ? "Response blocked: Potentially harmful content detected (" + detected_pattern + ")" : "",
compliance_events: compliance_events_list
}
}
Loading
Loading