bug_reproduction_test.go raw
1 package policy
2
3 import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "os"
8 "path/filepath"
9 "strings"
10 "testing"
11
12 "next.orly.dev/pkg/lol/log"
13 )
14
15 // TestBugReproduction_Kind1AllowedWithWhitelist4678 reproduces the reported bug
16 // where kind 1 events are being accepted even though only kind 4678 is in the whitelist.
17 func TestBugReproduction_Kind1AllowedWithWhitelist4678(t *testing.T) {
18 testSigner, testPubkey := generateTestKeypair(t)
19
20 // Create policy matching the production configuration
21 policyJSON := `{
22 "kind": { "whitelist": [4678] },
23 "rules": {
24 "4678": {
25 "description": "Zenotp events",
26 "script": "policy.sh"
27 }
28 }
29 }`
30
31 policy, err := New([]byte(policyJSON))
32 if err != nil {
33 t.Fatalf("Failed to create policy: %v", err)
34 }
35
36 t.Run("Kind 1 should be REJECTED (not in whitelist)", func(t *testing.T) {
37 event := createTestEvent(t, testSigner, "Hello Nostr!", 1)
38 allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
39 if err != nil {
40 t.Fatalf("Unexpected error: %v", err)
41 }
42 if allowed {
43 t.Errorf("BUG REPRODUCED: Kind 1 event was ALLOWED but should be REJECTED (only kind 4678 is whitelisted)")
44 t.Logf("Policy whitelist: %v", policy.Kind.Whitelist)
45 t.Logf("Policy rules: %v", policy.rules)
46 t.Logf("Default policy: %s", policy.DefaultPolicy)
47 }
48 })
49
50 t.Run("Kind 4678 should be ALLOWED (in whitelist)", func(t *testing.T) {
51 event := createTestEvent(t, testSigner, "Zenotp event", 4678)
52 allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
53 if err != nil {
54 t.Fatalf("Unexpected error: %v", err)
55 }
56 if !allowed {
57 t.Error("Kind 4678 should be ALLOWED (in whitelist)")
58 }
59 })
60 }
61
62 // TestBugReproduction_WithPolicyManager tests with a full policy manager setup
63 // to match production environment more closely
64 func TestBugReproduction_WithPolicyManager(t *testing.T) {
65 testSigner, testPubkey := generateTestKeypair(t)
66
67 // Create a temporary config directory
68 tmpDir := t.TempDir()
69 configDir := filepath.Join(tmpDir, "ORLY")
70 if err := os.MkdirAll(configDir, 0755); err != nil {
71 t.Fatalf("Failed to create config dir: %v", err)
72 }
73
74 // Write policy configuration matching production
75 policyConfig := map[string]interface{}{
76 "kind": map[string]interface{}{
77 "whitelist": []int{4678},
78 },
79 "rules": map[string]interface{}{
80 "4678": map[string]interface{}{
81 "description": "Zenotp events",
82 "script": "policy.sh",
83 },
84 },
85 }
86
87 policyJSON, err := json.MarshalIndent(policyConfig, "", " ")
88 if err != nil {
89 t.Fatalf("Failed to marshal policy JSON: %v", err)
90 }
91
92 policyPath := filepath.Join(configDir, "policy.json")
93 if err := os.WriteFile(policyPath, policyJSON, 0644); err != nil {
94 t.Fatalf("Failed to write policy file: %v", err)
95 }
96
97 // Create policy with manager (enabled)
98 ctx := context.Background()
99 policy := NewWithManager(ctx, "ORLY", true, "")
100
101 // Load policy from file
102 if err := policy.LoadFromFile(policyPath); err != nil {
103 t.Fatalf("Failed to load policy from file: %v", err)
104 }
105
106 t.Run("Kind 1 should be REJECTED with PolicyManager", func(t *testing.T) {
107 event := createTestEvent(t, testSigner, "Hello Nostr!", 1)
108 allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
109 if err != nil {
110 t.Fatalf("Unexpected error: %v", err)
111 }
112 if allowed {
113 t.Errorf("BUG REPRODUCED: Kind 1 event was ALLOWED but should be REJECTED")
114 t.Logf("Policy whitelist: %v", policy.Kind.Whitelist)
115 t.Logf("Policy rules: %v", policy.rules)
116 t.Logf("Default policy: %s", policy.DefaultPolicy)
117 t.Logf("Manager enabled: %v", policy.manager.IsEnabled())
118 }
119 })
120
121 t.Run("Kind 4678 should be ALLOWED with PolicyManager", func(t *testing.T) {
122 event := createTestEvent(t, testSigner, "Zenotp event", 4678)
123 allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
124 if err != nil {
125 t.Fatalf("Unexpected error: %v", err)
126 }
127 if !allowed {
128 t.Error("Kind 4678 should be ALLOWED (in whitelist)")
129 }
130 })
131
132 // Clean up
133 if policy.manager != nil {
134 policy.manager.Shutdown()
135 }
136 }
137
138 // TestBugReproduction_DebugPolicyFlow adds verbose logging to debug the policy flow
139 func TestBugReproduction_DebugPolicyFlow(t *testing.T) {
140 testSigner, testPubkey := generateTestKeypair(t)
141
142 policyJSON := `{
143 "kind": { "whitelist": [4678] },
144 "rules": {
145 "4678": {
146 "description": "Zenotp events",
147 "script": "policy.sh"
148 }
149 }
150 }`
151
152 policy, err := New([]byte(policyJSON))
153 if err != nil {
154 t.Fatalf("Failed to create policy: %v", err)
155 }
156
157 event := createTestEvent(t, testSigner, "Hello Nostr!", 1)
158
159 t.Logf("=== Policy Configuration ===")
160 t.Logf("Whitelist: %v", policy.Kind.Whitelist)
161 t.Logf("Blacklist: %v", policy.Kind.Blacklist)
162 t.Logf("rules: %v", policy.rules)
163 t.Logf("Default policy: %s", policy.DefaultPolicy)
164 t.Logf("")
165 t.Logf("=== Event Details ===")
166 t.Logf("Event kind: %d", event.Kind)
167 t.Logf("")
168 t.Logf("=== Policy Check Flow ===")
169
170 // Step 1: Check kinds policy
171 kindsAllowed := policy.checkKindsPolicy("write", event.Kind)
172 t.Logf("1. checkKindsPolicy(access=write, kind=%d) returned: %v", event.Kind, kindsAllowed)
173
174 // Full policy check
175 allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
176 t.Logf("2. CheckPolicy returned: allowed=%v, err=%v", allowed, err)
177
178 if allowed {
179 t.Errorf("BUG REPRODUCED: Kind 1 should be REJECTED but was ALLOWED")
180 }
181 }
182
183 // TestBugFix_FailSafeWhenConfigMissing tests the fix for the security bug
184 // where missing config would allow all events
185 func TestBugFix_FailSafeWhenConfigMissing(t *testing.T) {
186 testSigner, testPubkey := generateTestKeypair(t)
187
188 t.Run("Missing config with enabled policy causes panic", func(t *testing.T) {
189 // When policy is enabled but config file is missing, NewWithManager should panic
190 // This is a FATAL configuration error that must be fixed before the relay can start
191
192 defer func() {
193 r := recover()
194 if r == nil {
195 t.Error("Expected panic when policy is enabled but config is missing, but no panic occurred")
196 } else {
197 // Verify the panic message mentions the config error
198 panicMsg := fmt.Sprintf("%v", r)
199 if !strings.Contains(panicMsg, "fatal policy configuration error") {
200 t.Errorf("Panic message should mention 'fatal policy configuration error', got: %s", panicMsg)
201 }
202 t.Logf("Correctly panicked with message: %s", panicMsg)
203 }
204 }()
205
206 // Simulate NewWithManager behavior by directly testing the panic path
207 // Create a policy manager with a non-existent config path
208 ctx := context.Background()
209 tmpDir := t.TempDir()
210 configDir := filepath.Join(tmpDir, "ORLY_TEST_NO_CONFIG")
211 configPath := filepath.Join(configDir, "policy.json")
212
213 // Ensure directory exists but file doesn't
214 os.MkdirAll(configDir, 0755)
215
216 manager := &PolicyManager{
217 ctx: ctx,
218 configDir: configDir,
219 scriptPath: filepath.Join(configDir, "policy.sh"),
220 enabled: true,
221 runners: make(map[string]*ScriptRunner),
222 }
223
224 policy := &P{
225 DefaultPolicy: "allow",
226 manager: manager,
227 }
228
229 // Try to load from nonexistent file - this should trigger the panic
230 if err := policy.LoadFromFile(configPath); err != nil {
231 // Simulate what NewWithManager does when LoadFromFile fails
232 log.E.F(
233 "FATAL: Policy system is ENABLED (ORLY_POLICY_ENABLED=true) but configuration failed to load from %s: %v",
234 configPath, err,
235 )
236 log.E.F("The relay cannot start with an invalid policy configuration.")
237 log.E.F("Fix: Either disable the policy system (ORLY_POLICY_ENABLED=false) or ensure %s exists and contains valid JSON", configPath)
238 panic(fmt.Sprintf("fatal policy configuration error: %v", err))
239 }
240
241 // Should never reach here
242 t.Error("Should have panicked but didn't")
243 })
244
245 t.Run("Empty whitelist respects default_policy=deny", func(t *testing.T) {
246 // Create policy with empty whitelist and deny default
247 policy := &P{
248 DefaultPolicy: "deny",
249 Kind: Kinds{
250 Whitelist: []int{}, // Empty
251 },
252 rules: make(map[int]Rule), // No rules
253 }
254
255 event := createTestEvent(t, testSigner, "Hello Nostr!", 1)
256 allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
257 if err != nil {
258 t.Fatalf("Unexpected error: %v", err)
259 }
260 if allowed {
261 t.Error("Kind 1 should be REJECTED with empty whitelist and default_policy=deny")
262 }
263 })
264
265 t.Run("Empty whitelist respects default_policy=allow", func(t *testing.T) {
266 // Create policy with empty whitelist and allow default
267 policy := &P{
268 DefaultPolicy: "allow",
269 Kind: Kinds{
270 Whitelist: []int{}, // Empty
271 },
272 rules: make(map[int]Rule), // No rules
273 }
274
275 event := createTestEvent(t, testSigner, "Hello Nostr!", 1)
276 allowed, err := policy.CheckPolicy("write", event, testPubkey, "127.0.0.1")
277 if err != nil {
278 t.Fatalf("Unexpected error: %v", err)
279 }
280 if !allowed {
281 t.Error("Kind 1 should be ALLOWED with empty whitelist and default_policy=allow")
282 }
283 })
284 }
285