Skip to content

Commit 364af1c

Browse files
sallyomclaude
andcommitted
fix: resolve 3 PR review security blockers
- Add path traversal validation with explicit .. checks and path.Clean() - Move authentication validation to happen immediately before any operations - Verify all errors are properly handled (no ignored error values) Fixes path traversal vulnerability in PutSessionWorkspaceFile and DeleteSessionWorkspaceFile by validating paths stay within workspace directory. Corrects authentication check order to validate user tokens before RBAC checks or service lookups. Signed-off-by: sallyom <somalley@redhat.com> Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent c527be6 commit 364af1c

File tree

1 file changed

+57
-26
lines changed

1 file changed

+57
-26
lines changed

components/backend/handlers/sessions.go

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"net/http"
1212
"net/url"
1313
"os"
14+
"path"
1415
"sort"
1516
"strings"
1617
"time"
@@ -2512,25 +2513,37 @@ func PutSessionWorkspaceFile(c *gin.Context) {
25122513
c.JSON(http.StatusBadRequest, gin.H{"error": "Project namespace required"})
25132514
return
25142515
}
2515-
sub := strings.TrimPrefix(c.Param("path"), "/")
2516-
absPath := "/sessions/" + session + "/workspace/" + sub
2517-
token := c.GetHeader("Authorization")
2518-
if strings.TrimSpace(token) == "" {
2519-
token = c.GetHeader("X-Forwarded-Access-Token")
2520-
}
25212516

2522-
// Try temp service first (for completed sessions), then regular service
2523-
serviceName := fmt.Sprintf("temp-content-%s", session)
2517+
// Get user-scoped K8s clients and validate authentication IMMEDIATELY
25242518
reqK8s, reqDyn := GetK8sClientsForRequest(c)
2525-
serviceFound := false
2526-
2527-
// Check if user has valid auth before checking services or spawning temp pods
2528-
// This prevents unauthenticated requests from triggering resource creation
25292519
if reqK8s == nil || reqDyn == nil {
25302520
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or missing authentication token"})
25312521
return
25322522
}
25332523

2524+
// Validate and sanitize path to prevent directory traversal
2525+
sub := strings.TrimPrefix(c.Param("path"), "/")
2526+
// Check for path traversal attempts
2527+
if strings.Contains(sub, "..") {
2528+
log.Printf("PutSessionWorkspaceFile: path traversal attempt detected: %s", sub)
2529+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid path: path traversal not allowed"})
2530+
return
2531+
}
2532+
// Construct absolute path - Clean removes any remaining traversal attempts
2533+
workspaceBase := "/sessions/" + session + "/workspace/"
2534+
absPath := path.Clean(workspaceBase + sub)
2535+
// Verify path stays within workspace directory
2536+
if !strings.HasPrefix(absPath, workspaceBase) {
2537+
log.Printf("PutSessionWorkspaceFile: path escapes workspace: %s", absPath)
2538+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid path: must be within workspace directory"})
2539+
return
2540+
}
2541+
2542+
token := c.GetHeader("Authorization")
2543+
if strings.TrimSpace(token) == "" {
2544+
token = c.GetHeader("X-Forwarded-Access-Token")
2545+
}
2546+
25342547
// RBAC check: verify user has update permission on agenticsessions (file operations modify session state)
25352548
ssar := &authzv1.SelfSubjectAccessReview{
25362549
Spec: authzv1.SelfSubjectAccessReviewSpec{
@@ -2553,7 +2566,10 @@ func PutSessionWorkspaceFile(c *gin.Context) {
25532566
return
25542567
}
25552568

2556-
// reqK8s and reqDyn are guaranteed non-nil after auth check above
2569+
// Try temp service first (for completed sessions), then regular service
2570+
serviceName := fmt.Sprintf("temp-content-%s", session)
2571+
serviceFound := false
2572+
25572573
if _, err := reqK8s.CoreV1().Services(project).Get(c.Request.Context(), serviceName, v1.GetOptions{}); err != nil {
25582574
// Temp service doesn't exist, try regular service
25592575
serviceName = fmt.Sprintf("ambient-content-%s", session)
@@ -2683,25 +2699,37 @@ func DeleteSessionWorkspaceFile(c *gin.Context) {
26832699
c.JSON(http.StatusBadRequest, gin.H{"error": "Project namespace required"})
26842700
return
26852701
}
2686-
sub := strings.TrimPrefix(c.Param("path"), "/")
2687-
absPath := "/sessions/" + session + "/workspace/" + sub
2688-
token := c.GetHeader("Authorization")
2689-
if strings.TrimSpace(token) == "" {
2690-
token = c.GetHeader("X-Forwarded-Access-Token")
2691-
}
26922702

2693-
// Try temp service first, then regular service
2694-
serviceName := fmt.Sprintf("temp-content-%s", session)
2703+
// Get user-scoped K8s clients and validate authentication IMMEDIATELY
26952704
reqK8s, reqDyn := GetK8sClientsForRequest(c)
2696-
serviceFound := false
2697-
2698-
// Check if user has valid auth before checking services
2699-
// This prevents unauthenticated requests from accessing workspace files
27002705
if reqK8s == nil || reqDyn == nil {
27012706
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or missing authentication token"})
27022707
return
27032708
}
27042709

2710+
// Validate and sanitize path to prevent directory traversal
2711+
sub := strings.TrimPrefix(c.Param("path"), "/")
2712+
// Check for path traversal attempts
2713+
if strings.Contains(sub, "..") {
2714+
log.Printf("DeleteSessionWorkspaceFile: path traversal attempt detected: %s", sub)
2715+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid path: path traversal not allowed"})
2716+
return
2717+
}
2718+
// Construct absolute path - Clean removes any remaining traversal attempts
2719+
workspaceBase := "/sessions/" + session + "/workspace/"
2720+
absPath := path.Clean(workspaceBase + sub)
2721+
// Verify path stays within workspace directory
2722+
if !strings.HasPrefix(absPath, workspaceBase) {
2723+
log.Printf("DeleteSessionWorkspaceFile: path escapes workspace: %s", absPath)
2724+
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid path: must be within workspace directory"})
2725+
return
2726+
}
2727+
2728+
token := c.GetHeader("Authorization")
2729+
if strings.TrimSpace(token) == "" {
2730+
token = c.GetHeader("X-Forwarded-Access-Token")
2731+
}
2732+
27052733
// RBAC check: verify user has update permission on agenticsessions (file operations modify session state)
27062734
ssar := &authzv1.SelfSubjectAccessReview{
27072735
Spec: authzv1.SelfSubjectAccessReviewSpec{
@@ -2724,7 +2752,10 @@ func DeleteSessionWorkspaceFile(c *gin.Context) {
27242752
return
27252753
}
27262754

2727-
// reqK8s is guaranteed non-nil after auth check above
2755+
// Try temp service first, then regular service
2756+
serviceName := fmt.Sprintf("temp-content-%s", session)
2757+
serviceFound := false
2758+
27282759
if _, err := reqK8s.CoreV1().Services(project).Get(c.Request.Context(), serviceName, v1.GetOptions{}); err != nil {
27292760
// Temp service doesn't exist, try regular service
27302761
serviceName = fmt.Sprintf("ambient-content-%s", session)

0 commit comments

Comments
 (0)