Skip to content

Commit da914d5

Browse files
authored
fix: improve YAML formatting and resolve gosec lint warnings (#219)
* fix: use IndentSequence for properly formatted YAML output * fix: resolve gosec lint warnings (G122, G703, G118)
1 parent c19ea11 commit da914d5

8 files changed

Lines changed: 35 additions & 16 deletions

File tree

internal/commands/discover/workflow.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ func marshalConfigWithComments(cfg *config.Config, plugins []string) ([]byte, er
616616

617617
// marshalToYAML marshals a config to YAML bytes.
618618
func marshalToYAML(cfg *config.Config) ([]byte, error) {
619-
return yaml.Marshal(cfg)
619+
return yaml.MarshalWithOptions(cfg, yaml.Indent(2), yaml.IndentSequence(true))
620620
}
621621

622622
// generateDependencyCheckConfig generates YAML configuration for dependency-check.

internal/commands/initialize/config_generator.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,8 @@ func GenerateConfigWithComments(path string, selectedPlugins []string) ([]byte,
5858

5959
cfg.Plugins = pluginsCfg
6060

61-
// Marshal to YAML
62-
data, err := yaml.Marshal(cfg)
61+
// Marshal to YAML with proper indentation
62+
data, err := yaml.MarshalWithOptions(cfg, yaml.Indent(2), yaml.IndentSequence(true))
6363
if err != nil {
6464
return nil, fmt.Errorf("failed to marshal config: %w", err)
6565
}
@@ -148,8 +148,8 @@ func GenerateConfigWithDiscovery(path string, selectedPlugins []string, syncCand
148148

149149
cfg.Plugins = pluginsCfg
150150

151-
// Marshal to YAML
152-
data, err := yaml.Marshal(cfg)
151+
// Marshal to YAML with proper indentation
152+
data, err := yaml.MarshalWithOptions(cfg, yaml.Indent(2), yaml.IndentSequence(true))
153153
if err != nil {
154154
return nil, fmt.Errorf("failed to marshal config: %w", err)
155155
}

internal/commands/initialize/workspace.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package initialize
22

33
import (
44
"fmt"
5+
"io"
56
"os"
67
"path/filepath"
78
"strings"
@@ -68,7 +69,20 @@ func discoverVersionFiles(root string) ([]DiscoveredModule, error) {
6869
"__pycache__": true,
6970
}
7071

71-
err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
72+
// Resolve the scan directory: if root is a file path, use its parent directory.
73+
scanDir := root
74+
if info, statErr := os.Stat(root); statErr != nil || !info.IsDir() {
75+
scanDir = filepath.Dir(root)
76+
}
77+
78+
// Use root-scoped API to prevent symlink TOCTOU traversal (gosec G122).
79+
rootDir, err := os.OpenRoot(scanDir)
80+
if err != nil {
81+
return nil, fmt.Errorf("failed to open root directory: %w", err)
82+
}
83+
defer rootDir.Close()
84+
85+
err = filepath.WalkDir(scanDir, func(path string, d os.DirEntry, err error) error {
7286
if err != nil {
7387
return nil // Skip inaccessible directories
7488
}
@@ -85,17 +99,20 @@ func discoverVersionFiles(root string) ([]DiscoveredModule, error) {
8599
if d.Name() == ".version" {
86100
// Skip root .version file
87101
dir := filepath.Dir(path)
88-
if dir == "." || dir == root {
102+
if dir == "." || dir == scanDir {
89103
return nil
90104
}
91105

92-
relPath, _ := filepath.Rel(root, path)
106+
relPath, _ := filepath.Rel(scanDir, path)
93107
moduleName := filepath.Base(dir)
94108

95-
// Read current version
109+
// Read current version using root-scoped file access
96110
version := ""
97-
if data, err := os.ReadFile(path); err == nil {
98-
version = strings.TrimSpace(string(data))
111+
if f, openErr := rootDir.Open(relPath); openErr == nil {
112+
if data, readErr := io.ReadAll(f); readErr == nil {
113+
version = strings.TrimSpace(string(data))
114+
}
115+
f.Close()
99116
}
100117

101118
modules = append(modules, DiscoveredModule{

internal/config/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ func (w *osFileWriter) WriteFile(file *os.File, data []byte) (int, error) {
7777
type yamlMarshaler struct{}
7878

7979
func (m *yamlMarshaler) Marshal(v any) ([]byte, error) {
80-
return yaml.Marshal(v)
80+
return yaml.MarshalWithOptions(v, yaml.Indent(2), yaml.IndentSequence(true))
8181
}
8282

8383
// NewConfigSaver creates a ConfigSaver with the given dependencies.

internal/extensionmgr/error_recovery_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ echo '{"version": "1.0.0"}'
112112

113113
executor := NewScriptExecutorWithTimeout(30 * time.Second)
114114
ctx, cancel := context.WithCancel(context.Background())
115+
defer cancel()
115116

116117
input := &HookInput{
117118
Hook: "test",

internal/extensionmgr/updater.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func (u *DefaultConfigUpdater) AddExtension(path string, extension config.Extens
6464
result = original + "\n" + replacement + "\n"
6565
}
6666

67-
if err := os.WriteFile(path, []byte(result), config.ConfigFilePerm); err != nil {
67+
if err := os.WriteFile(path, []byte(result), config.ConfigFilePerm); err != nil { //nolint:gosec // G703: path is the config file path resolved by the config loader
6868
return fmt.Errorf("failed to write config %q: %w", path, err)
6969
}
7070
return nil
@@ -117,7 +117,7 @@ func (u *DefaultConfigUpdater) RemoveExtension(path string, extensionName string
117117
return fmt.Errorf("extensions section not found in config file %q", path)
118118
}
119119

120-
if err := os.WriteFile(path, []byte(result), config.ConfigFilePerm); err != nil {
120+
if err := os.WriteFile(path, []byte(result), config.ConfigFilePerm); err != nil { //nolint:gosec // G703: path is the config file path resolved by the config loader
121121
return fmt.Errorf("failed to write config %q: %w", path, err)
122122
}
123123
return nil
@@ -167,7 +167,7 @@ func (u *DefaultConfigUpdater) SetExtensionEnabled(path string, extensionName st
167167
return fmt.Errorf("extensions section not found in config file %q", path)
168168
}
169169

170-
if err := os.WriteFile(path, []byte(result), config.ConfigFilePerm); err != nil {
170+
if err := os.WriteFile(path, []byte(result), config.ConfigFilePerm); err != nil { //nolint:gosec // G703: path is the config file path resolved by the config loader
171171
return fmt.Errorf("failed to write config %q: %w", path, err)
172172
}
173173
return nil

internal/plugins/changeloggenerator/generator.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ func (g *Generator) WriteUnifiedChangelog(newContent string) error {
380380
// Normalize: trim trailing whitespace and ensure single trailing newline
381381
finalContent = strings.TrimRight(finalContent, "\n\r\t ") + "\n"
382382

383-
if err := os.WriteFile(path, []byte(finalContent), core.PermPublicRead); err != nil {
383+
if err := os.WriteFile(path, []byte(finalContent), core.PermPublicRead); err != nil { //nolint:gosec // G703: path is the changelog file path from plugin config
384384
return fmt.Errorf("failed to write changelog %q: %w", path, err)
385385
}
386386

internal/workspace/executor_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ func TestExecutor_Run_ContextCancellation(t *testing.T) {
228228
}
229229

230230
ctx, cancel := context.WithCancel(context.Background())
231+
defer cancel()
231232

232233
op := &mockOperation{
233234
name: "test",

0 commit comments

Comments
 (0)