Documentation
¶
Overview ¶
Package workspace holds the shared, security-sensitive primitives the standard tools depend on: `**`-aware glob matching, workspace path containment, and typed-nil detection for injected dependencies.
Index ¶
- func ContainedPath(root, input string) (string, error)
- func DenyFilteredRel(guard loop.ReadGuard, resolvedRoot, absolutePath string) (string, bool)
- func IsNil(value any) bool
- func JoinedPath(root, input string) string
- func MatchGlob(pattern, relPath string) bool
- func ResolveRoot(root string) (string, error)
- func ResolveSpawnDir(root, workdir string) (string, error)
- func ResolvedPath(root, input string) (abs string, contained bool, err error)
- type ContainmentError
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ContainedPath ¶
ContainedPath resolves input beneath root and fails closed when containment cannot be proven.
func DenyFilteredRel ¶
DenyFilteredRel applies the read guard to a canonical path and returns its workspace-relative slash form only when it remains inside the workspace.
func JoinedPath ¶
JoinedPath returns the lexically cleaned workspace path without resolving a final-component symlink. Callers must prove containment separately.
func MatchGlob ¶
MatchGlob matches a slash-separated workspace-relative path, including the recursive ** segment supported by the standard file and permission tools.
func ResolveRoot ¶
ResolveRoot returns the canonical absolute workspace root.
func ResolveSpawnDir ¶
ResolveSpawnDir resolves an optional workspace-relative command directory.
func ResolvedPath ¶
ResolvedPath resolves a caller-supplied path against a workspace root and reports whether the result is CONTAINED within that root, instead of rejecting an uncontained result the way ContainedPath does.
A RELATIVE input is resolved exactly as containedPath does -- anchored under root, symlinks resolved, any lexical or symlink escape above root rejected with an error. This preserves containedPath's existing escape-prevention unchanged: a relative "../" climb is still hard-rejected, never silently widened.
An ABSOLUTE input is honoured AS ABSOLUTE (not anchored under root): its existing prefix is symlink-resolved the same fail-secure way, and the result is reported contained=true or contained=false depending on whether it lands under the resolved root. This is the one new capability ResolvedPath adds over ContainedPath: a literal absolute path can now resolve to a real, reported location outside the workspace instead of being rejected outright.
ResolvedPath itself grants nothing: contained=false is not an authorization decision. Callers MUST route an uncontained result through their own authorization (e.g. the filesystem.read capability gate) before treating it as approved -- exactly as an uncontained Bash command target is authorized by the OS sandbox profile, never by this function.
Types ¶
type ContainmentError ¶
type ContainmentError struct {
Root string // the workspace root as supplied to containedPath
Input string // the caller-supplied (untrusted) path
Resolved string // the best resolved path we computed before rejecting ("" if none)
Reason string // human-readable, non-secret reason for the denial
Err error // underlying cause (e.g. an os/filepath error), may be nil
}
ContainmentError is the single typed error returned by containedPath whenever a path cannot be proven to live inside the workspace root, or the resolution itself fails. It is errors.As-able so callers can inspect every dimension of the rejection. A non-nil ContainmentError ALWAYS means "deny": containedPath is fail-secure and never returns a path alongside an error.
func (*ContainmentError) Error ¶
func (e *ContainmentError) Error() string
func (*ContainmentError) Unwrap ¶
func (e *ContainmentError) Unwrap() error
Unwrap exposes the underlying cause for errors.Is / errors.As chaining.