@@ -125,7 +125,35 @@ func (d *FSPluginDecoder) Close() error {
125125 return nil
126126}
127127
128- func ensureUnderRoot (root , name string ) (string , error ) {
128+ // secureResolvePath securely resolves a path relative to a root directory.
129+ //
130+ // This function prevents path traversal attacks by validating that the resolved
131+ // path stays within the root directory. It handles both forward slashes and
132+ // OS-specific path separators, making it safe for cross-platform use.
133+ //
134+ // Parameters:
135+ // - root: The base directory path that acts as a security boundary
136+ // - name: A relative path (potentially with forward slashes) to resolve
137+ //
138+ // Returns:
139+ // - The absolute, resolved path if it stays within root
140+ // - An error if the path attempts to escape the root directory
141+ //
142+ // Security: This prevents attacks like "../../../etc/passwd" by computing
143+ // the relative path from root to the target and rejecting any path that
144+ // starts with ".." (indicating an escape attempt).
145+ //
146+ // Algorithm:
147+ // 1. Join root with name, converting forward slashes to OS format
148+ // 2. Clean the joined path to resolve any "." or ".." segments
149+ // 3. Convert both root and target to absolute paths
150+ // 4. Compute the relative path from root to target
151+ // 5. If relative path starts with "..", reject as path traversal
152+ //
153+ // Example:
154+ // root="/app/plugins", name="config/settings.yaml" -> "/app/plugins/config/settings.yaml"
155+ // root="/app/plugins", name="../../../etc/passwd" -> error (path traversal)
156+ func secureResolvePath (root , name string ) (string , error ) {
129157 p := filepath .Join (root , filepath .FromSlash (name ))
130158 clean := filepath .Clean (p )
131159 rootAbs , err := filepath .Abs (root )
@@ -150,15 +178,15 @@ func ensureUnderRoot(root, name string) (string, error) {
150178}
151179
152180func (d * FSPluginDecoder ) Stat (filename string ) (fs.FileInfo , error ) {
153- abs , err := ensureUnderRoot (d .root , filename )
181+ abs , err := secureResolvePath (d .root , filename )
154182 if err != nil {
155183 return nil , err
156184 }
157185 return os .Stat (abs )
158186}
159187
160188func (d * FSPluginDecoder ) ReadFile (filename string ) ([]byte , error ) {
161- abs , err := ensureUnderRoot (d .root , filename )
189+ abs , err := secureResolvePath (d .root , filename )
162190 if err != nil {
163191 return nil , err
164192 }
@@ -190,7 +218,7 @@ func (d *FSPluginDecoder) ReadDir(dirname string) ([]string, error) {
190218}
191219
192220func (d * FSPluginDecoder ) FileReader (filename string ) (io.ReadCloser , error ) {
193- abs , err := ensureUnderRoot (d .root , filename )
221+ abs , err := secureResolvePath (d .root , filename )
194222 if err != nil {
195223 return nil , err
196224 }
0 commit comments