diff --git a/internal/builder/builder.go b/internal/builder/builder.go index 0ca481d..9894c3e 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log/slog" + "regexp" "sort" "sync" "time" @@ -13,6 +14,10 @@ import ( "git.t-juice.club/torjus/homelab-deploy/internal/nats" ) +// hostnameRegex validates hostnames from flake output. +// Allows: alphanumeric, dashes, underscores, dots. +var hostnameRegex = regexp.MustCompile(`^[a-zA-Z0-9._-]+$`) + // BuilderConfig holds the configuration for the builder. type BuilderConfig struct { NATSUrl string @@ -197,6 +202,16 @@ func (b *Builder) handleBuildRequest(subject string, data []byte) { } return } + // Filter out hostnames with invalid characters (security: prevent injection) + validHosts := make([]string, 0, len(hosts)) + for _, host := range hosts { + if hostnameRegex.MatchString(host) { + validHosts = append(validHosts, host) + } else { + b.logger.Warn("skipping hostname with invalid characters", "hostname", host) + } + } + hosts = validHosts // Sort hosts for consistent ordering sort.Strings(hosts) } else { diff --git a/internal/messages/build.go b/internal/messages/build.go index 2dbf43f..82749a2 100644 --- a/internal/messages/build.go +++ b/internal/messages/build.go @@ -45,6 +45,10 @@ func (r *BuildRequest) Validate() error { if r.Target == "" { return fmt.Errorf("target is required") } + // Target must be "all" or a valid hostname (same format as revision/branch) + if r.Target != "all" && !revisionRegex.MatchString(r.Target) { + return fmt.Errorf("invalid target format: %q", r.Target) + } if r.Branch != "" && !revisionRegex.MatchString(r.Branch) { return fmt.Errorf("invalid branch format: %q", r.Branch) }