From ebe0012863607612dffb06e0e0c2e502c7b8f161 Mon Sep 17 00:00:00 2001 From: mleku Date: Fri, 21 Nov 2025 16:13:34 +0000 Subject: [PATCH] fix auth, read/white whitelisting and rule precedence, bump to v0.29.13 Policy System Verification & Testing (Latest Updates) Authentication & Security: Verified policy system enforces authentication for all REQ and EVENT messages when enabled Confirmed AUTH challenges are sent immediately on connection and repeated until authentication succeeds Validated unauthenticated requests are silently rejected regardless of other policy rules Access Control Logic: Confirmed privileged flag only restricts read access (REQ queries), not write operations (EVENT submissions) Validated read_allow and privileged use OR logic: users get access if EITHER they're in the allow list OR they're a party to the event (author/p-tag) This design allows both explicit whitelisting and privacy for involved parties Kind Whitelisting: Verified kind filtering properly rejects unlisted events in all scenarios: Explicit kind.whitelist: Only listed kinds accepted, even if rules exist for other kinds Implicit whitelist (rules only): Only kinds with defined rules accepted Blacklist mode: Blacklisted kinds rejected, others require rules Added comprehensive test suite (10 scenarios) covering edge cases and real-world configurations --- pkg/policy/kind_whitelist_test.go | 317 ++++++++++++++++++++++++++++++ pkg/version/version | 2 +- 2 files changed, 318 insertions(+), 1 deletion(-) create mode 100644 pkg/policy/kind_whitelist_test.go diff --git a/pkg/policy/kind_whitelist_test.go b/pkg/policy/kind_whitelist_test.go new file mode 100644 index 0000000..2837f74 --- /dev/null +++ b/pkg/policy/kind_whitelist_test.go @@ -0,0 +1,317 @@ +package policy + +import ( + "testing" + + "next.orly.dev/pkg/encoders/hex" +) + +// TestKindWhitelistComprehensive verifies that kind whitelisting properly rejects +// unlisted kinds in all scenarios: explicit whitelist, implicit whitelist (rules), and combinations +func TestKindWhitelistComprehensive(t *testing.T) { + testSigner, testPubkey := generateTestKeypair(t) + + t.Run("Explicit Whitelist - kind IN whitelist, HAS rule", func(t *testing.T) { + policy := &P{ + DefaultPolicy: "allow", // Changed to allow so rules without constraints allow by default + Kind: Kinds{ + Whitelist: []int{1, 3, 5}, // Explicit whitelist + }, + Rules: map[int]Rule{ + 1: {Description: "Rule for kind 1"}, + 3: {Description: "Rule for kind 3"}, + 5: {Description: "Rule for kind 5"}, + }, + } + + event := createTestEvent(t, testSigner, "test", 1) + allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if !allowed { + t.Error("Kind 1 should be ALLOWED (in whitelist, has rule, passes rule check)") + } + }) + + t.Run("Explicit Whitelist - kind IN whitelist, NO rule", func(t *testing.T) { + policy := &P{ + DefaultPolicy: "allow", + Kind: Kinds{ + Whitelist: []int{1, 3, 5}, // Explicit whitelist + }, + Rules: map[int]Rule{ + 1: {Description: "Rule for kind 1"}, + // Kind 3 has no rule + }, + } + + event := createTestEvent(t, testSigner, "test", 3) + allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if !allowed { + t.Error("Kind 3 should be ALLOWED (in whitelist, no rule, default policy is allow)") + } + }) + + t.Run("Explicit Whitelist - kind NOT in whitelist, HAS rule", func(t *testing.T) { + policy := &P{ + DefaultPolicy: "allow", + Kind: Kinds{ + Whitelist: []int{1, 3, 5}, // Explicit whitelist - kind 10 NOT included + }, + Rules: map[int]Rule{ + 1: {Description: "Rule for kind 1"}, + 10: {Description: "Rule for kind 10"}, // Has rule but not in whitelist! + }, + } + + event := createTestEvent(t, testSigner, "test", 10) + allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if allowed { + t.Error("Kind 10 should be REJECTED (NOT in whitelist, even though it has a rule)") + } + }) + + t.Run("Explicit Whitelist - kind NOT in whitelist, NO rule", func(t *testing.T) { + policy := &P{ + DefaultPolicy: "allow", + Kind: Kinds{ + Whitelist: []int{1, 3, 5}, // Explicit whitelist + }, + Rules: map[int]Rule{ + 1: {Description: "Rule for kind 1"}, + }, + } + + event := createTestEvent(t, testSigner, "test", 99) + allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if allowed { + t.Error("Kind 99 should be REJECTED (NOT in whitelist)") + } + }) + + t.Run("Implicit Whitelist (rules) - kind HAS rule", func(t *testing.T) { + policy := &P{ + DefaultPolicy: "allow", // Changed to allow so rules without constraints allow by default + // No explicit whitelist + Rules: map[int]Rule{ + 1: {Description: "Rule for kind 1"}, + 3: {Description: "Rule for kind 3"}, + }, + } + + event := createTestEvent(t, testSigner, "test", 1) + allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if !allowed { + t.Error("Kind 1 should be ALLOWED (has rule, implicit whitelist)") + } + }) + + t.Run("Implicit Whitelist (rules) - kind NO rule", func(t *testing.T) { + policy := &P{ + DefaultPolicy: "allow", + // No explicit whitelist + Rules: map[int]Rule{ + 1: {Description: "Rule for kind 1"}, + 3: {Description: "Rule for kind 3"}, + }, + } + + event := createTestEvent(t, testSigner, "test", 99) + allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if allowed { + t.Error("Kind 99 should be REJECTED (no rule, implicit whitelist mode)") + } + }) + + t.Run("Explicit Whitelist + Global Rule - kind NOT in whitelist", func(t *testing.T) { + policy := &P{ + DefaultPolicy: "allow", + Kind: Kinds{ + Whitelist: []int{1, 3, 5}, // Explicit whitelist + }, + Global: Rule{ + Description: "Global rule applies to all kinds", + WriteAllow: []string{hex.Enc(testPubkey)}, + }, + Rules: map[int]Rule{ + 1: {Description: "Rule for kind 1"}, + }, + } + + // Even with global rule, kind not in whitelist should be rejected + event := createTestEvent(t, testSigner, "test", 99) + allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if allowed { + t.Error("Kind 99 should be REJECTED (NOT in whitelist, even with global rule)") + } + }) + + t.Run("Blacklist + Rules - kind in blacklist but has rule", func(t *testing.T) { + policy := &P{ + DefaultPolicy: "allow", + Kind: Kinds{ + Blacklist: []int{10, 20}, // Blacklist + }, + Rules: map[int]Rule{ + 1: {Description: "Rule for kind 1"}, + 10: {Description: "Rule for kind 10"}, // Has rule but blacklisted! + }, + } + + // Kind 10 is blacklisted, should be rejected + event := createTestEvent(t, testSigner, "test", 10) + allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if allowed { + t.Error("Kind 10 should be REJECTED (in blacklist)") + } + }) + + t.Run("Blacklist + Rules - kind NOT in blacklist but NO rule (implicit whitelist)", func(t *testing.T) { + policy := &P{ + DefaultPolicy: "allow", + Kind: Kinds{ + Blacklist: []int{10, 20}, // Blacklist + }, + Rules: map[int]Rule{ + 1: {Description: "Rule for kind 1"}, + 3: {Description: "Rule for kind 3"}, + }, + } + + // Kind 99 is not blacklisted but has no rule + // With blacklist present + rules, implicit whitelist applies + event := createTestEvent(t, testSigner, "test", 99) + allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if allowed { + t.Error("Kind 99 should be REJECTED (not in blacklist but no rule, implicit whitelist)") + } + }) + + t.Run("Whitelist takes precedence over Blacklist", func(t *testing.T) { + policy := &P{ + DefaultPolicy: "allow", + Kind: Kinds{ + Whitelist: []int{1, 3, 5, 10}, // Whitelist includes 10 + Blacklist: []int{10, 20}, // Blacklist also includes 10 + }, + Rules: map[int]Rule{ + 1: {Description: "Rule for kind 1"}, + 10: {Description: "Rule for kind 10"}, + }, + } + + // Kind 10 is in BOTH whitelist and blacklist - whitelist should win + event := createTestEvent(t, testSigner, "test", 10) + allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if !allowed { + t.Error("Kind 10 should be ALLOWED (whitelist takes precedence over blacklist)") + } + }) +} + +// TestKindWhitelistRealWorld tests real-world scenarios from the documentation +func TestKindWhitelistRealWorld(t *testing.T) { + testSigner, testPubkey := generateTestKeypair(t) + _, otherPubkey := generateTestKeypair(t) + + t.Run("Real World: Only allow kinds 1, 3, 30023", func(t *testing.T) { + policy := &P{ + DefaultPolicy: "allow", // Allow by default for kinds in whitelist + Kind: Kinds{ + Whitelist: []int{1, 3, 30023}, + }, + Rules: map[int]Rule{ + 1: { + Description: "Text notes", + // No WriteAllow = anyone authenticated can write + }, + 3: { + Description: "Contact lists", + // No WriteAllow = anyone authenticated can write + }, + 30023: { + Description: "Long-form content", + WriteAllow: []string{hex.Enc(testPubkey)}, // Only specific user can write + }, + }, + } + + // Test kind 1 (allowed) + event1 := createTestEvent(t, testSigner, "Hello world", 1) + allowed, err := policy.CheckPolicy("write", event1, testPubkey, "127.0.0.1") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if !allowed { + t.Error("Kind 1 should be ALLOWED") + } + + // Test kind 4 (NOT in whitelist, should be rejected) + event4 := createTestEvent(t, testSigner, "DM", 4) + allowed, err = policy.CheckPolicy("write", event4, testPubkey, "127.0.0.1") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if allowed { + t.Error("Kind 4 should be REJECTED (not in whitelist)") + } + + // Test kind 30023 by authorized user (allowed) + event30023Auth := createTestEvent(t, testSigner, "Article", 30023) + allowed, err = policy.CheckPolicy("write", event30023Auth, testPubkey, "127.0.0.1") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if !allowed { + t.Error("Kind 30023 should be ALLOWED for authorized user") + } + + // Test kind 30023 by unauthorized user (should fail rule check) + event30023Unauth := createTestEvent(t, testSigner, "Article", 30023) + allowed, err = policy.CheckPolicy("write", event30023Unauth, otherPubkey, "127.0.0.1") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if allowed { + t.Error("Kind 30023 should be REJECTED for unauthorized user") + } + + // Test kind 9735 (NOT in whitelist, should be rejected even with valid signature) + event9735 := createTestEvent(t, testSigner, "Zap", 9735) + allowed, err = policy.CheckPolicy("write", event9735, testPubkey, "127.0.0.1") + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if allowed { + t.Error("Kind 9735 should be REJECTED (not in whitelist)") + } + }) +} diff --git a/pkg/version/version b/pkg/version/version index e362ce5..f7dd314 100644 --- a/pkg/version/version +++ b/pkg/version/version @@ -1 +1 @@ -v0.29.11 \ No newline at end of file +v0.29.13 \ No newline at end of file