@@ -159,13 +159,13 @@ func ContentGitDiff(c *gin.Context) {
159159// ContentGitStatus handles GET /content/git-status?path=
160160func ContentGitStatus (c * gin.Context ) {
161161 path := filepath .Clean ("/" + strings .TrimSpace (c .Query ("path" )))
162- if path == "/" || strings .Contains (path , ".." ) {
162+ abs := filepath .Join (StateBaseDir , path )
163+ // Verify abs is within StateBaseDir to prevent path traversal
164+ if ! strings .HasPrefix (abs , filepath .Clean (StateBaseDir )+ string (os .PathSeparator )) {
163165 c .JSON (http .StatusBadRequest , gin.H {"error" : "invalid path" })
164166 return
165167 }
166168
167- abs := filepath .Join (StateBaseDir , path )
168-
169169 // Check if directory exists
170170 if info , err := os .Stat (abs ); err != nil || ! info .IsDir () {
171171 c .JSON (http .StatusOK , gin.H {
@@ -224,13 +224,13 @@ func ContentGitConfigureRemote(c *gin.Context) {
224224 }
225225
226226 path := filepath .Clean ("/" + body .Path )
227- if path == "/" || strings .Contains (path , ".." ) {
227+ abs := filepath .Join (StateBaseDir , path )
228+ // Verify abs is within StateBaseDir to prevent path traversal
229+ if ! strings .HasPrefix (abs , filepath .Clean (StateBaseDir )+ string (os .PathSeparator )) {
228230 c .JSON (http .StatusBadRequest , gin.H {"error" : "invalid path" })
229231 return
230232 }
231233
232- abs := filepath .Join (StateBaseDir , path )
233-
234234 // Check if directory exists
235235 if info , err := os .Stat (abs ); err != nil || ! info .IsDir () {
236236 c .JSON (http .StatusBadRequest , gin.H {"error" : "directory not found" })
@@ -301,13 +301,13 @@ func ContentGitSync(c *gin.Context) {
301301 }
302302
303303 path := filepath .Clean ("/" + body .Path )
304- if path == "/" || strings .Contains (path , ".." ) {
304+ abs := filepath .Join (StateBaseDir , path )
305+ // Verify abs is within StateBaseDir to prevent path traversal
306+ if ! strings .HasPrefix (abs , filepath .Clean (StateBaseDir )+ string (os .PathSeparator )) {
305307 c .JSON (http .StatusBadRequest , gin.H {"error" : "invalid path" })
306308 return
307309 }
308310
309- abs := filepath .Join (StateBaseDir , path )
310-
311311 // Check if git repo exists
312312 gitDir := filepath .Join (abs , ".git" )
313313 if _ , err := os .Stat (gitDir ); err != nil {
@@ -343,12 +343,13 @@ func ContentWrite(c *gin.Context) {
343343 log .Printf ("ContentWrite: path=%q contentLen=%d encoding=%q StateBaseDir=%q" , req .Path , len (req .Content ), req .Encoding , StateBaseDir )
344344
345345 path := filepath .Clean ("/" + strings .TrimSpace (req .Path ))
346- if path == "/" || strings .Contains (path , ".." ) {
347- log .Printf ("ContentWrite: invalid path rejected: path=%q" , path )
346+ abs := filepath .Join (StateBaseDir , path )
347+ // Verify abs is within StateBaseDir to prevent path traversal
348+ if ! strings .HasPrefix (abs , filepath .Clean (StateBaseDir )+ string (os .PathSeparator )) {
349+ log .Printf ("ContentWrite: path traversal attempt rejected: path=%q abs=%q" , path , abs )
348350 c .JSON (http .StatusBadRequest , gin.H {"error" : "invalid path" })
349351 return
350352 }
351- abs := filepath .Join (StateBaseDir , path )
352353 log .Printf ("ContentWrite: absolute path=%q" , abs )
353354
354355 if err := os .MkdirAll (filepath .Dir (abs ), 0755 ); err != nil {
@@ -383,12 +384,13 @@ func ContentRead(c *gin.Context) {
383384 log .Printf ("ContentRead: requested path=%q StateBaseDir=%q" , c .Query ("path" ), StateBaseDir )
384385 log .Printf ("ContentRead: cleaned path=%q" , path )
385386
386- if path == "/" || strings .Contains (path , ".." ) {
387- log .Printf ("ContentRead: invalid path rejected: path=%q" , path )
387+ abs := filepath .Join (StateBaseDir , path )
388+ // Verify abs is within StateBaseDir to prevent path traversal
389+ if ! strings .HasPrefix (abs , filepath .Clean (StateBaseDir )+ string (os .PathSeparator )) {
390+ log .Printf ("ContentRead: path traversal attempt rejected: path=%q abs=%q" , path , abs )
388391 c .JSON (http .StatusBadRequest , gin.H {"error" : "invalid path" })
389392 return
390393 }
391- abs := filepath .Join (StateBaseDir , path )
392394 log .Printf ("ContentRead: absolute path=%q" , abs )
393395
394396 b , err := os .ReadFile (abs )
@@ -412,12 +414,13 @@ func ContentList(c *gin.Context) {
412414 log .Printf ("ContentList: cleaned path=%q" , path )
413415 log .Printf ("ContentList: StateBaseDir=%q" , StateBaseDir )
414416
415- if path == "/" || strings .Contains (path , ".." ) {
416- log .Printf ("ContentList: invalid path rejected: path=%q" , path )
417+ abs := filepath .Join (StateBaseDir , path )
418+ // Verify abs is within StateBaseDir to prevent path traversal
419+ if ! strings .HasPrefix (abs , filepath .Clean (StateBaseDir )+ string (os .PathSeparator )) {
420+ log .Printf ("ContentList: path traversal attempt rejected: path=%q abs=%q" , path , abs )
417421 c .JSON (http .StatusBadRequest , gin.H {"error" : "invalid path" })
418422 return
419423 }
420- abs := filepath .Join (StateBaseDir , path )
421424 log .Printf ("ContentList: absolute path=%q" , abs )
422425
423426 info , err := os .Stat (abs )
@@ -670,7 +673,9 @@ func ContentGitMergeStatus(c *gin.Context) {
670673 path := filepath .Clean ("/" + strings .TrimSpace (c .Query ("path" )))
671674 branch := strings .TrimSpace (c .Query ("branch" ))
672675
673- if path == "/" || strings .Contains (path , ".." ) {
676+ abs := filepath .Join (StateBaseDir , path )
677+ // Verify abs is within StateBaseDir to prevent path traversal
678+ if ! strings .HasPrefix (abs , filepath .Clean (StateBaseDir )+ string (os .PathSeparator )) {
674679 c .JSON (http .StatusBadRequest , gin.H {"error" : "invalid path" })
675680 return
676681 }
@@ -679,8 +684,6 @@ func ContentGitMergeStatus(c *gin.Context) {
679684 branch = "main"
680685 }
681686
682- abs := filepath .Join (StateBaseDir , path )
683-
684687 // Check if git repo exists
685688 gitDir := filepath .Join (abs , ".git" )
686689 if _ , err := os .Stat (gitDir ); err != nil {
@@ -718,7 +721,9 @@ func ContentGitPull(c *gin.Context) {
718721 }
719722
720723 path := filepath .Clean ("/" + body .Path )
721- if path == "/" || strings .Contains (path , ".." ) {
724+ abs := filepath .Join (StateBaseDir , path )
725+ // Verify abs is within StateBaseDir to prevent path traversal
726+ if ! strings .HasPrefix (abs , filepath .Clean (StateBaseDir )+ string (os .PathSeparator )) {
722727 c .JSON (http .StatusBadRequest , gin.H {"error" : "invalid path" })
723728 return
724729 }
@@ -727,8 +732,6 @@ func ContentGitPull(c *gin.Context) {
727732 body .Branch = "main"
728733 }
729734
730- abs := filepath .Join (StateBaseDir , path )
731-
732735 if err := GitPullRepo (c .Request .Context (), abs , body .Branch ); err != nil {
733736 c .JSON (http .StatusBadRequest , gin.H {"error" : err .Error ()})
734737 return
@@ -753,7 +756,9 @@ func ContentGitPushToBranch(c *gin.Context) {
753756 }
754757
755758 path := filepath .Clean ("/" + body .Path )
756- if path == "/" || strings .Contains (path , ".." ) {
759+ abs := filepath .Join (StateBaseDir , path )
760+ // Verify abs is within StateBaseDir to prevent path traversal
761+ if ! strings .HasPrefix (abs , filepath .Clean (StateBaseDir )+ string (os .PathSeparator )) {
757762 c .JSON (http .StatusBadRequest , gin.H {"error" : "invalid path" })
758763 return
759764 }
@@ -766,8 +771,6 @@ func ContentGitPushToBranch(c *gin.Context) {
766771 body .Message = "Session artifacts update"
767772 }
768773
769- abs := filepath .Join (StateBaseDir , path )
770-
771774 if err := GitPushToRepo (c .Request .Context (), abs , body .Branch , body .Message ); err != nil {
772775 c .JSON (http .StatusBadRequest , gin.H {"error" : err .Error ()})
773776 return
@@ -791,7 +794,9 @@ func ContentGitCreateBranch(c *gin.Context) {
791794 }
792795
793796 path := filepath .Clean ("/" + body .Path )
794- if path == "/" || strings .Contains (path , ".." ) {
797+ abs := filepath .Join (StateBaseDir , path )
798+ // Verify abs is within StateBaseDir to prevent path traversal
799+ if ! strings .HasPrefix (abs , filepath .Clean (StateBaseDir )+ string (os .PathSeparator )) {
795800 c .JSON (http .StatusBadRequest , gin.H {"error" : "invalid path" })
796801 return
797802 }
@@ -801,8 +806,6 @@ func ContentGitCreateBranch(c *gin.Context) {
801806 return
802807 }
803808
804- abs := filepath .Join (StateBaseDir , path )
805-
806809 if err := GitCreateBranch (c .Request .Context (), abs , body .BranchName ); err != nil {
807810 c .JSON (http .StatusBadRequest , gin.H {"error" : err .Error ()})
808811 return
@@ -816,13 +819,13 @@ func ContentGitCreateBranch(c *gin.Context) {
816819func ContentGitListBranches (c * gin.Context ) {
817820 path := filepath .Clean ("/" + strings .TrimSpace (c .Query ("path" )))
818821
819- if path == "/" || strings .Contains (path , ".." ) {
822+ abs := filepath .Join (StateBaseDir , path )
823+ // Verify abs is within StateBaseDir to prevent path traversal
824+ if ! strings .HasPrefix (abs , filepath .Clean (StateBaseDir )+ string (os .PathSeparator )) {
820825 c .JSON (http .StatusBadRequest , gin.H {"error" : "invalid path" })
821826 return
822827 }
823828
824- abs := filepath .Join (StateBaseDir , path )
825-
826829 branches , err := GitListRemoteBranches (c .Request .Context (), abs )
827830 if err != nil {
828831 c .JSON (http .StatusInternalServerError , gin.H {"error" : err .Error ()})
@@ -845,12 +848,13 @@ func ContentDelete(c *gin.Context) {
845848 log .Printf ("ContentDelete: path=%q StateBaseDir=%q" , req .Path , StateBaseDir )
846849
847850 path := filepath .Clean ("/" + strings .TrimSpace (req .Path ))
848- if path == "/" || strings .Contains (path , ".." ) {
849- log .Printf ("ContentDelete: invalid path rejected: path=%q" , path )
851+ abs := filepath .Join (StateBaseDir , path )
852+ // Verify abs is within StateBaseDir to prevent path traversal
853+ if ! strings .HasPrefix (abs , filepath .Clean (StateBaseDir )+ string (os .PathSeparator )) {
854+ log .Printf ("ContentDelete: path traversal attempt rejected: path=%q abs=%q" , path , abs )
850855 c .JSON (http .StatusBadRequest , gin.H {"error" : "invalid path" })
851856 return
852857 }
853- abs := filepath .Join (StateBaseDir , path )
854858 log .Printf ("ContentDelete: absolute path=%q" , abs )
855859
856860 // Check if file exists
0 commit comments