diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-15 18:01:49 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-15 18:01:49 +0200 |
| commit | 7a6b1c1ada3ba6ca2ca4ed70cf51f2f5f30d7b1e (patch) | |
| tree | 6b0e0ffd76f0de162093a56d1799847a8b81ade5 /internal/notify/service_test.go | |
| parent | 3b8a0e0f4a5c28394ac578e8559bf57ae82d3fd2 (diff) | |
| download | notifyd-7a6b1c1ada3ba6ca2ca4ed70cf51f2f5f30d7b1e.tar.gz notifyd-7a6b1c1ada3ba6ca2ca4ed70cf51f2f5f30d7b1e.zip | |
fix(notify): cover expiry cleanup and harden the image-removal guard
Split the dismiss test so expiry is exercised on its own: Expire removes
the image without deleting the inert entry, and nothing asserted it.
removeImage now requires filepath.Dir(filepath.Clean(path)) to equal the
image directory, so a path carrying .. cannot reach a sibling daemon file
such as queue.json. The previous prefix test accepted it. Not exploitable
until Task 4 populates Popup.Image from a client hint, which is exactly
why the guard is fixed now.
Diffstat (limited to 'internal/notify/service_test.go')
| -rw-r--r-- | internal/notify/service_test.go | 34 |
1 files changed, 34 insertions, 0 deletions
diff --git a/internal/notify/service_test.go b/internal/notify/service_test.go index fb47b98..871a0c7 100644 --- a/internal/notify/service_test.go +++ b/internal/notify/service_test.go @@ -144,6 +144,40 @@ func TestCloseNotificationEmitsReasonThree(t *testing.T) { } } +func TestRemoveImageRefusesPathsOutsideTheImageDir(t *testing.T) { + dir := t.TempDir() + imgDir := ImagesDir(dir) + if err := os.MkdirAll(imgDir, 0o700); err != nil { + t.Fatal(err) + } + sibling := filepath.Join(dir, "queue.json") + if err := os.WriteFile(sibling, []byte("x"), 0o600); err != nil { + t.Fatal(err) + } + owned := filepath.Join(imgDir, "1.png") + if err := os.WriteFile(owned, []byte("x"), 0o600); err != nil { + t.Fatal(err) + } + s := &Service{dir: dir} + + s.removeImage(filepath.Join(imgDir, "..", "queue.json")) + if _, err := os.Stat(sibling); err != nil { + t.Fatalf("traversal removed a sibling: %v", err) + } + s.removeImage(sibling) + if _, err := os.Stat(sibling); err != nil { + t.Fatalf("unrelated path removed a sibling: %v", err) + } + s.removeImage("") + if _, err := os.Stat(sibling); err != nil { + t.Fatalf("empty path removed a sibling: %v", err) + } + s.removeImage(owned) + if _, err := os.Stat(owned); !os.IsNotExist(err) { + t.Fatalf("owned image not removed: %v", err) + } +} + // A natural expiry must drop the timer entry, or the map grows by one timer per // id for the whole session. No bus is needed: the store's emit is a no-op here, // so the test exercises arm's closure directly. |
