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:
+109
-24
@@ -645,19 +645,31 @@ func parseBuildHAKArgs(args []string) (buildHAKOptions, error) {
|
||||
}
|
||||
opts.sourceManifest = args[index]
|
||||
default:
|
||||
if value, ok := parseInlineFlagValue(arg, "--hak"); ok {
|
||||
if value, ok, err := requireInlineFlagValue(arg, "--hak"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.filteredHAKs = append(opts.filteredHAKs, value)
|
||||
continue
|
||||
}
|
||||
if value, ok := parseInlineFlagValue(arg, "--archive"); ok {
|
||||
if value, ok, err := requireInlineFlagValue(arg, "--archive"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.filteredArchives = append(opts.filteredArchives, value)
|
||||
continue
|
||||
}
|
||||
if value, ok := parseInlineFlagValue(arg, "--source-manifest"); ok {
|
||||
if value, ok, err := requireInlineFlagValue(arg, "--source-manifest"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.sourceManifest = value
|
||||
continue
|
||||
}
|
||||
if value, ok := parseInlineFlagValue(arg, "--music-dataset"); ok {
|
||||
if value, ok, err := requireInlineFlagValue(arg, "--music-dataset"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.musicDatasets = append(opts.musicDatasets, value)
|
||||
continue
|
||||
}
|
||||
@@ -679,6 +691,17 @@ func parseInlineFlagValue(arg, name string) (string, bool) {
|
||||
return strings.TrimSpace(strings.TrimPrefix(arg, prefix)), true
|
||||
}
|
||||
|
||||
func requireInlineFlagValue(arg, name string) (string, bool, error) {
|
||||
value, ok := parseInlineFlagValue(arg, name)
|
||||
if !ok {
|
||||
return "", false, nil
|
||||
}
|
||||
if value == "" {
|
||||
return "", true, fmt.Errorf("%s requires a value", name)
|
||||
}
|
||||
return value, true, nil
|
||||
}
|
||||
|
||||
type musicCommandOptions struct {
|
||||
datasets []string
|
||||
json bool
|
||||
@@ -765,7 +788,10 @@ func parseMusicCommandArgs(args []string) (musicCommandOptions, error) {
|
||||
case "--force":
|
||||
opts.force = true
|
||||
default:
|
||||
if value, ok := parseInlineFlagValue(arg, "--dataset"); ok {
|
||||
if value, ok, err := requireInlineFlagValue(arg, "--dataset"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.datasets = append(opts.datasets, value)
|
||||
continue
|
||||
}
|
||||
@@ -1417,29 +1443,56 @@ func parseDeployWikiArgs(commandName string, args []string) (topdata.DeployWikiO
|
||||
case "-h", "--help":
|
||||
return opts, fmt.Errorf("usage: %s [--source-dir <path>] [--endpoint <url>] [--token <token>] [--version <ver>] [--namespace <ns> ...] [--category <namespace=cid> ...] [--manifest <path>] [--dry-run] [--create] [--force]", commandName)
|
||||
default:
|
||||
if strings.HasPrefix(arg, "--source-dir=") {
|
||||
opts.SourceDir = strings.TrimPrefix(arg, "--source-dir=")
|
||||
} else if strings.HasPrefix(arg, "--endpoint=") {
|
||||
opts.Endpoint = strings.TrimPrefix(arg, "--endpoint=")
|
||||
} else if strings.HasPrefix(arg, "--username=") {
|
||||
opts.Username = strings.TrimPrefix(arg, "--username=")
|
||||
} else if strings.HasPrefix(arg, "--password=") {
|
||||
opts.Password = strings.TrimPrefix(arg, "--password=")
|
||||
} else if strings.HasPrefix(arg, "--token=") {
|
||||
opts.Token = strings.TrimPrefix(arg, "--token=")
|
||||
} else if strings.HasPrefix(arg, "--version=") {
|
||||
opts.Version = strings.TrimPrefix(arg, "--version=")
|
||||
} else if strings.HasPrefix(arg, "--namespace=") {
|
||||
opts.Namespaces = append(opts.Namespaces, strings.TrimPrefix(arg, "--namespace="))
|
||||
} else if strings.HasPrefix(arg, "--category=") {
|
||||
if value, ok, err := requireInlineFlagValue(arg, "--source-dir"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.SourceDir = value
|
||||
} else if value, ok, err := requireInlineFlagValue(arg, "--endpoint"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.Endpoint = value
|
||||
} else if value, ok, err := requireInlineFlagValue(arg, "--username"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.Username = value
|
||||
} else if value, ok, err := requireInlineFlagValue(arg, "--password"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.Password = value
|
||||
} else if value, ok, err := requireInlineFlagValue(arg, "--token"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.Token = value
|
||||
} else if value, ok, err := requireInlineFlagValue(arg, "--version"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.Version = value
|
||||
} else if value, ok, err := requireInlineFlagValue(arg, "--namespace"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.Namespaces = append(opts.Namespaces, value)
|
||||
} else if value, ok, err := requireInlineFlagValue(arg, "--category"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
if opts.CategoryIDs == nil {
|
||||
opts.CategoryIDs = map[string]int{}
|
||||
}
|
||||
if err := parseWikiCategoryArg(strings.TrimPrefix(arg, "--category="), opts.CategoryIDs); err != nil {
|
||||
if err := parseWikiCategoryArg(value, opts.CategoryIDs); err != nil {
|
||||
return opts, err
|
||||
}
|
||||
} else if strings.HasPrefix(arg, "--manifest=") {
|
||||
opts.ManifestPath = strings.TrimPrefix(arg, "--manifest=")
|
||||
} else if value, ok, err := requireInlineFlagValue(arg, "--manifest"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.ManifestPath = value
|
||||
} else if arg == "--dry-run" {
|
||||
opts.DryRun = true
|
||||
} else if arg == "--force" {
|
||||
@@ -1546,7 +1599,39 @@ func parseBuildChangelogArgs(commandName string, args []string) (buildChangelogO
|
||||
case "-h", "--help":
|
||||
return opts, usage
|
||||
default:
|
||||
return opts, fmt.Errorf("unknown %s argument %q", commandName, arg)
|
||||
if value, ok, err := requireInlineFlagValue(arg, "--config"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.configPath = value
|
||||
} else if value, ok, err := requireInlineFlagValue(arg, "--output"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.outputPath = value
|
||||
} else if value, ok, err := requireInlineFlagValue(arg, "--current-tag"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.currentTag = value
|
||||
} else if value, ok, err := requireInlineFlagValue(arg, "--previous-tag"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.previousTag = value
|
||||
} else if value, ok, err := requireInlineFlagValue(arg, "--api-base-url"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.apiBaseURL = value
|
||||
} else if value, ok, err := requireInlineFlagValue(arg, "--token"); ok || err != nil {
|
||||
if err != nil {
|
||||
return opts, err
|
||||
}
|
||||
opts.token = value
|
||||
} else {
|
||||
return opts, fmt.Errorf("unknown %s argument %q", commandName, arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -391,6 +391,43 @@ paths:
|
||||
}
|
||||
}
|
||||
|
||||
func TestInlineFlagParsersRejectEmptyValues(t *testing.T) {
|
||||
if _, err := parseBuildHAKArgs([]string{"--hak="}); err == nil || !strings.Contains(err.Error(), "--hak requires a value") {
|
||||
t.Fatalf("expected empty --hak inline value error, got %v", err)
|
||||
}
|
||||
if _, err := parseMusicCommandArgs([]string{"--dataset="}); err == nil || !strings.Contains(err.Error(), "--dataset requires a value") {
|
||||
t.Fatalf("expected empty --dataset inline value error, got %v", err)
|
||||
}
|
||||
if _, err := parseDeployWikiArgs("deploy-wiki", []string{"--endpoint="}); err == nil || !strings.Contains(err.Error(), "--endpoint requires a value") {
|
||||
t.Fatalf("expected empty --endpoint inline value error, got %v", err)
|
||||
}
|
||||
if _, err := parseBuildChangelogArgs("build-changelog", []string{"--output="}); err == nil || !strings.Contains(err.Error(), "--output requires a value") {
|
||||
t.Fatalf("expected empty --output inline value error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseBuildChangelogArgsAcceptsInlineValues(t *testing.T) {
|
||||
opts, err := parseBuildChangelogArgs("build-changelog", []string{
|
||||
"--config=scripts/changelog.json",
|
||||
"--output=CHANGELOG.md",
|
||||
"--current-tag=v2",
|
||||
"--previous-tag=v1",
|
||||
"--api-base-url=https://gitea.example/api/v1",
|
||||
"--token=secret",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("parseBuildChangelogArgs failed: %v", err)
|
||||
}
|
||||
if opts.configPath != "scripts/changelog.json" ||
|
||||
opts.outputPath != "CHANGELOG.md" ||
|
||||
opts.currentTag != "v2" ||
|
||||
opts.previousTag != "v1" ||
|
||||
opts.apiBaseURL != "https://gitea.example/api/v1" ||
|
||||
opts.token != "secret" {
|
||||
t.Fatalf("unexpected changelog opts: %#v", opts)
|
||||
}
|
||||
}
|
||||
|
||||
func mkdirAll(t *testing.T, path string) {
|
||||
t.Helper()
|
||||
if err := os.MkdirAll(path, 0o755); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user