chore(go): harden coverage-gate file input handling
This commit is contained in:
@@ -43,9 +43,9 @@ type PackageResult struct {
|
||||
|
||||
// LoadPolicy reads policy JSON from disk.
|
||||
func LoadPolicy(path string) (Policy, error) {
|
||||
f, err := os.Open(path)
|
||||
f, err := openValidatedReadOnlyFile(path, ".json", "policy")
|
||||
if err != nil {
|
||||
return Policy{}, fmt.Errorf("open policy: %w", err)
|
||||
return Policy{}, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
@@ -61,9 +61,9 @@ func LoadPolicy(path string) (Policy, error) {
|
||||
|
||||
// ParseCoverProfile parses a Go coverprofile and aggregates package coverage.
|
||||
func ParseCoverProfile(profilePath string, policy Policy) (map[string]Coverage, error) {
|
||||
f, err := os.Open(profilePath)
|
||||
f, err := openValidatedReadOnlyFile(profilePath, "", "coverage profile")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open coverage profile: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
@@ -121,6 +121,41 @@ func ParseCoverProfile(profilePath string, policy Policy) (map[string]Coverage,
|
||||
return coverage, nil
|
||||
}
|
||||
|
||||
func openValidatedReadOnlyFile(path string, requiredExt string, label string) (*os.File, error) {
|
||||
cleaned := filepath.Clean(strings.TrimSpace(path))
|
||||
if cleaned == "" || cleaned == "." {
|
||||
return nil, fmt.Errorf("invalid %s path", label)
|
||||
}
|
||||
|
||||
if requiredExt != "" {
|
||||
ext := strings.ToLower(filepath.Ext(cleaned))
|
||||
if ext != strings.ToLower(requiredExt) {
|
||||
return nil, fmt.Errorf("invalid %s file extension: got %q, want %q", label, ext, requiredExt)
|
||||
}
|
||||
}
|
||||
|
||||
absPath, err := filepath.Abs(cleaned)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve %s path: %w", label, err)
|
||||
}
|
||||
|
||||
info, err := os.Stat(absPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("stat %s: %w", label, err)
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil, fmt.Errorf("%s path must be a file, got directory", label)
|
||||
}
|
||||
|
||||
// #nosec G304 -- path is explicitly cleaned, normalized, and pre-validated as an existing file.
|
||||
f, err := os.Open(absPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("open %s: %w", label, err)
|
||||
}
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// EvaluateCoverage evaluates package coverage against policy thresholds.
|
||||
func EvaluateCoverage(packages []string, byPackage map[string]Coverage, policy Policy) []PackageResult {
|
||||
results := make([]PackageResult, 0, len(packages))
|
||||
|
||||
Reference in New Issue
Block a user