@@ -125,6 +125,7 @@ func (s *Server) handleSandboxCreate(c *gin.Context, kind string) {
125125 namespace := sandbox .Namespace
126126
127127 dynamicClient := s .k8sClient .dynamicClient
128+ var createdBy string
128129 if s .config .EnableAuth {
129130 userDynamicClient , errExtractClient := s .extractUserK8sClient (c )
130131 if errExtractClient != nil {
@@ -133,6 +134,7 @@ func (s *Server) handleSandboxCreate(c *gin.Context, kind string) {
133134 return
134135 }
135136 dynamicClient = userDynamicClient
137+ _ , _ , _ , createdBy = extractUserInfo (c )
136138 }
137139
138140 // CRITICAL: Register watcher BEFORE creating sandbox
@@ -141,7 +143,7 @@ func (s *Server) handleSandboxCreate(c *gin.Context, kind string) {
141143 // Ensure cleanup is called when function returns to prevent memory leak
142144 defer s .sandboxController .UnWatchSandbox (namespace , sandboxName )
143145
144- response , err := s .createSandbox (c .Request .Context (), dynamicClient , sandbox , sandboxClaim , sandboxEntry , resultChan )
146+ response , err := s .createSandbox (c .Request .Context (), dynamicClient , sandbox , sandboxClaim , sandboxEntry , resultChan , createdBy )
145147 if err != nil {
146148 // Client disconnected — abort with 499 so logs/metrics reflect the cancellation.
147149 if errors .Is (err , context .Canceled ) {
@@ -196,8 +198,8 @@ func (s *Server) createK8sResources(ctx context.Context, dynamicClient dynamic.I
196198}
197199
198200// createSandbox performs sandbox creation and returns the response payload or an error with an HTTP status code.
199- func (s * Server ) createSandbox (ctx context.Context , dynamicClient dynamic.Interface , sandbox * sandboxv1alpha1.Sandbox , sandboxClaim * extensionsv1alpha1.SandboxClaim , sandboxEntry * sandboxEntry , resultChan <- chan SandboxStatusUpdate ) (* types.CreateSandboxResponse , error ) {
200- placeholder := buildSandboxPlaceHolder (sandbox , sandboxEntry )
201+ func (s * Server ) createSandbox (ctx context.Context , dynamicClient dynamic.Interface , sandbox * sandboxv1alpha1.Sandbox , sandboxClaim * extensionsv1alpha1.SandboxClaim , sandboxEntry * sandboxEntry , resultChan <- chan SandboxStatusUpdate , createdBy string ) (* types.CreateSandboxResponse , error ) {
202+ placeholder := buildSandboxPlaceHolder (sandbox , sandboxEntry , createdBy )
201203 if err := s .storeClient .StoreSandbox (ctx , placeholder ); err != nil {
202204 if isContextError (err ) {
203205 return nil , err
@@ -261,7 +263,7 @@ func (s *Server) createSandbox(ctx context.Context, dynamicClient dynamic.Interf
261263 return nil , api .NewInternalError (fmt .Errorf ("failed to verify sandbox %s/%s entrypoints: %w" , sandbox .Namespace , sandbox .Name , err ))
262264 }
263265
264- storeCacheInfo := buildSandboxInfo (createdSandbox , podIP , sandboxEntry )
266+ storeCacheInfo := buildSandboxInfo (createdSandbox , podIP , sandboxEntry , createdBy )
265267
266268 response := & types.CreateSandboxResponse {
267269 Kind : storeCacheInfo .Kind ,
@@ -329,12 +331,19 @@ func (s *Server) handleGetSandbox(c *gin.Context, kind string) {
329331 }
330332
331333 if s .config .EnableAuth {
332- _ , userNamespace , _ , _ := extractUserInfo (c )
334+ _ , userNamespace , _ , serviceAccountName := extractUserInfo (c )
333335 if sandboxInfo .SandboxNamespace != userNamespace {
334336 klog .Warningf ("unauthorized GET attempt to session %s by user in namespace %s" , sessionID , userNamespace )
335337 respondError (c , http .StatusNotFound , fmt .Sprintf ("Session ID %s not found" , sessionID ))
336338 return
337339 }
340+ // Enforce per-service-account ownership: only the creating SA can retrieve its session.
341+ // Returns 404 (not 403) to prevent session ID enumeration.
342+ if sandboxInfo .CreatedBy != "" && sandboxInfo .CreatedBy != serviceAccountName {
343+ klog .Warningf ("unauthorized GET attempt to session %s by service account %s (owner: %s)" , sessionID , serviceAccountName , sandboxInfo .CreatedBy )
344+ respondError (c , http .StatusNotFound , fmt .Sprintf ("Session ID %s not found" , sessionID ))
345+ return
346+ }
338347 }
339348
340349 respondJSON (c , http .StatusOK , sandboxInfo )
@@ -354,13 +363,20 @@ func (s *Server) handleListSandboxes(c *gin.Context, kind string) {
354363 return
355364 }
356365
357- _ , userNamespace , _ , _ := extractUserInfo (c )
366+ _ , userNamespace , _ , serviceAccountName := extractUserInfo (c )
358367 // Filter in-place to avoid a new allocation.
368+ // When CreatedBy is set, enforce per-SA ownership (documented intent in auth.go).
369+ // When CreatedBy is empty (legacy entries created before this field was added),
370+ // fall back to namespace-scoped filtering so existing sessions remain accessible.
359371 filtered := sandboxes [:0 ]
360372 for _ , sb := range sandboxes {
361- if sb .SandboxNamespace == userNamespace {
362- filtered = append (filtered , sb )
373+ if sb .SandboxNamespace != userNamespace {
374+ continue
375+ }
376+ if sb .CreatedBy != "" && sb .CreatedBy != serviceAccountName {
377+ continue
363378 }
379+ filtered = append (filtered , sb )
364380 }
365381 respondJSON (c , http .StatusOK , filtered )
366382}
0 commit comments