Some checks failed
Go / build-and-release (push) Has been cancelled
- Add README.md table of contents for easier navigation - Add Curation ACL documentation section to README.md - Create detailed Curation Mode Guide (docs/CURATION_MODE_GUIDE.md) - Fix OOM during BBolt index building by closing temp file before build - Add GC calls before index building to reclaim batch buffer memory - Improve import-export.go with processJSONLEventsReturningCount - Add policy-aware import path for sync operations Files modified: - README.md: Added TOC and curation ACL documentation - docs/CURATION_MODE_GUIDE.md: New comprehensive curation mode guide - pkg/bbolt/import-export.go: Memory-safe import with deferred cleanup - pkg/bbolt/import-minimal.go: Added GC before index build - pkg/version/version: Bump to v0.48.8 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
412 lines
12 KiB
Markdown
412 lines
12 KiB
Markdown
# Curation Mode Guide
|
|
|
|
Curation mode is a sophisticated access control system for Nostr relays that provides three-tier publisher classification, rate limiting, IP-based flood protection, and event kind whitelisting.
|
|
|
|
## Overview
|
|
|
|
Unlike simple allow/deny lists, curation mode classifies publishers into three tiers:
|
|
|
|
| Tier | Rate Limited | Daily Limit | Visibility |
|
|
|------|--------------|-------------|------------|
|
|
| **Trusted** | No | Unlimited | Full |
|
|
| **Blacklisted** | N/A (blocked) | 0 | Hidden from regular users |
|
|
| **Unclassified** | Yes | 50 events/day (default) | Full |
|
|
|
|
This allows relay operators to:
|
|
- Reward quality contributors with unlimited publishing
|
|
- Block bad actors while preserving their events for admin review
|
|
- Allow new users to participate with reasonable rate limits
|
|
- Prevent spam floods through automatic IP-based protections
|
|
|
|
## Quick Start
|
|
|
|
### 1. Start the Relay
|
|
|
|
```bash
|
|
export ORLY_ACL_MODE=curating
|
|
export ORLY_OWNERS=npub1your_owner_pubkey
|
|
./orly
|
|
```
|
|
|
|
### 2. Publish Configuration
|
|
|
|
The relay will not accept events until you publish a configuration event. Use the web UI at `http://your-relay/#curation` or publish a kind 30078 event:
|
|
|
|
```json
|
|
{
|
|
"kind": 30078,
|
|
"tags": [["d", "curating-config"]],
|
|
"content": "{\"dailyLimit\":50,\"ipDailyLimit\":500,\"firstBanHours\":1,\"secondBanHours\":168,\"kindCategories\":[\"social\"]}"
|
|
}
|
|
```
|
|
|
|
### 3. Manage Publishers
|
|
|
|
Use the web UI or NIP-86 API to:
|
|
- Trust quality publishers
|
|
- Blacklist spammers
|
|
- Review unclassified users by activity
|
|
- Unblock IPs if needed
|
|
|
|
## Configuration
|
|
|
|
### Environment Variables
|
|
|
|
| Variable | Default | Description |
|
|
|----------|---------|-------------|
|
|
| `ORLY_ACL_MODE` | `none` | Set to `curating` to enable |
|
|
| `ORLY_OWNERS` | | Owner pubkeys (can configure relay) |
|
|
| `ORLY_ADMINS` | | Admin pubkeys (can manage publishers) |
|
|
|
|
### Configuration Event (Kind 30078)
|
|
|
|
Configuration is stored as a replaceable Nostr event (kind 30078) with d-tag `curating-config`. Only owners and admins can publish configuration.
|
|
|
|
```typescript
|
|
interface CuratingConfig {
|
|
// Rate Limiting
|
|
dailyLimit: number; // Max events/day for unclassified users (default: 50)
|
|
ipDailyLimit: number; // Max events/day from single IP (default: 500)
|
|
|
|
// IP Ban Durations
|
|
firstBanHours: number; // First offense ban duration (default: 1 hour)
|
|
secondBanHours: number; // Subsequent offense ban duration (default: 168 hours / 1 week)
|
|
|
|
// Kind Filtering (choose one or combine)
|
|
allowedKinds: number[]; // Explicit kind numbers: [0, 1, 3, 7]
|
|
allowedRanges: string[]; // Kind ranges: ["1000-1999", "30000-39999"]
|
|
kindCategories: string[]; // Pre-defined categories: ["social", "dm"]
|
|
}
|
|
```
|
|
|
|
### Event Kind Categories
|
|
|
|
Pre-defined categories for convenient kind whitelisting:
|
|
|
|
| Category | Kinds | Description |
|
|
|----------|-------|-------------|
|
|
| `social` | 0, 1, 3, 6, 7, 10002 | Profiles, notes, contacts, reposts, reactions |
|
|
| `dm` | 4, 14, 1059 | Direct messages (NIP-04, NIP-17, gift wraps) |
|
|
| `longform` | 30023, 30024 | Long-form articles and drafts |
|
|
| `media` | 1063, 20, 21, 22 | File metadata, picture/video/audio events |
|
|
| `marketplace` | 30017-30020, 1021, 1022 | Products, stalls, auctions, bids |
|
|
| `groups_nip29` | 9-12, 9000-9002, 39000-39002 | NIP-29 relay-based groups |
|
|
| `groups_nip72` | 34550, 1111, 4550 | NIP-72 moderated communities |
|
|
| `lists` | 10000, 10001, 10003, 30000, 30001, 30003 | Mute, pin, bookmark lists |
|
|
|
|
Example configuration allowing social interactions and DMs:
|
|
|
|
```json
|
|
{
|
|
"kindCategories": ["social", "dm"],
|
|
"dailyLimit": 100,
|
|
"ipDailyLimit": 1000
|
|
}
|
|
```
|
|
|
|
## Three-Tier Classification
|
|
|
|
### Trusted Publishers
|
|
|
|
Trusted publishers have unlimited publishing rights:
|
|
- Bypass all rate limiting
|
|
- Can publish any allowed kind
|
|
- Events always visible to all users
|
|
|
|
**Use case**: Known quality contributors, verified community members, partner relays.
|
|
|
|
### Blacklisted Publishers
|
|
|
|
Blacklisted publishers are blocked from publishing:
|
|
- All events rejected with `"pubkey is blacklisted"` error
|
|
- Existing events become invisible to regular users
|
|
- Admins and owners can still see blacklisted events (for review)
|
|
|
|
**Use case**: Spammers, abusive users, bad actors.
|
|
|
|
### Unclassified Publishers
|
|
|
|
Everyone else falls into the unclassified tier:
|
|
- Subject to daily event limit (default: 50 events/day)
|
|
- Subject to IP-based flood protection
|
|
- Events visible to all users
|
|
- Can be promoted to trusted or demoted to blacklisted
|
|
|
|
**Use case**: New users, general public.
|
|
|
|
## Rate Limiting & Flood Protection
|
|
|
|
### Per-Pubkey Limits
|
|
|
|
Unclassified publishers are limited to a configurable number of events per day (default: 50). The count resets at midnight UTC.
|
|
|
|
When a user exceeds their limit:
|
|
1. Event is rejected with `"daily event limit exceeded"` error
|
|
2. Their IP is flagged for potential abuse
|
|
|
|
### Per-IP Limits
|
|
|
|
To prevent Sybil attacks (creating many pubkeys from one IP), there's also an IP-based daily limit (default: 500 events).
|
|
|
|
When an IP exceeds its limit:
|
|
1. All events from that IP are rejected
|
|
2. The IP is temporarily banned
|
|
|
|
### Automatic IP Banning
|
|
|
|
When rate limits are exceeded:
|
|
|
|
| Offense | Ban Duration | Description |
|
|
|---------|--------------|-------------|
|
|
| First | 1 hour | Quick timeout for accidental over-posting |
|
|
| Second+ | 1 week | Extended ban for repeated abuse |
|
|
|
|
Ban durations are configurable via `firstBanHours` and `secondBanHours`.
|
|
|
|
### Offense Tracking
|
|
|
|
The system tracks which pubkeys triggered rate limits from each IP:
|
|
|
|
```
|
|
IP 192.168.1.100:
|
|
- npub1abc... exceeded limit at 2024-01-15 10:30:00
|
|
- npub1xyz... exceeded limit at 2024-01-15 10:45:00
|
|
Offense count: 2
|
|
Status: Banned until 2024-01-22 10:45:00
|
|
```
|
|
|
|
This helps identify coordinated spam attacks.
|
|
|
|
## Spam Flagging
|
|
|
|
Events can be flagged as spam without deletion:
|
|
|
|
- Flagged events are hidden from regular users
|
|
- Admins can review flagged events
|
|
- Events can be unflagged if incorrectly marked
|
|
- Original event data is preserved
|
|
|
|
This is useful for:
|
|
- Moderation review queues
|
|
- Training spam detection systems
|
|
- Preserving evidence of abuse
|
|
|
|
## NIP-86 Management API
|
|
|
|
All management operations use NIP-98 HTTP authentication.
|
|
|
|
### Trust Management
|
|
|
|
```bash
|
|
# Trust a pubkey
|
|
curl -X POST https://relay.example.com \
|
|
-H "Authorization: Nostr <nip98_token>" \
|
|
-d '{"method":"trustpubkey","params":["<pubkey_hex>"]}'
|
|
|
|
# Untrust a pubkey
|
|
curl -X POST https://relay.example.com \
|
|
-H "Authorization: Nostr <nip98_token>" \
|
|
-d '{"method":"untrustpubkey","params":["<pubkey_hex>"]}'
|
|
|
|
# List trusted pubkeys
|
|
curl -X POST https://relay.example.com \
|
|
-H "Authorization: Nostr <nip98_token>" \
|
|
-d '{"method":"listtrustedpubkeys","params":[]}'
|
|
```
|
|
|
|
### Blacklist Management
|
|
|
|
```bash
|
|
# Blacklist a pubkey
|
|
curl -X POST https://relay.example.com \
|
|
-H "Authorization: Nostr <nip98_token>" \
|
|
-d '{"method":"blacklistpubkey","params":["<pubkey_hex>"]}'
|
|
|
|
# Remove from blacklist
|
|
curl -X POST https://relay.example.com \
|
|
-H "Authorization: Nostr <nip98_token>" \
|
|
-d '{"method":"unblacklistpubkey","params":["<pubkey_hex>"]}'
|
|
|
|
# List blacklisted pubkeys
|
|
curl -X POST https://relay.example.com \
|
|
-H "Authorization: Nostr <nip98_token>" \
|
|
-d '{"method":"listblacklistedpubkeys","params":[]}'
|
|
```
|
|
|
|
### Unclassified User Management
|
|
|
|
```bash
|
|
# List unclassified users sorted by event count
|
|
curl -X POST https://relay.example.com \
|
|
-H "Authorization: Nostr <nip98_token>" \
|
|
-d '{"method":"listunclassifiedusers","params":[]}'
|
|
```
|
|
|
|
Response includes pubkey, event count, and last activity for each user.
|
|
|
|
### Spam Management
|
|
|
|
```bash
|
|
# Mark event as spam
|
|
curl -X POST https://relay.example.com \
|
|
-H "Authorization: Nostr <nip98_token>" \
|
|
-d '{"method":"markspam","params":["<event_id_hex>"]}'
|
|
|
|
# Unmark spam
|
|
curl -X POST https://relay.example.com \
|
|
-H "Authorization: Nostr <nip98_token>" \
|
|
-d '{"method":"unmarkspam","params":["<event_id_hex>"]}'
|
|
|
|
# List spam events
|
|
curl -X POST https://relay.example.com \
|
|
-H "Authorization: Nostr <nip98_token>" \
|
|
-d '{"method":"listspamevents","params":[]}'
|
|
```
|
|
|
|
### IP Block Management
|
|
|
|
```bash
|
|
# List blocked IPs
|
|
curl -X POST https://relay.example.com \
|
|
-H "Authorization: Nostr <nip98_token>" \
|
|
-d '{"method":"listblockedips","params":[]}'
|
|
|
|
# Unblock an IP
|
|
curl -X POST https://relay.example.com \
|
|
-H "Authorization: Nostr <nip98_token>" \
|
|
-d '{"method":"unblockip","params":["<ip_address>"]}'
|
|
```
|
|
|
|
### Configuration Management
|
|
|
|
```bash
|
|
# Get current configuration
|
|
curl -X POST https://relay.example.com \
|
|
-H "Authorization: Nostr <nip98_token>" \
|
|
-d '{"method":"getcuratingconfig","params":[]}'
|
|
|
|
# Set allowed kind categories
|
|
curl -X POST https://relay.example.com \
|
|
-H "Authorization: Nostr <nip98_token>" \
|
|
-d '{"method":"setallowedkindcategories","params":[["social","dm","longform"]]}'
|
|
|
|
# Get allowed kind categories
|
|
curl -X POST https://relay.example.com \
|
|
-H "Authorization: Nostr <nip98_token>" \
|
|
-d '{"method":"getallowedkindcategories","params":[]}'
|
|
```
|
|
|
|
## Web UI
|
|
|
|
The curation web UI is available at `/#curation` and provides:
|
|
|
|
- **Configuration Panel**: Set rate limits, ban durations, and allowed kinds
|
|
- **Publisher Management**: Trust/blacklist pubkeys with one click
|
|
- **Unclassified Users**: View users sorted by activity, promote or blacklist
|
|
- **IP Blocks**: View and unblock banned IP addresses
|
|
- **Spam Queue**: Review flagged events, confirm or unflag
|
|
|
|
## Database Storage
|
|
|
|
Curation data is stored in the relay database with the following key prefixes:
|
|
|
|
| Prefix | Purpose |
|
|
|--------|---------|
|
|
| `CURATING_ACL_CONFIG` | Current configuration |
|
|
| `CURATING_ACL_TRUSTED_PUBKEY_{pubkey}` | Trusted publisher list |
|
|
| `CURATING_ACL_BLACKLISTED_PUBKEY_{pubkey}` | Blacklisted publisher list |
|
|
| `CURATING_ACL_EVENT_COUNT_{pubkey}_{date}` | Daily event counts per pubkey |
|
|
| `CURATING_ACL_IP_EVENT_COUNT_{ip}_{date}` | Daily event counts per IP |
|
|
| `CURATING_ACL_IP_OFFENSE_{ip}` | Offense tracking per IP |
|
|
| `CURATING_ACL_BLOCKED_IP_{ip}` | Active IP blocks |
|
|
| `CURATING_ACL_SPAM_EVENT_{eventID}` | Spam-flagged events |
|
|
|
|
## Caching
|
|
|
|
For performance, the following data is cached in memory:
|
|
|
|
- Trusted pubkey set
|
|
- Blacklisted pubkey set
|
|
- Allowed kinds set
|
|
- Current configuration
|
|
|
|
Caches are refreshed every hour by the background cleanup goroutine.
|
|
|
|
## Background Maintenance
|
|
|
|
A background goroutine runs hourly to:
|
|
|
|
1. Remove expired IP blocks
|
|
2. Clean up old event count entries (older than 2 days)
|
|
3. Refresh in-memory caches
|
|
4. Log maintenance statistics
|
|
|
|
## Best Practices
|
|
|
|
### Starting a New Curated Relay
|
|
|
|
1. Start with permissive settings:
|
|
```json
|
|
{"dailyLimit": 100, "ipDailyLimit": 1000, "kindCategories": ["social"]}
|
|
```
|
|
|
|
2. Monitor unclassified users for a few days
|
|
|
|
3. Trust active, quality contributors
|
|
|
|
4. Blacklist obvious spammers
|
|
|
|
5. Adjust rate limits based on observed patterns
|
|
|
|
### Handling Spam Waves
|
|
|
|
During spam attacks:
|
|
|
|
1. The IP-based flood protection will auto-ban attack sources
|
|
2. Review blocked IPs via web UI or API
|
|
3. Blacklist any pubkeys that got through
|
|
4. Consider temporarily lowering `ipDailyLimit`
|
|
|
|
### Recovering from Mistakes
|
|
|
|
- **Accidentally blacklisted someone**: Use `unblacklistpubkey` - their events become visible again
|
|
- **Wrongly flagged spam**: Use `unmarkspam` - event becomes visible again
|
|
- **Blocked legitimate IP**: Use `unblockip` - IP can publish again immediately
|
|
|
|
## Comparison with Other ACL Modes
|
|
|
|
| Feature | None | Follows | Managed | Curating |
|
|
|---------|------|---------|---------|----------|
|
|
| Default Access | Write | Write if followed | Explicit allow | Rate-limited |
|
|
| Rate Limiting | No | No | No | Yes |
|
|
| Kind Filtering | No | No | Optional | Yes |
|
|
| IP Protection | No | No | No | Yes |
|
|
| Spam Flagging | No | No | No | Yes |
|
|
| Configuration | Env vars | Follow lists | NIP-86 | Kind 30078 events |
|
|
| Web UI | Basic | Basic | Basic | Full curation panel |
|
|
|
|
## Troubleshooting
|
|
|
|
### "Relay not accepting events"
|
|
|
|
The relay requires a configuration event before accepting any events. Publish a kind 30078 event with d-tag `curating-config`.
|
|
|
|
### "daily event limit exceeded"
|
|
|
|
The user has exceeded their daily limit. Options:
|
|
1. Wait until midnight UTC for reset
|
|
2. Trust the pubkey if they're a quality contributor
|
|
3. Increase `dailyLimit` in configuration
|
|
|
|
### "pubkey is blacklisted"
|
|
|
|
The pubkey is on the blacklist. Use `unblacklistpubkey` if this was a mistake.
|
|
|
|
### "IP is blocked"
|
|
|
|
The IP has been auto-banned due to rate limit violations. Use `unblockip` if legitimate, or wait for the ban to expire.
|
|
|
|
### Events disappearing for users
|
|
|
|
Check if the event author has been blacklisted. Blacklisted authors' events are hidden from regular users but visible to admins.
|