// Package localfile provides explicit policies for opening regular local files. // The zero value denies access. Callers must deliberately choose either a // symlink-safe filesystem root or unrestricted host-filesystem access. // // On Unix, files are opened nonblocking and verified from the opened descriptor // before Open returns, preventing ordinary FIFO open waits. Device-specific // open behavior can still vary. Other platforms have no portable nonblocking // open flag, so a hostile special file may block before it can be rejected. In // addition, os.Root cannot provide a symlink security boundary on GOOS=js and // does not retain directory identity across renames on GOOS=js or GOOS=plan9. // Callers on those platforms must not treat Rooted as a complete boundary for // hostile filesystems.
package localfile import ( ) // ErrDenied reports that a policy does not permit a local-file path. var ErrDenied = errors.New("localfile: access denied") type policyMode uint8 const ( modeDenied policyMode = iota modeRooted modeUnrestricted ) // Policy controls access to local files. Its zero value denies all local-file // access and is safe for untrusted input. // // Policy implements the part of fs.FS needed by D2's compiler imports. Open // intentionally rejects directories and other non-regular files, so Policy is // not a general-purpose directory-browsing filesystem. type Policy struct { mode policyMode rootPath string root *rootState cacheScope [32]byte } // rootState makes Policy copies share exactly one close operation. Policy is a // value type and is commonly copied into compiler and asset resolver options. type rootState struct { handle *os.Root once sync.Once err error } // Rooted returns a policy that permits files at or beneath root. Opens use // one retained os.Root, so symbolic links cannot escape root and replacing the // configured path cannot retarget the policy on supported platforms. func ( string) (Policy, error) { , := filepath.Abs() if != nil { return Policy{}, fmt.Errorf("localfile: resolve root %q: %w", , ) } = filepath.Clean() , := os.OpenRoot() if != nil { return Policy{}, fmt.Errorf("localfile: open root %q: %w", , ) } var [32]byte if , := rand.Read([:]); != nil { return Policy{}, errors.Join( fmt.Errorf("localfile: create cache scope for root %q: %w", , ), closeRoot(, ), ) } return Policy{mode: modeRooted, rootPath: , root: &rootState{handle: }, cacheScope: }, nil } // Unrestricted returns a policy that permits arbitrary host-filesystem reads. // Use it only for trusted local input, such as the D2 command-line interface. func () Policy { return Policy{mode: modeUnrestricted} } // RootPath returns the absolute configured root and true for a rooted policy. // It returns an empty string and false for denied and unrestricted policies. func ( Policy) () (string, bool) { return .rootPath, .mode == modeRooted } // Close releases the retained OS root handle. Copies of a rooted Policy share // one copy-safe close operation and return the same close result. It does not // erase or revoke immutable resources that callers already retained in their // own caches. Closing a denied or unrestricted policy is a no-op. func ( Policy) () error { if .mode != modeRooted || .root == nil { return nil } .root.once.Do(func() { .root.err = closeRoot(.root.handle, .rootPath) }) return .root.err } // CacheKey returns a policy-scoped, canonical key for name. It rejects names // outside the policy's lexical scope; Open additionally enforces the rooted // symbolic-link boundary. The key prevents a cache filled under an unrestricted // policy from bypassing a rooted or denied policy. func ( Policy) ( string) (string, error) { , := .resolve() if != nil { return "", } switch .mode { case modeRooted: // The random per-policy scope prevents a cache entry produced through an // older directory at the same path from crossing into a replacement root. return fmt.Sprintf("root:%x:%s", .cacheScope, filepath.ToSlash(.relative)), nil case modeUnrestricted: return "unrestricted:" + filepath.ToSlash(.absolute), nil default: panic("localfile: resolved path under invalid policy mode") } } // Open opens a regular file according to the policy. For rooted policies, // relative names are interpreted relative to the configured root. Absolute // names are accepted only when they are lexically beneath that root, and the // retained os.Root enforces the same boundary while following symbolic links. func ( Policy) ( string) (fs.File, error) { , := .resolve() if != nil { return nil, } if .mode == modeUnrestricted { , := openFile(.absolute) if != nil { return nil, } return requireRegular(, ) } if .root == nil || .root.handle == nil { return nil, errors.New("localfile: invalid rooted policy") } , := openFileInRoot(.root.handle, .relative) if != nil { return nil, fmt.Errorf("localfile: open %q beneath root %q: %w", , .rootPath, ) } return requireRegular(, ) } func requireRegular( string, *os.File) (fs.File, error) { , := .Stat() if != nil { return nil, errors.Join( fmt.Errorf("localfile: stat opened file %q: %w", , ), closeFile(, ), ) } if !.Mode().IsRegular() { return nil, errors.Join( fmt.Errorf("localfile: %q is not a regular file", ), closeFile(, ), ) } return , nil } func closeFile( *os.File, string) error { if := .Close(); != nil { return fmt.Errorf("localfile: close %q: %w", , ) } return nil } func closeRoot( *os.Root, string) error { if := .Close(); != nil { return fmt.Errorf("localfile: close root %q: %w", , ) } return nil } type resolvedPath struct { absolute string relative string } func ( Policy) ( string) (resolvedPath, error) { switch .mode { case modeDenied: return resolvedPath{}, fmt.Errorf("%w: local-file access is disabled", ErrDenied) case modeUnrestricted: , := filepath.Abs() if != nil { return resolvedPath{}, fmt.Errorf("localfile: resolve %q: %w", , ) } return resolvedPath{absolute: filepath.Clean()}, nil case modeRooted: var string if filepath.IsAbs() { var error , = filepath.Rel(.rootPath, filepath.Clean()) if != nil { return resolvedPath{}, fmt.Errorf("localfile: resolve %q beneath root %q: %w", , .rootPath, ) } } else { = filepath.Clean() } if !filepath.IsLocal() { return resolvedPath{}, fmt.Errorf("%w: %q is outside configured root %q", ErrDenied, , .rootPath) } return resolvedPath{ absolute: filepath.Join(.rootPath, ), relative: , }, nil default: return resolvedPath{}, errors.New("localfile: invalid policy") } }