From a52e5f53c096961b61e4016a35b693aa72b51339 Mon Sep 17 00:00:00 2001 From: bircni Date: Fri, 21 Aug 2026 09:52:23 +0200 Subject: [PATCH] fix(git): restrict hook permissions (#39008) Create delegate hook files and directories without group or other write access, including correcting existing hook directories. _Assisted-by: Codex:GPT-5_ --- modules/git/hooks.go | 12 ++++++++---- modules/git/hooks_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 modules/git/hooks_test.go diff --git a/modules/git/hooks.go b/modules/git/hooks.go index 7fe1153777e..365706de06e 100644 --- a/modules/git/hooks.go +++ b/modules/git/hooks.go @@ -119,15 +119,19 @@ func createDelegateHooks(hookDir string) (err error) { oldHookPath := filepath.Join(hookDir, hookName) newHookPath := filepath.Join(hookDir, hookName+".d", "gitea") - if err := os.MkdirAll(filepath.Join(hookDir, hookName+".d"), os.ModePerm); err != nil { - return fmt.Errorf("create hooks dir '%s': %w", filepath.Join(hookDir, hookName+".d"), err) + hookDDir := filepath.Join(hookDir, hookName+".d") + if err := os.MkdirAll(hookDDir, 0o755); err != nil { + return fmt.Errorf("create hooks dir '%s': %w", hookDDir, err) + } + if err := os.Chmod(hookDDir, 0o755); err != nil { + return fmt.Errorf("chmod hooks dir '%s': %w", hookDDir, err) } // WARNING: This will override all old server-side hooks if err = util.RemoveWithRetry(oldHookPath); err != nil && !os.IsNotExist(err) { return fmt.Errorf("unable to pre-remove old hook file '%s' prior to rewriting: %w ", oldHookPath, err) } - if err = os.WriteFile(oldHookPath, []byte(hookTpls[i]), 0o777); err != nil { + if err = os.WriteFile(oldHookPath, []byte(hookTpls[i]), 0o755); err != nil { return fmt.Errorf("write old hook file '%s': %w", oldHookPath, err) } @@ -138,7 +142,7 @@ func createDelegateHooks(hookDir string) (err error) { if err = util.RemoveWithRetry(newHookPath); err != nil && !os.IsNotExist(err) { return fmt.Errorf("unable to pre-remove new hook file '%s' prior to rewriting: %w", newHookPath, err) } - if err = os.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0o777); err != nil { + if err = os.WriteFile(newHookPath, []byte(giteaHookTpls[i]), 0o755); err != nil { return fmt.Errorf("write new hook file '%s': %w", newHookPath, err) } diff --git a/modules/git/hooks_test.go b/modules/git/hooks_test.go new file mode 100644 index 00000000000..7671b8a7429 --- /dev/null +++ b/modules/git/hooks_test.go @@ -0,0 +1,34 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestCreateDelegateHooksPermissions(t *testing.T) { + hookDir := t.TempDir() + existingHookDir := filepath.Join(hookDir, "post-receive.d") + require.NoError(t, os.MkdirAll(existingHookDir, 0o777)) + require.NoError(t, os.Chmod(existingHookDir, 0o777)) + + require.NoError(t, createDelegateHooks(hookDir)) + + hookNames, _, _ := getHookTemplates() + for _, hookName := range hookNames { + for _, path := range []string{ + filepath.Join(hookDir, hookName), + filepath.Join(hookDir, hookName+".d"), + filepath.Join(hookDir, hookName+".d", "gitea"), + } { + info, err := os.Stat(path) + require.NoError(t, err) + require.Equal(t, os.FileMode(0o755), info.Mode().Perm(), path) + } + } +}