Phase 3 security audit follow-up. Medium and low-severity hardenings plus a deferred-work plan for the cross-repo stream-token rollout. Stream server CORS: replace the wildcard Access-Control-Allow-Origin with an allowlist that echoes back only torrentclaw.com, app.torrentclaw.com, the local Next dev port (3030 — matches the web repo package.json) and any extras the operator adds via the new downloads.cors_extra_origins TOML key. A Vary: Origin header is now emitted whenever the request carries an Origin header so an intermediate cache cannot serve a stale ACAO to a different origin. URL scheme guard: openBrowser and OpenPlayer refuse any URL that is not http(s). Combined with passing the URL after "--" wherever the launched helper supports it (open, mpv, vlc, cvlc), this stops a leading "-" from being parsed as a switch by the spawned process. State file permissions: WriteState now writes 0o600 so the agent ID, PID and counters cannot be enumerated by another local user on a shared host. Matches the existing config file mode. ZIP slip defense-in-depth: extractZip extracts the safety check into safeZipPath, which canonicalises the entry name (normalising backslashes to "/"), rejects "..", "../" prefix and "/../" interior components, and verifies the final destination stays inside destDir before opening any file. Mirror fallback: documented the design for multi-provider mirrors.json hosting in the comment block on DefaultStaticFallbackURLs and added a follow-up note about signing it with the same ed25519 release key. The list is kept at one provider until the second host is provisioned and added to torrentclaw-web's STATIC_FALLBACKS. Deferred work: a new plan document Docs/plans/security-stream-token.md covers the per-task stream token (Phase 2.2 of the original audit) which requires coordinated web + CLI work and ships separately.
71 lines
1.9 KiB
Go
71 lines
1.9 KiB
Go
package cmd
|
|
|
|
import (
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
)
|
|
|
|
// openBrowser opens a URL in the default browser.
|
|
//
|
|
// The URL is restricted to http(s) so that a hostile caller cannot trick
|
|
// xdg-open/open into interpreting it as a flag (a leading "-" would otherwise
|
|
// match a switch on every helper we shell out to). Where the helper supports
|
|
// it we also append "--" to terminate switch parsing as belt-and-braces.
|
|
func openBrowser(url string) {
|
|
if !isSafeBrowserURL(url) {
|
|
return
|
|
}
|
|
var c *exec.Cmd
|
|
switch runtime.GOOS {
|
|
case "darwin":
|
|
c = exec.Command("open", "--", url)
|
|
case "windows":
|
|
// rundll32 does not parse switches from positional args.
|
|
c = exec.Command("rundll32", "url.dll,FileProtocolHandler", url)
|
|
default: // linux, freebsd
|
|
c = exec.Command("xdg-open", url)
|
|
}
|
|
_ = c.Start() // fire and forget; best-effort
|
|
}
|
|
|
|
// isSafeBrowserURL accepts only http(s) URLs. Other schemes (file://, javascript:,
|
|
// data:, ...) and flag-shaped strings ("--help") are rejected.
|
|
func isSafeBrowserURL(url string) bool {
|
|
return strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://")
|
|
}
|
|
|
|
// defaultDownloadDir returns a sensible default download directory.
|
|
func defaultDownloadDir() string {
|
|
home, _ := os.UserHomeDir()
|
|
candidates := []string{
|
|
filepath.Join(home, "Media"),
|
|
filepath.Join(home, "Downloads", "unarr"),
|
|
}
|
|
for _, d := range candidates {
|
|
if fi, err := os.Stat(d); err == nil && fi.IsDir() {
|
|
return d
|
|
}
|
|
}
|
|
return filepath.Join(home, "Media")
|
|
}
|
|
|
|
// expandHome expands a leading ~/ to the user's home directory.
|
|
func expandHome(path string) string {
|
|
if strings.HasPrefix(path, "~/") {
|
|
home, _ := os.UserHomeDir()
|
|
return filepath.Join(home, path[2:])
|
|
}
|
|
return path
|
|
}
|
|
|
|
// isTerminal checks if stdin is a terminal (not piped).
|
|
func isTerminal() bool {
|
|
fi, err := os.Stdin.Stat()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
return fi.Mode()&os.ModeCharDevice != 0
|
|
}
|