Introduce a comprehensive reference guide for ORLY policy configuration. This document outlines policy options, validation rules, access control, and debugging methods, serving as the authoritative resource for policy-related behavior.
14 KiB
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, andprivileged - 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
{
"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.
{
"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_policyand whether rules exist
{
"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.
{
"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.
{
"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.
{
"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
{
"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
ptags
Interaction with read_allow:
read_allowpresent +privileged: true= OR logic (in list OR party involved)read_allowempty +privileged: true= Only parties involvedprivileged: truealone = Only parties involved
{
"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.
{
"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.
{
"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.
{
"global": {
"size_limit": 100000
}
}
content_limit
Type: int64 (bytes)
Applies to: Write only
Maximum size of the content field.
{
"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.
{
"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.
{
"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 daysPT1H= 1 hourP1DT12H= 1 day 12 hoursPT30M= 30 minutes
{
"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.
{
"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).
{
"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.
{
"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_validationonly validates format.
{
"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.
{
"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)
{
"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
{
"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)
{
"default_policy": "deny",
"policy_admins": ["community_admin_pubkey"],
"policy_follow_whitelist_enabled": true,
"global": {
"write_allow_follows": true
}
}
Encrypted DMs (Privileged Access)
{
"rules": {
"4": {
"description": "Encrypted DMs - only sender/recipient",
"privileged": true,
"protected_required": true
}
}
}
Long-Form Content with Validation
{
"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
- Deny lists (
write_deny,read_deny) are checked first and have highest priority - Allow lists are exclusive when populated - ONLY listed pubkeys are allowed
- 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 allnullor 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:
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