Files
next.orly.dev/docs/POLICY_TROUBLESHOOTING.md

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:

  1. Configure relay to accept only certain kind events
  2. Scenario A: Only certain users should be allowed to write events
  3. Scenario B: Only certain users should be allowed to read events
  4. Scenario C: Only users involved in events should be able to read the events (privileged)
  5. 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:

  1. Global Rules (age, size) - Universal constraints applied first
  2. Kind Whitelist/Blacklist - Absolute gatekeepers for event types
  3. Script Execution (if configured and enabled)
  4. Rule-based Filtering (see detailed order below)

Rule-based Filtering Order (within checkRulePolicy):

  1. Universal Constraints - Size limits, required tags, timestamps
  2. Explicit Denials (deny lists) - Highest priority blacklist
  3. Privileged Access Check - Parties involved override allow lists
  4. Exclusive Allow Lists - ONLY listed users get access
  5. Privileged Final Check - Non-involved users denied for privileged events
  6. Default Behavior - Fallback when no specific rules apply

Key Concepts:

  • Allow lists are EXCLUSIVE: When write_allow or read_allow is 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: true AND allow lists are specified, the allow list is authoritative - even parties involved must be in the allow list.
  • Privileged without allow lists: If privileged: true but no allow lists, parties involved get automatic access.
  • Default policy rarely applies: Only used when no specific rules exist for a kind.

Common Misunderstandings:

  1. "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.

  2. "Default policy should apply when not in allow list" - NO! When an allow list exists, it completely overrides default policy for that kind.

  3. "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_allow can 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_allow can 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:
    1. The author of the event (ev.pubkey == user.pubkey), OR
    2. Mentioned in a p tag (["p", "user_pubkey_hex"])
  • 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:

  1. Must be executable (chmod +x script.sh)
  2. Reads JSONL (one event per line) from stdin
  3. Writes JSONL responses to stdout
  4. 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:

  1. Verify ORLY_POLICY_ENABLED=true is set
  2. Verify config file is in correct location
  3. Verify JSON is valid (use jq . < ~/.config/ORLY/policy.json)
  4. Restart the relay

Issue 2: Read Restrictions Not Enforced

Symptoms:

  • You configured read_allow for a kind
  • Unauthorized users can still see those events

Solution:

  1. Check authentication: Users MUST be authenticated via NIP-42 AUTH

    • Set ORLY_AUTH_REQUIRED=true to force authentication
    • Or use ACL mode: ORLY_ACL_MODE=managed or ORLY_ACL_MODE=follows
  2. Check policy configuration:

    cat ~/.config/ORLY/policy.json | jq '.rules["YOUR_KIND"].read_allow'
    
  3. Check relay logs when a REQ is made:

    sudo journalctl -u orly -f | grep -E "(policy|CheckPolicy|read)"
    
  4. 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:

  1. Policy not enabled

    # Check environment variable
    systemctl show orly | grep ORLY_POLICY_ENABLED
    
  2. 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
  3. Default policy overriding

    • If default_policy is not set correctly
    • Kind whitelist is checked BEFORE default policy

Issue 4: Privileged Events Visible to Everyone

Symptoms:

  • You set "privileged": true for a kind
  • Users can see events they're not involved in

Solution:

  1. Check authentication: Users MUST authenticate via NIP-42

    # Force authentication
    export ORLY_AUTH_REQUIRED=true
    
  2. 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"]
  3. Verify policy configuration:

    {
      "rules": {
        "4": {
          "privileged": true
        }
      }
    }
    
  4. 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:

  1. Check script exists and is executable:

    ls -l ~/.config/ORLY/policy.sh
    chmod +x ~/.config/ORLY/policy.sh
    
  2. Check policy manager is enabled:

    echo $ORLY_POLICY_ENABLED  # Must be "true"
    
  3. Test script manually:

    echo '{"id":"test","pubkey":"abc","created_at":1234567890,"kind":1,"content":"test","tags":[],"sig":"def"}' | ~/.config/ORLY/policy.sh
    
  4. Check script output format: Must output JSONL:

    {"id":"event_id","action":"accept","msg":"ok"}
    
  5. 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:

  1. Global Rules - Applied to ALL events first
  2. Kind Whitelist/Blacklist - Checked before specific rules
  3. Specific Kind Rules - Rule for the event's kind
  4. Script Validation (if configured) - Custom script logic
  5. 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:

  1. Check logs: sudo journalctl -u orly -f | grep -i policy
  2. Verify configuration: cat ~/.config/ORLY/policy.json | jq .
  3. Run tests: go test -v ./pkg/policy
  4. 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