path and path/filepath Deep Dive

Updated

September 8, 2026

path and path/filepath Deep Dive

Overview

path is slash-separated (URLs, import paths). path/filepath is OS paths. Mixing them is a common bug.

Quick rules

Package Use
path/filepath Files on disk
path URL paths, logical slash paths
filepath.Join("a", "b")     // a/b or a\b
path.Join("a", "b")         // always a/b
filepath.Base("/tmp/x.go")
filepath.Ext("x.go")        // .go
filepath.Clean("../etc/passwd")
filepath.Abs(".")

Safe join (jail)

func safeJoin(root, unsafe string) (string, error) {
    c := filepath.Clean("/" + unsafe) // force abs clean form
    rel := strings.TrimPrefix(c, string(filepath.Separator))
    full := filepath.Join(root, rel)
    absRoot, _ := filepath.Abs(root)
    absFull, _ := filepath.Abs(full)
    if !strings.HasPrefix(absFull, absRoot+string(filepath.Separator)) && absFull != absRoot {
        return "", fmt.Errorf("escape")
    }
    return absFull, nil
}

Walk

filepath.WalkDir(root, func(p string, d fs.DirEntry, err error) error {
    if err != nil {
        return err
    }
    if d.IsDir() && d.Name() == ".git" {
        return fs.SkipDir
    }
    return nil
})

Match / Glob

filepath.Glob("*.go")
filepath.Match("a/*.txt", name)

Rules

Do Don’t
filepath for files path.Join for disk on Windows
Clean + jail user paths Concatenate root + "/" + user

Try next

  1. Prove path.Join vs filepath.Join docs on Windows GOOS.
  2. WalkDir skip node_modules.
  3. SafeJoin reject ../../etc/passwd.