Skip to content

Commit 7323e69

Browse files
mswiszczclaude
andcommitted
fix(badge): add path traversal protection for custom sounds, clean up clear event semantics
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 27e94df commit 7323e69

File tree

3 files changed

+19
-635
lines changed

3 files changed

+19
-635
lines changed

cmd/wsh/cmd/wshcmd-badge.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package cmd
66
import (
77
"fmt"
88
"runtime"
9+
"strings"
910

1011
"github.com/google/uuid"
1112
"github.com/spf13/cobra"
@@ -66,6 +67,13 @@ func badgeRun(cmd *cobra.Command, args []string) (rtnErr error) {
6667
resolvedSound = "system"
6768
}
6869

70+
// Validate custom sound filename (no path traversal)
71+
if resolvedSound != "" && resolvedSound != "system" {
72+
if strings.Contains(resolvedSound, "/") || strings.Contains(resolvedSound, "\\") || strings.Contains(resolvedSound, "..") {
73+
return fmt.Errorf("custom sound filename must not contain path separators or '..'")
74+
}
75+
}
76+
6977
oref, err := resolveBlockArg()
7078
if err != nil {
7179
return fmt.Errorf("resolving block: %v", err)
@@ -76,13 +84,14 @@ func badgeRun(cmd *cobra.Command, args []string) (rtnErr error) {
7684

7785
var eventData baseds.BadgeEvent
7886
eventData.ORef = oref.String()
79-
eventData.Sound = resolvedSound
80-
eventData.Border = badgeBorder
81-
eventData.BorderColor = badgeBorderColor
8287

8388
if badgeClear {
8489
eventData.Clear = true
8590
} else {
91+
eventData.Sound = resolvedSound
92+
eventData.Border = badgeBorder
93+
eventData.BorderColor = badgeBorderColor
94+
8695
icon := "circle-small"
8796
if len(args) > 0 {
8897
icon = args[0]

frontend/app/store/badge.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ function playBadgeSound(sound: string): void {
6666
fireAndForget(() => RpcApi.ElectronSystemBellCommand(TabRpcClient, { route: "electron" }));
6767
return;
6868
}
69+
// Reject sounds with path components (security: prevent path traversal)
70+
if (sound.includes("/") || sound.includes("\\") || sound.includes("..")) {
71+
console.warn(`[badge] rejecting sound with path components: ${sound}`);
72+
return;
73+
}
6974
let audioPath: string;
7075
if (BUILTIN_SOUND_PRESETS.has(sound)) {
7176
audioPath = `/sounds/${sound}.mp3`;

0 commit comments

Comments
 (0)