15 KiB
Policy System Troubleshooting Guide
This guide helps you configure and troubleshoot the ORLY relay policy system based on the requirements from Issue #5.
Definition of Done Requirements
The policy system must support:
- Configure relay to accept only certain kind events ✅
- Scenario A: Only certain users should be allowed to write events ✅
- Scenario B: Only certain users should be allowed to read events ✅
- Scenario C: Only users involved in events should be able to read the events (privileged) ✅
- Scenario D: Scripting option for complex validation ✅
All requirements are implemented and tested (see pkg/policy/comprehensive_test.go).
Policy Evaluation Order (CRITICAL FOR CORRECT CONFIGURATION)
The policy system evaluates rules in a specific order. Understanding this order is crucial for correct configuration:
Overall Evaluation Flow:
- Global Rules (age, size) - Universal constraints applied first
- Kind Whitelist/Blacklist - Absolute gatekeepers for event types
- Script Execution (if configured and enabled)
- Rule-based Filtering (see detailed order below)
Rule-based Filtering Order (within checkRulePolicy):
- Universal Constraints - Size limits, required tags, timestamps
- Explicit Denials (deny lists) - Highest priority blacklist
- Privileged Access Check - Parties involved override allow lists
- Exclusive Allow Lists - ONLY listed users get access
- Privileged Final Check - Non-involved users denied for privileged events
- Default Behavior - Fallback when no specific rules apply
Key Concepts:
- Allow lists are EXCLUSIVE: When
write_alloworread_allowis specified, ONLY those users can access. Others are denied regardless of default policy. - Deny lists have highest priority: Users in deny lists are always denied, even if they're in allow lists or involved in privileged events.
- Allow lists override privileged access: When BOTH
privileged: trueAND allow lists are specified, the allow list is authoritative - even parties involved must be in the allow list. - Privileged without allow lists: If
privileged: truebut no allow lists, parties involved get automatic access. - Default policy rarely applies: Only used when no specific rules exist for a kind.
Common Misunderstandings:
-
"Allow lists should be inclusive" - NO! Allow lists are exclusive. If you want some users to have guaranteed access while others follow default policy, use privileged events or scripting.
-
"Default policy should apply when not in allow list" - NO! When an allow list exists, it completely overrides default policy for that kind.
-
"Privileged should be checked last" - NO! Privileged access is checked early to override allow lists for parties involved.
Quick Start
Step 1: Enable Policy System
Set the environment variable:
export ORLY_POLICY_ENABLED=true
Or add to your service file:
Environment="ORLY_POLICY_ENABLED=true"
Step 2: Create Policy Configuration File
The policy configuration file must be located at:
$HOME/.config/ORLY/policy.json
Or if using a custom app name:
$HOME/.config/<YOUR_APP_NAME>/policy.json
Step 3: Configure Your Policy
Create ~/.config/ORLY/policy.json with your desired rules. See examples below.
Step 4: Restart Relay
sudo systemctl restart orly
Step 5: Verify Policy is Loaded
Check the logs:
sudo journalctl -u orly -f | grep -i policy
You should see:
loaded policy configuration from /home/user/.config/ORLY/policy.json
Configuration Examples
Example 1: Kind Whitelist (Requirement 1)
Only accept kinds 1, 3, 4, and 7:
{
"kind": {
"whitelist": [1, 3, 4, 7]
},
"default_policy": "allow"
}
How it works:
- Events with kinds 1, 3, 4, or 7 are allowed
- Events with any other kind are automatically rejected
- This is checked BEFORE any rule-specific policies
Example 2: Per-Kind Write Access (Scenario A)
Only specific users can write kind 10 events:
{
"rules": {
"10": {
"description": "Only Alice can write kind 10",
"write_allow": ["ALICE_PUBKEY_HEX"]
}
},
"default_policy": "allow"
}
How it works:
- Only the pubkey in
write_allowcan publish kind 10 events - All other users are denied
- The pubkey in the event MUST match one in
write_allow
Example 3: Per-Kind Read Access (Scenario B)
Only specific users can read kind 20 events:
{
"rules": {
"20": {
"description": "Only Bob can read kind 20",
"read_allow": ["BOB_PUBKEY_HEX"]
}
},
"default_policy": "allow"
}
How it works:
- Only users authenticated as the pubkey in
read_allowcan see kind 20 events in REQ responses - Unauthenticated users cannot see these events
- Users authenticated as different pubkeys cannot see these events
Example 4: Privileged Events (Scenario C)
Only users involved in the event can read it:
{
"rules": {
"4": {
"description": "Encrypted DMs - only parties involved",
"privileged": true
},
"14": {
"description": "Direct Messages - only parties involved",
"privileged": true
}
},
"default_policy": "allow"
}
How it works:
- A user can read a privileged event ONLY if they are:
- The author of the event (
ev.pubkey == user.pubkey), OR - Mentioned in a
ptag (["p", "user_pubkey_hex"])
- The author of the event (
- Unauthenticated users cannot see privileged events
- Third parties cannot see privileged events
Example 5: Script-Based Validation (Scenario D)
Use a custom script for complex validation:
{
"rules": {
"30078": {
"description": "Custom validation via script",
"script": "/home/user/.config/ORLY/validate-30078.sh"
}
},
"default_policy": "allow"
}
Script Requirements:
- Must be executable (
chmod +x script.sh) - Reads JSONL (one event per line) from stdin
- Writes JSONL responses to stdout
- Each response must have:
{"id":"event_id","action":"accept|reject|shadowReject","msg":"reason"}
Example script:
#!/bin/bash
while IFS= read -r line; do
# Parse event JSON and apply custom logic
if echo "$line" | jq -e '.kind == 30078 and (.content | length) < 1000' > /dev/null; then
echo "{\"id\":\"$(echo "$line" | jq -r .id)\",\"action\":\"accept\",\"msg\":\"ok\"}"
else
echo "{\"id\":\"$(echo "$line" | jq -r .id)\",\"action\":\"reject\",\"msg\":\"content too long\"}"
fi
done
Example 6: Combined Policy
All features together:
{
"kind": {
"whitelist": [1, 3, 4, 10, 20, 30]
},
"rules": {
"10": {
"description": "Only Alice can write",
"write_allow": ["ALICE_PUBKEY_HEX"]
},
"20": {
"description": "Only Bob can read",
"read_allow": ["BOB_PUBKEY_HEX"]
},
"4": {
"description": "Encrypted DMs - privileged",
"privileged": true
},
"30": {
"description": "Custom validation",
"script": "/home/user/.config/ORLY/validate.sh",
"write_allow": ["ALICE_PUBKEY_HEX"]
}
},
"global": {
"description": "Global rules for all events",
"max_age_of_event": 31536000,
"max_age_event_in_future": 3600
},
"default_policy": "allow"
}
Common Issues and Solutions
Issue 1: Events Outside Whitelist Are Accepted
Symptoms:
- You configured a kind whitelist
- Events with kinds NOT in the whitelist are still accepted
Solution: Check that policy is enabled:
# Check if policy is enabled
echo $ORLY_POLICY_ENABLED
# Check if config file exists
ls -l ~/.config/ORLY/policy.json
# Check logs for policy loading
sudo journalctl -u orly | grep -i policy
If policy is not loading:
- Verify
ORLY_POLICY_ENABLED=trueis set - Verify config file is in correct location
- Verify JSON is valid (use
jq . < ~/.config/ORLY/policy.json) - Restart the relay
Issue 2: Read Restrictions Not Enforced
Symptoms:
- You configured
read_allowfor a kind - Unauthorized users can still see those events
Solution:
-
Check authentication: Users MUST be authenticated via NIP-42 AUTH
- Set
ORLY_AUTH_REQUIRED=trueto force authentication - Or use ACL mode:
ORLY_ACL_MODE=managedorORLY_ACL_MODE=follows
- Set
-
Check policy configuration:
cat ~/.config/ORLY/policy.json | jq '.rules["YOUR_KIND"].read_allow' -
Check relay logs when a REQ is made:
sudo journalctl -u orly -f | grep -E "(policy|CheckPolicy|read)" -
Verify pubkey format: Use hex (64 chars), not npub
Example to convert npub to hex:
# Using nak (nostr army knife)
nak decode npub1...
# Or use your client's developer tools
Issue 3: Kind Whitelist Not Working
Symptoms:
- You have
"whitelist": [1,3,4] - Events with kind 5 are still accepted
Possible Causes:
-
Policy not enabled
# Check environment variable systemctl show orly | grep ORLY_POLICY_ENABLED -
Config file not loaded
- Check file path:
~/.config/ORLY/policy.json - Check file permissions:
chmod 644 ~/.config/ORLY/policy.json - Check JSON syntax:
jq . < ~/.config/ORLY/policy.json
- Check file path:
-
Default policy overriding
- If
default_policyis not set correctly - Kind whitelist is checked BEFORE default policy
- If
Issue 4: Privileged Events Visible to Everyone
Symptoms:
- You set
"privileged": truefor a kind - Users can see events they're not involved in
Solution:
-
Check authentication: Users MUST authenticate via NIP-42
# Force authentication export ORLY_AUTH_REQUIRED=true -
Check event has p-tags: For users to be "involved", they must be:
- The author (
ev.pubkey), OR - In a p-tag:
["p", "user_pubkey_hex"]
- The author (
-
Verify policy configuration:
{ "rules": { "4": { "privileged": true } } } -
Check logs:
sudo journalctl -u orly -f | grep -E "(privileged|IsPartyInvolved)"
Issue 5: Script Not Running
Symptoms:
- You configured a script path
- Script is not being executed
Solution:
-
Check script exists and is executable:
ls -l ~/.config/ORLY/policy.sh chmod +x ~/.config/ORLY/policy.sh -
Check policy manager is enabled:
echo $ORLY_POLICY_ENABLED # Must be "true" -
Test script manually:
echo '{"id":"test","pubkey":"abc","created_at":1234567890,"kind":1,"content":"test","tags":[],"sig":"def"}' | ~/.config/ORLY/policy.sh -
Check script output format: Must output JSONL:
{"id":"event_id","action":"accept","msg":"ok"} -
Check relay logs:
sudo journalctl -u orly -f | grep -E "(policy script|script)"
Testing Your Policy Configuration
Test 1: Kind Whitelist
# 1. Configure whitelist for kinds 1,3
cat > ~/.config/ORLY/policy.json <<EOF
{
"kind": {
"whitelist": [1, 3]
},
"default_policy": "allow"
}
EOF
# 2. Restart relay
sudo systemctl restart orly
# 3. Try to publish kind 1 (should succeed)
# 4. Try to publish kind 5 (should fail)
Test 2: Write Access Control
# 1. Get your pubkey
YOUR_PUBKEY="$(nak key public)"
# 2. Configure write access
cat > ~/.config/ORLY/policy.json <<EOF
{
"rules": {
"10": {
"write_allow": ["$YOUR_PUBKEY"]
}
},
"default_policy": "allow"
}
EOF
# 3. Restart relay
sudo systemctl restart orly
# 4. Publish kind 10 with your key (should succeed)
# 5. Publish kind 10 with different key (should fail)
Test 3: Read Access Control
# 1. Configure read access
cat > ~/.config/ORLY/policy.json <<EOF
{
"rules": {
"20": {
"read_allow": ["$YOUR_PUBKEY"]
}
},
"default_policy": "allow"
}
EOF
# 2. Enable authentication
export ORLY_AUTH_REQUIRED=true
# 3. Restart relay
sudo systemctl restart orly
# 4. Authenticate with your key and query kind 20 (should see events)
# 5. Query without auth or with different key (should not see events)
Test 4: Privileged Events
# 1. Configure privileged
cat > ~/.config/ORLY/policy.json <<EOF
{
"rules": {
"4": {
"privileged": true
}
},
"default_policy": "allow"
}
EOF
# 2. Restart relay
sudo systemctl restart orly
# 3. Publish kind 4 with p-tag to Bob
# 4. Query as Bob (authenticated) - should see event
# 5. Query as Alice (authenticated) - should NOT see event
Policy Evaluation Order
The policy system evaluates in this order:
- Global Rules - Applied to ALL events first
- Kind Whitelist/Blacklist - Checked before specific rules
- Specific Kind Rules - Rule for the event's kind
- Script Validation (if configured) - Custom script logic
- Default Policy - Applied if no rule denies
Event Arrives
↓
Global Rules (max_age, size_limit, etc.)
↓ (if passes)
Kind Whitelist/Blacklist
↓ (if passes)
Specific Rule for Kind
├─ Script (if configured)
├─ write_allow/write_deny
├─ read_allow/read_deny
├─ privileged
└─ Other rule criteria
↓ (if no rule found or passes)
Default Policy (allow or deny)
Getting Your Pubkey in Hex Format
From npub:
# Using nak
nak decode npub1abc...
# Using Python
python3 -c "from nostr_sdk import PublicKey; print(PublicKey.from_bech32('npub1abc...').to_hex())"
From nsec:
# Using nak
nak key public nsec1abc...
# Using Python
python3 -c "from nostr_sdk import Keys; print(Keys.from_sk_str('nsec1abc...').public_key().to_hex())"
Additional Configuration
Combine with ACL System
Policy and ACL work together:
# Enable managed ACL + Policy
export ORLY_ACL_MODE=managed
export ORLY_POLICY_ENABLED=true
export ORLY_AUTH_REQUIRED=true
Query Cache with Policy
Policy filtering happens BEFORE cache, so cached results respect policy:
export ORLY_QUERY_CACHE_SIZE_MB=512
export ORLY_QUERY_CACHE_MAX_AGE=5m
Debugging Tips
Enable Debug Logging
export ORLY_LOG_LEVEL=debug
sudo systemctl restart orly
sudo journalctl -u orly -f
Test Policy in Isolation
Use the comprehensive test:
cd /home/mleku/src/next.orly.dev
CGO_ENABLED=0 go test -v ./pkg/policy -run TestPolicyDefinitionOfDone
Check Policy Manager Status
Look for these log messages:
✅ "loaded policy configuration from ..."
✅ "policy script started: ..."
❌ "failed to load policy configuration: ..."
❌ "policy script does not exist at ..."
Support
If you're still experiencing issues:
- Check logs:
sudo journalctl -u orly -f | grep -i policy - Verify configuration:
cat ~/.config/ORLY/policy.json | jq . - Run tests:
go test -v ./pkg/policy - File an issue: https://git.nostrdev.com/mleku/next.orly.dev/issues
Summary
✅ All requirements are implemented and working ✅ Comprehensive tests verify all scenarios ✅ Configuration examples provided ✅ Troubleshooting guide available
The policy system is fully functional. Most issues are due to:
- Policy not enabled (
ORLY_POLICY_ENABLED=true) - Config file in wrong location (
~/.config/ORLY/policy.json) - Authentication not required for read restrictions
- Invalid JSON syntax in config file