This guide helps you configure and troubleshoot the ORLY relay policy system based on the requirements from Issue #5.
The policy system must support:
All requirements are implemented and tested (see pkg/policy/comprehensive_test.go).
The policy system evaluates rules in a specific order. Understanding this order is crucial for correct configuration:
write_allow or read_allow is specified, ONLY those users can access. Others are denied regardless of default policy.privileged: true AND allow lists are specified, the allow list is authoritative - even parties involved must be in the allow list.privileged: true but no allow lists, parties involved get automatic access.Set the environment variable:
export ORLY_POLICY_ENABLED=true
Or add to your service file:
Environment="ORLY_POLICY_ENABLED=true"
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
Create ~/.config/ORLY/policy.json with your desired rules. See examples below.
sudo systemctl restart orly
Check the logs:
sudo journalctl -u orly -f | grep -i policy
You should see:
loaded policy configuration from /home/user/.config/ORLY/policy.json
Only accept kinds 1, 3, 4, and 7:
{
"kind": {
"whitelist": [1, 3, 4, 7]
},
"default_policy": "allow"
}
How it works:
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:
write_allow can publish kind 10 eventswrite_allowOnly 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:
read_allow can see kind 20 events in REQ responsesOnly 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:
1. The author of the event (ev.pubkey == user.pubkey), OR
2. Mentioned in a p tag (["p", "user_pubkey_hex"])
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:
chmod +x script.sh){"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
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"
}
Symptoms:
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:
ORLY_POLICY_ENABLED=true is setjq . < ~/.config/ORLY/policy.json)Symptoms:
read_allow for a kindSolution:
- Set ORLY_AUTH_REQUIRED=true to force authentication
- Or use ACL mode: ORLY_ACL_MODE=managed or ORLY_ACL_MODE=follows
`bash
cat ~/.config/ORLY/policy.json | jq '.rules["YOURKIND"].readallow'
`
`bash
sudo journalctl -u orly -f | grep -E "(policy|CheckPolicy|read)"
`
Example to convert npub to hex:
# Using nak (nostr army knife)
nak decode npub1...
# Or use your client's developer tools
Symptoms:
"whitelist": [1,3,4]Possible Causes:
`bash
# Check environment variable
systemctl show orly | grep ORLYPOLICYENABLED
`
- Check file path: ~/.config/ORLY/policy.json
- Check file permissions: chmod 644 ~/.config/ORLY/policy.json
- Check JSON syntax: jq . < ~/.config/ORLY/policy.json
- If default_policy is not set correctly
- Kind whitelist is checked BEFORE default policy
Symptoms:
"privileged": true for a kindSolution:
`bash
# Force authentication
export ORLYAUTHREQUIRED=true
`
- The author (ev.pubkey), OR
- In a p-tag: ["p", "user_pubkey_hex"]
`json
{
"rules": {
"4": {
"privileged": true
}
}
}
`
`bash
sudo journalctl -u orly -f | grep -E "(privileged|IsPartyInvolved)"
`
Symptoms:
Solution:
`bash
ls -l ~/.config/ORLY/policy.sh
chmod +x ~/.config/ORLY/policy.sh
`
`bash
echo $ORLYPOLICYENABLED # Must be "true"
`
`bash
echo '{"id":"test","pubkey":"abc","created_at":1234567890,"kind":1,"content":"test","tags":[],"sig":"def"}' | ~/.config/ORLY/policy.sh
`
`json
{"id":"event_id","action":"accept","msg":"ok"}
`
`bash
sudo journalctl -u orly -f | grep -E "(policy script|script)"
`
# 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)
# 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)
# 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)
# 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
The policy system evaluates in this order:
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)
# Using nak
nak decode npub1abc...
# Using Python
python3 -c "from nostr_sdk import PublicKey; print(PublicKey.from_bech32('npub1abc...').to_hex())"
# 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())"
Policy and ACL work together:
# Enable managed ACL + Policy
export ORLY_ACL_MODE=managed
export ORLY_POLICY_ENABLED=true
export ORLY_AUTH_REQUIRED=true
Policy filtering happens BEFORE cache, so cached results respect policy:
export ORLY_QUERY_CACHE_SIZE_MB=512
export ORLY_QUERY_CACHE_MAX_AGE=5m
export ORLY_LOG_LEVEL=debug
sudo systemctl restart orly
sudo journalctl -u orly -f
Use the comprehensive test:
cd /home/mleku/src/next.orly.dev
CGO_ENABLED=0 go test -v ./pkg/policy -run TestPolicyDefinitionOfDone
Look for these log messages:
✅ "loaded policy configuration from ..."
✅ "policy script started: ..."
❌ "failed to load policy configuration: ..."
❌ "policy script does not exist at ..."
If you're still experiencing issues:
sudo journalctl -u orly -f | grep -i policycat ~/.config/ORLY/policy.json | jq .go test -v ./pkg/policy✅ 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:
ORLY_POLICY_ENABLED=true)~/.config/ORLY/policy.json)