diff --git a/internal/engine/auth.go b/internal/engine/auth.go index 8c705f9..fa88382 100644 --- a/internal/engine/auth.go +++ b/internal/engine/auth.go @@ -325,18 +325,16 @@ func splitAuthParams(value string) []string { return params } -// SIP Digest authentication (RFC 3261/7616) requires MD5; not password storage. -// -// codeql[go/weak-sensitive-data-hashing] func md5Hex(value string) string { + // SIP Digest authentication (RFC 3261/7616); not password storage. + // codeql[go/weak-sensitive-data-hashing] sum := md5.Sum([]byte(value)) return hex.EncodeToString(sum[:]) } -// SIP Digest authentication (RFC 3261/7616) requires SHA-256; not password storage. -// -// codeql[go/weak-sensitive-data-hashing] func sha256Hex(value string) string { + // SIP Digest authentication (RFC 3261/7616); not password storage. + // codeql[go/weak-sensitive-data-hashing] sum := sha256.Sum256([]byte(value)) return hex.EncodeToString(sum[:]) } diff --git a/internal/safepath/fs.go b/internal/safepath/fs.go index 39dc1b8..89f6e50 100644 --- a/internal/safepath/fs.go +++ b/internal/safepath/fs.go @@ -83,6 +83,30 @@ func ReadDir(baseRoot, dir string) ([]os.DirEntry, error) { return os.ReadDir(abs) } +// EnsureDirUnder creates dir when it resolves inside root. +func EnsureDirUnder(root, dir string, perm os.FileMode) error { + abs, err := resolveUnderRoot(root, dir) + if err != nil { + return err + } + // codeql[go/path-injection] + return os.MkdirAll(abs, perm) +} + +// RenameUnder renames src to dst when both paths resolve inside root. +func RenameUnder(root, src, dst string) error { + srcAbs, err := resolveUnderRoot(root, src) + if err != nil { + return err + } + dstAbs, err := resolveUnderRoot(root, dst) + if err != nil { + return err + } + // codeql[go/path-injection] + return os.Rename(srcAbs, dstAbs) +} + func resolveUnderRoot(root, target string) (string, error) { rootAbs, err := filepath.Abs(filepath.Clean(root)) if err != nil { diff --git a/internal/uistore/layout.go b/internal/uistore/layout.go index 2c478bc..d1f5746 100644 --- a/internal/uistore/layout.go +++ b/internal/uistore/layout.go @@ -101,13 +101,5 @@ func (l Layout) JobArtifactDir(jobID string) (string, error) { if !isSafeID(jobID) { return "", fmt.Errorf("uistore: invalid job id %q", jobID) } - base := filepath.Clean(l.JobsArtifactsDir()) - p, err := safepath.Join(base, jobID) - if err != nil { - return "", fmt.Errorf("uistore: invalid job id %q", jobID) - } - if err := os.MkdirAll(p, 0o750); err != nil { - return "", err - } - return p, nil + return safepath.EnsureJobArtifactsDir(l.Root, jobID, 0o750) } diff --git a/internal/uistore/store.go b/internal/uistore/store.go index fddeac1..84c7f58 100644 --- a/internal/uistore/store.go +++ b/internal/uistore/store.go @@ -11,6 +11,8 @@ import ( "strings" "sync" "time" + + "github.com/sipcapture/gossipper/internal/safepath" ) // ErrNotFound is returned when a profile / scenario / media asset is missing. @@ -102,7 +104,7 @@ func (s *Store) writeAtomic(targetPath string, data []byte, perm os.FileMode) er if err := ensurePathWithin(s.layout.Root, targetPath); err != nil { return err } - if err := os.MkdirAll(filepath.Dir(targetPath), 0o750); err != nil { + if err := safepath.EnsureDirUnder(s.layout.Root, filepath.Dir(targetPath), 0o750); err != nil { return err } tmp, err := os.CreateTemp(s.layout.TempDir(), "uistore-*.tmp") @@ -129,7 +131,7 @@ func (s *Store) writeAtomic(targetPath string, data []byte, perm os.FileMode) er cleanup() return err } - if err := os.Rename(tmpPath, targetPath); err != nil { + if err := safepath.RenameUnder(s.layout.Root, tmpPath, targetPath); err != nil { cleanup() return err }