Hardening audit

Key hardening changes:

  - Config validation now rejects paths.source: . and paths.assets: ., so source/asset roots cannot
    resolve to the repository root.
  - apply-hak-manifest now refuses to run when paths.source is unset or unsafe, instead of potentially
    writing under a root-level module/.
  - Inline flags across utility parsers now reject empty values consistently, e.g. --dataset=, --hak=,
    --endpoint=, --output=.
  - build-changelog now supports --flag=value inline syntax like the other utilities.
This commit is contained in:
2026-05-08 00:31:17 +02:00
parent 9f16aa8872
commit fa4c9116ae
8 changed files with 314 additions and 28 deletions
+14
View File
@@ -470,6 +470,8 @@ func (p *Project) ValidateLayout() error {
failures = append(failures, validateValidationConfig(p.Config.Validation)...)
effective := p.EffectiveConfig()
failures = append(failures, validateRelativePath("paths.cache", effective.Paths.Cache)...)
failures = append(failures, validateTreeRootPath("paths.source", effective.Paths.Source)...)
failures = append(failures, validateTreeRootPath("paths.assets", effective.Paths.Assets)...)
failures = append(failures, validateRelativePath("outputs.module_archive", effective.Outputs.ModuleArchive)...)
failures = append(failures, validateRelativePath("outputs.hak_manifest", effective.Outputs.HAKManifest)...)
failures = append(failures, validateRelativePath("outputs.hak_archive", effective.Outputs.HAKArchive)...)
@@ -1406,6 +1408,18 @@ func validateRelativePath(field, path string) []error {
return failures
}
func validateTreeRootPath(field, path string) []error {
failures := validateRelativePath(field, path)
trimmed := strings.TrimSpace(path)
if trimmed == "" {
return failures
}
if filepath.Clean(filepath.FromSlash(trimmed)) == "." {
failures = append(failures, fmt.Errorf("%s must not resolve to the repository root: %q", field, path))
}
return failures
}
func sameCleanPath(a, b string) bool {
return filepath.Clean(a) == filepath.Clean(b)
}
+24
View File
@@ -648,6 +648,30 @@ func TestValidateLayoutRejectsUnsafeGeneratedOutputPaths(t *testing.T) {
}
}
func TestValidateLayoutRejectsRootSourceAndAssetPaths(t *testing.T) {
root := t.TempDir()
mkdirAll(t, filepath.Join(root, "build"))
proj := &Project{
Root: root,
Config: Config{
Module: ModuleConfig{Name: "Test", ResRef: "test"},
Paths: PathConfig{Source: ".", Assets: ".", Build: "build"},
},
}
err := proj.ValidateLayout()
if err == nil {
t.Fatal("expected root source and asset path validation error")
}
if !strings.Contains(err.Error(), "paths.source") {
t.Fatalf("expected source path validation error, got %v", err)
}
if !strings.Contains(err.Error(), "paths.assets") {
t.Fatalf("expected assets path validation error, got %v", err)
}
}
func TestValidateLayoutRejectsInvalidAutogenConsumerConfig(t *testing.T) {
root := t.TempDir()
mkdirAll(t, filepath.Join(root, "src"))