diff --git a/CHANGELOG.md b/CHANGELOG.md index 60bd139a..1d35f4d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Security + +- the config file, which stores the API token, is now saved with owner-only + permissions, and files left world-readable by older versions are tightened on + the next save + ### Fixed - shell completion no longer crashes with an index-out-of-range panic when diff --git a/pkg/cmdutil/config.go b/pkg/cmdutil/config.go index 0b6f6b63..a760c5b2 100644 --- a/pkg/cmdutil/config.go +++ b/pkg/cmdutil/config.go @@ -223,11 +223,19 @@ func (*config) Save() error { } dir := path.Join(home, ".config", "clockify-cli") - _ = os.MkdirAll(dir, os.ModePerm) + if err := os.MkdirAll(dir, 0o700); err != nil { + return err + } filename = path.Join(dir, ".clockify-cli.yaml") } - return viper.WriteConfigAs(filename) + // the file holds the api token, keep it owner-only even when it was + // created world-readable by an older version + viper.SetConfigPermissions(0o600) + if err := viper.WriteConfigAs(filename); err != nil { + return err + } + return os.Chmod(filename, 0o600) } func configFunc() func() (c Config) { diff --git a/pkg/cmdutil/config_save_test.go b/pkg/cmdutil/config_save_test.go new file mode 100644 index 00000000..bb87b355 --- /dev/null +++ b/pkg/cmdutil/config_save_test.go @@ -0,0 +1,46 @@ +package cmdutil_test + +import ( + "os" + "path/filepath" + "testing" + + "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/lucassabreu/clockify-cli/pkg/cmdutil" +) + +func TestSaveUsesOwnerOnlyPermissions(t *testing.T) { + home := t.TempDir() + t.Setenv("HOME", home) + t.Cleanup(homedir.Reset) + homedir.Reset() + t.Cleanup(func() { viper.SetConfigFile("") }) + viper.SetConfigFile("") + + config := cmdutil.NewFactory(cmdutil.Version{}).Config() + config.SetString(cmdutil.CONF_TOKEN, "a token") + require.NoError(t, config.Save()) + + dir := filepath.Join(home, ".config", "clockify-cli") + filename := filepath.Join(dir, ".clockify-cli.yaml") + + dirInfo, err := os.Stat(dir) + require.NoError(t, err) + assert.Equal(t, os.FileMode(0o700), dirInfo.Mode().Perm()) + + fileInfo, err := os.Stat(filename) + require.NoError(t, err) + assert.Equal(t, os.FileMode(0o600), fileInfo.Mode().Perm()) + + // a file left world-readable by an older version gets tightened on save + require.NoError(t, os.Chmod(filename, 0o644)) + require.NoError(t, config.Save()) + + fileInfo, err = os.Stat(filename) + require.NoError(t, err) + assert.Equal(t, os.FileMode(0o600), fileInfo.Mode().Perm()) +}