fix(security): CWE path-injection — resolveInsideRoot for Restart + ReadFile template paths (PR #1261)
workspace_restart.go:127-133 accepted body.Template (attacker-controlled) via raw filepath.Join(h.configsDir, template), allowing path traversal (e.g. "../../../etc") to escape configsDir. Fix: replace raw filepath.Join with resolveInsideRoot, same pattern as workspace.go:102 (already fixed) and workspace.go:249 (already fixed). Both the explicit template path and the findTemplateByName fallback are safe — findTemplateByName returns a directory name from os.ReadDir which is inherently bounded and cannot contain "/". On resolve error the template is cleared so findTemplateByName fallback still fires (preserves existing restart behaviour when template is invalid). Closes: #1043 Co-authored-by: Molecule AI Core-BE <core-be@agents.moleculesai.app> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
52709718ec
commit
0bd2bf2b7f
@ -295,6 +295,13 @@ func (h *TemplatesHandler) ReadFile(c *gin.Context) {
|
|||||||
c.JSON(http.StatusNotFound, gin.H{"error": "file not found (container offline, no template)"})
|
c.JSON(http.StatusNotFound, gin.H{"error": "file not found (container offline, no template)"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// validateRelPath is already called above (line 260) for the container path,
|
||||||
|
// but the fallback below uses filePath directly in filepath.Join without
|
||||||
|
// any sanitization. Re-validate before the host-side read to close the gap.
|
||||||
|
if err := validateRelPath(filePath); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid path"})
|
||||||
|
return
|
||||||
|
}
|
||||||
fullPath := filepath.Join(templateDir, filePath)
|
fullPath := filepath.Join(templateDir, filePath)
|
||||||
data, err := os.ReadFile(fullPath)
|
data, err := os.ReadFile(fullPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -125,10 +125,15 @@ func (h *WorkspaceHandler) Restart(c *gin.Context) {
|
|||||||
template = findTemplateByName(h.configsDir, wsName)
|
template = findTemplateByName(h.configsDir, wsName)
|
||||||
}
|
}
|
||||||
if template != "" {
|
if template != "" {
|
||||||
candidatePath := filepath.Join(h.configsDir, template)
|
candidatePath, resolveErr := resolveInsideRoot(h.configsDir, template)
|
||||||
if _, err := os.Stat(candidatePath); err == nil {
|
if resolveErr != nil {
|
||||||
|
log.Printf("Restart: invalid template %q: %v — proceeding without it", template, resolveErr)
|
||||||
|
template = "" // clear so findTemplateByName fallback fires
|
||||||
|
} else if _, err := os.Stat(candidatePath); err == nil {
|
||||||
templatePath = candidatePath
|
templatePath = candidatePath
|
||||||
configLabel = template
|
configLabel = template
|
||||||
|
} else {
|
||||||
|
log.Printf("Restart: template %q dir not found — proceeding without it", template)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user