# ORLY Policy Configuration Reference This document provides a definitive reference for all policy configuration options and when each rule applies. Use this as the authoritative source for understanding policy behavior. ## Quick Reference: Read vs Write Applicability | Rule Field | Write (EVENT) | Read (REQ) | Notes | |------------|:-------------:|:----------:|-------| | `size_limit` | ✅ | ❌ | Validates incoming events only | | `content_limit` | ✅ | ❌ | Validates incoming events only | | `max_age_of_event` | ✅ | ❌ | Prevents replay attacks | | `max_age_event_in_future` | ✅ | ❌ | Prevents future-dated events | | `max_expiry_duration` | ✅ | ❌ | Requires expiration tag | | `must_have_tags` | ✅ | ❌ | Validates required tags | | `protected_required` | ✅ | ❌ | Requires NIP-70 "-" tag | | `identifier_regex` | ✅ | ❌ | Validates "d" tag format | | `tag_validation` | ✅ | ❌ | Validates tag values with regex | | `write_allow` | ✅ | ❌ | Pubkey whitelist for writing | | `write_deny` | ✅ | ❌ | Pubkey blacklist for writing | | `read_allow` | ❌ | ✅ | Pubkey whitelist for reading | | `read_deny` | ❌ | ✅ | Pubkey blacklist for reading | | `privileged` | ❌ | ✅ | Party-involved access control | | `write_allow_follows` | ✅ | ✅ | Grants **both** read AND write | | `follows_whitelist_admins` | ✅ | ✅ | Grants **both** read AND write | | `script` | ✅ | ❌ | Scripts only run for writes | --- ## Core Principle: Validation vs Filtering The policy system has two distinct modes of operation: ### Write Operations (EVENT messages) - **Purpose**: Validate and accept/reject incoming events - **All rules apply** except `read_allow`, `read_deny`, and `privileged` - Events are checked **before storage** - Rejected events are never stored ### Read Operations (REQ messages) - **Purpose**: Filter which stored events a user can retrieve - **Only access control rules apply**: `read_allow`, `read_deny`, `privileged`, `write_allow_follows`, `follows_whitelist_admins` - Validation rules (size, age, tags) do NOT apply - Scripts are NOT executed for reads - Filtering happens **after database query** --- ## Configuration Structure ```json { "default_policy": "allow|deny", "kind": { "whitelist": [1, 3, 7], "blacklist": [4, 42] }, "owners": ["hex_pubkey_64_chars"], "policy_admins": ["hex_pubkey_64_chars"], "policy_follow_whitelist_enabled": true, "global": { /* Rule object */ }, "rules": { "1": { /* Rule object for kind 1 */ }, "30023": { /* Rule object for kind 30023 */ } } } ``` --- ## Top-Level Configuration Fields ### `default_policy` **Type**: `string` **Values**: `"allow"` (default) or `"deny"` **Applies to**: Both read and write The fallback behavior when no specific rule makes a decision. ```json { "default_policy": "deny" } ``` ### `kind.whitelist` and `kind.blacklist` **Type**: `[]int` **Applies to**: Both read and write Controls which event kinds are processed at all. - **Whitelist** takes precedence: If present, ONLY whitelisted kinds are allowed - **Blacklist**: If no whitelist, these kinds are denied - **Neither**: Behavior depends on `default_policy` and whether rules exist ```json { "kind": { "whitelist": [0, 1, 3, 7, 30023] } } ``` ### `owners` **Type**: `[]string` (64-character hex pubkeys) **Applies to**: Policy administration Relay owners with full control. Merged with `ORLY_OWNERS` environment variable. ```json { "owners": ["4a93c5ac0c6f49d2c7e7a5b8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8"] } ``` ### `policy_admins` **Type**: `[]string` (64-character hex pubkeys) **Applies to**: Policy administration Pubkeys that can update policy via kind 12345 events (with restrictions). ### `policy_follow_whitelist_enabled` **Type**: `boolean` **Default**: `false` **Applies to**: Both read and write (when `write_allow_follows` is true) When enabled, allows `write_allow_follows` rules to grant access to policy admin follows. --- ## Rule Object Fields Rules can be defined in `global` (applies to all events) or `rules[kind]` (applies to specific kind). ### Access Control Fields #### `write_allow` **Type**: `[]string` (hex pubkeys) **Applies to**: Write only **Behavior**: Exclusive whitelist When present with entries, ONLY these pubkeys can write events of this kind. All others are denied. ```json { "rules": { "1": { "write_allow": ["pubkey1_hex", "pubkey2_hex"] } } } ``` **Special case**: Empty array `[]` explicitly allows all writers. #### `write_deny` **Type**: `[]string` (hex pubkeys) **Applies to**: Write only **Behavior**: Blacklist (highest priority) These pubkeys cannot write events of this kind. **Checked before allow lists.** ```json { "rules": { "1": { "write_deny": ["banned_pubkey_hex"] } } } ``` #### `read_allow` **Type**: `[]string` (hex pubkeys) **Applies to**: Read only **Behavior**: Exclusive whitelist (with OR logic for privileged) When present with entries: - If `privileged: false`: ONLY these pubkeys can read - If `privileged: true`: These pubkeys OR parties involved can read ```json { "rules": { "4": { "read_allow": ["trusted_pubkey_hex"], "privileged": true } } } ``` #### `read_deny` **Type**: `[]string` (hex pubkeys) **Applies to**: Read only **Behavior**: Blacklist (highest priority) These pubkeys cannot read events of this kind. **Checked before allow lists.** #### `privileged` **Type**: `boolean` **Default**: `false` **Applies to**: Read only When `true`, events are only readable by "parties involved": - The event author (`event.pubkey`) - Users mentioned in `p` tags **Interaction with `read_allow`**: - `read_allow` present + `privileged: true` = OR logic (in list OR party involved) - `read_allow` empty + `privileged: true` = Only parties involved - `privileged: true` alone = Only parties involved ```json { "rules": { "4": { "description": "DMs - only sender and recipient can read", "privileged": true } } } ``` #### `write_allow_follows` **Type**: `boolean` **Default**: `false` **Applies to**: Both read AND write **Requires**: `policy_follow_whitelist_enabled: true` at top level Grants **both read and write access** to pubkeys followed by policy admins. > **Important**: Despite the name, this grants BOTH read and write access. ```json { "policy_follow_whitelist_enabled": true, "rules": { "1": { "write_allow_follows": true } } } ``` #### `follows_whitelist_admins` **Type**: `[]string` (hex pubkeys) **Applies to**: Both read AND write Alternative to `write_allow_follows` that specifies which admin pubkeys' follows are whitelisted for this specific rule. ```json { "rules": { "30023": { "follows_whitelist_admins": ["curator_pubkey_hex"] } } } ``` --- ### Validation Fields (Write-Only) These fields validate incoming events and are **completely ignored for read operations**. #### `size_limit` **Type**: `int64` (bytes) **Applies to**: Write only Maximum total serialized event size. ```json { "global": { "size_limit": 100000 } } ``` #### `content_limit` **Type**: `int64` (bytes) **Applies to**: Write only Maximum size of the `content` field. ```json { "rules": { "1": { "content_limit": 10000 } } } ``` #### `max_age_of_event` **Type**: `int64` (seconds) **Applies to**: Write only Maximum age of events. Events with `created_at` older than `now - max_age_of_event` are rejected. ```json { "global": { "max_age_of_event": 86400 } } ``` #### `max_age_event_in_future` **Type**: `int64` (seconds) **Applies to**: Write only Maximum time events can be dated in the future. Events with `created_at` later than `now + max_age_event_in_future` are rejected. ```json { "global": { "max_age_event_in_future": 300 } } ``` #### `max_expiry_duration` **Type**: `string` (ISO-8601 duration) **Applies to**: Write only Maximum allowed expiry time from event creation. Events **must** have an `expiration` tag when this is set. **Format**: `P[n]Y[n]M[n]W[n]DT[n]H[n]M[n]S` **Examples**: - `P7D` = 7 days - `PT1H` = 1 hour - `P1DT12H` = 1 day 12 hours - `PT30M` = 30 minutes ```json { "rules": { "20": { "description": "Ephemeral events must expire within 24 hours", "max_expiry_duration": "P1D" } } } ``` #### `must_have_tags` **Type**: `[]string` (tag names) **Applies to**: Write only Required tags that must be present on the event. ```json { "rules": { "1": { "must_have_tags": ["p", "e"] } } } ``` #### `protected_required` **Type**: `boolean` **Default**: `false` **Applies to**: Write only Requires events to have a `-` tag (NIP-70 protected events). ```json { "rules": { "4": { "protected_required": true } } } ``` #### `identifier_regex` **Type**: `string` (regex pattern) **Applies to**: Write only Regex pattern that `d` tag values must match. Events **must** have a `d` tag when this is set. ```json { "rules": { "30023": { "identifier_regex": "^[a-z0-9-]{1,64}$" } } } ``` #### `tag_validation` **Type**: `map[string]string` (tag name → regex pattern) **Applies to**: Write only Regex patterns for validating specific tag values. Only validates tags that are **present** on the event. > **Note**: To require a tag to exist, use `must_have_tags`. `tag_validation` only validates format. ```json { "rules": { "30023": { "tag_validation": { "t": "^[a-z0-9-]{1,32}$", "d": "^[a-z0-9-]+$" } } } } ``` --- ### Script Configuration #### `script` **Type**: `string` (file path) **Applies to**: Write only Path to a custom validation script. **Scripts are NOT executed for read operations.** ```json { "rules": { "1": { "script": "/etc/orly/scripts/spam-filter.py" } } } ``` --- ## Policy Evaluation Order ### For Write Operations ``` 1. Global Rule Check (all fields apply) ├─ Universal constraints (size, tags, age, etc.) ├─ write_deny check ├─ write_allow_follows / follows_whitelist_admins check └─ write_allow check 2. Kind Filtering (whitelist/blacklist) 3. Kind-Specific Rule Check (same as global) ├─ Universal constraints ├─ write_deny check ├─ write_allow_follows / follows_whitelist_admins check ├─ write_allow check └─ Script execution (if configured) 4. Default Policy (if no rules matched) ``` ### For Read Operations ``` 1. Global Rule Check (access control only) ├─ read_deny check ├─ write_allow_follows / follows_whitelist_admins check ├─ read_allow check └─ privileged check (party involved) 2. Kind Filtering (whitelist/blacklist) 3. Kind-Specific Rule Check (access control only) ├─ read_deny check ├─ write_allow_follows / follows_whitelist_admins check ├─ read_allow + privileged (OR logic) └─ privileged-only check 4. Default Policy (if no rules matched) NOTE: Scripts are NOT executed for read operations ``` --- ## Common Configuration Patterns ### Private Relay (Whitelist Only) ```json { "default_policy": "deny", "global": { "write_allow": ["trusted_pubkey_1", "trusted_pubkey_2"], "read_allow": ["trusted_pubkey_1", "trusted_pubkey_2"] } } ``` ### Open Relay with Spam Protection ```json { "default_policy": "allow", "global": { "size_limit": 100000, "max_age_of_event": 86400, "max_age_event_in_future": 300 }, "rules": { "1": { "script": "/etc/orly/scripts/spam-filter.sh" } } } ``` ### Community Relay (Follows-Based) ```json { "default_policy": "deny", "policy_admins": ["community_admin_pubkey"], "policy_follow_whitelist_enabled": true, "global": { "write_allow_follows": true } } ``` ### Encrypted DMs (Privileged Access) ```json { "rules": { "4": { "description": "Encrypted DMs - only sender/recipient", "privileged": true, "protected_required": true } } } ``` ### Long-Form Content with Validation ```json { "rules": { "30023": { "description": "Long-form articles", "size_limit": 100000, "content_limit": 50000, "max_expiry_duration": "P30D", "identifier_regex": "^[a-z0-9-]{1,64}$", "tag_validation": { "t": "^[a-z0-9-]{1,32}$" } } } } ``` --- ## Important Behaviors ### Whitelist vs Blacklist Precedence 1. **Deny lists** (`write_deny`, `read_deny`) are checked **first** and have highest priority 2. **Allow lists** are exclusive when populated - ONLY listed pubkeys are allowed 3. **Deny-only configuration**: If only deny list exists (no allow list), all non-denied pubkeys are allowed ### Empty Arrays vs Null - `[]` (empty array explicitly set) = Allow all - `null` or field omitted = No list configured, use other rules ### Global Rules Are Additive Global rules are always evaluated **in addition to** kind-specific rules. They cannot be overridden at the kind level. ### Implicit Kind Whitelist When rules are defined but no explicit `kind.whitelist`: - If `default_policy: "allow"`: All kinds allowed - If `default_policy: "deny"` or unset: Only kinds with rules allowed --- ## Debugging Policy Issues Enable debug logging to see policy decisions: ```bash export ORLY_LOG_LEVEL=debug ``` Log messages include: - Policy evaluation steps - Rule matching - Access decisions with reasons --- ## Source Code Reference - Policy struct definition: `pkg/policy/policy.go:75-144` (Rule struct) - Policy struct definition: `pkg/policy/policy.go:380-412` (P struct) - Check evaluation: `pkg/policy/policy.go:1260-1595` (checkRulePolicy) - Write handler: `app/handle-event.go:114-138` - Read handler: `app/handle-req.go:420-438`