Skip to content

Commit c59eafe

Browse files
intel352claude
andcommitted
fix(wfctl): validate env names in CI gen; fix secret store priority order
- ci_init.go: add safeEnvNameRe guard to skip env names with unsafe characters that could inject shell commands or corrupt generated YAML - secrets_setup.go: reorder resolveSecretStoreForSetup priority so per-secret store field takes precedence over environment override, matching runtime ResolveSecretStore behavior Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 6b5d998 commit c59eafe

2 files changed

Lines changed: 29 additions & 6 deletions

File tree

cmd/wfctl/ci_init.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@ import (
44
"flag"
55
"fmt"
66
"os"
7+
"regexp"
78
"strings"
89

910
"github.com/GoCodeAlone/workflow/config"
1011
"gopkg.in/yaml.v3"
1112
)
1213

14+
// safeEnvNameRe matches environment names that are safe to embed in shell run
15+
// commands and YAML values. Only alphanumerics, hyphens, underscores, and dots
16+
// are permitted; anything else could inject commands or corrupt the YAML.
17+
var safeEnvNameRe = regexp.MustCompile(`^[a-zA-Z0-9_.\-]+$`)
18+
1319
func runCIInit(args []string) error {
1420
fs := flag.NewFlagSet("ci init", flag.ContinueOnError)
1521
platform := fs.String("platform", "github-actions", "CI platform: github-actions, gitlab-ci")
@@ -98,6 +104,11 @@ func generateGHABootstrap(cfg *config.WorkflowConfig) string {
98104
if env == nil {
99105
continue
100106
}
107+
if !safeEnvNameRe.MatchString(envName) {
108+
// Skip environments whose names would corrupt the generated YAML or
109+
// inject shell commands. Names must be [a-zA-Z0-9_.-] only.
110+
continue
111+
}
101112
jobName := "deploy-" + strings.ReplaceAll(envName, ".", "-")
102113
sb.WriteString(" " + jobName + ":\n")
103114
sb.WriteString(" runs-on: ubuntu-latest\n")
@@ -125,6 +136,9 @@ func generateGitLabCIBootstrap(cfg *config.WorkflowConfig) string {
125136
// Add deploy stages for each environment.
126137
if cfg != nil && cfg.CI != nil && cfg.CI.Deploy != nil {
127138
for envName := range cfg.CI.Deploy.Environments {
139+
if !safeEnvNameRe.MatchString(envName) {
140+
continue
141+
}
128142
sb.WriteString(" - deploy-" + strings.ReplaceAll(envName, ".", "-") + "\n")
129143
}
130144
}
@@ -152,6 +166,9 @@ func generateGitLabCIBootstrap(cfg *config.WorkflowConfig) string {
152166
if env == nil {
153167
continue
154168
}
169+
if !safeEnvNameRe.MatchString(envName) {
170+
continue
171+
}
155172
stageID := strings.ReplaceAll(envName, ".", "-")
156173
sb.WriteString("\ndeploy-" + stageID + ":\n")
157174
sb.WriteString(" stage: deploy-" + stageID + "\n")

cmd/wfctl/secrets_setup.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ Options:
7474
// Auto-generate for key/secret/token names if flag is set.
7575
if *autoGenKeys && existing == "" && isAutoGenCandidate(entry.Name) {
7676
val := generateSecretValue()
77+
if val == "" {
78+
fmt.Printf(" %-24s [ERROR] crypto/rand unavailable; cannot auto-generate\n", entry.Name)
79+
skipped++
80+
continue
81+
}
7782
if setErr := provider.Set(ctx, entry.Name, val); setErr != nil {
7883
fmt.Printf(" %-24s [ERROR] %v\n", entry.Name, setErr)
7984
} else {
@@ -119,18 +124,19 @@ Options:
119124
}
120125

121126
// resolveSecretStoreForSetup determines which store to use for a secret in a given environment.
122-
// Priority: environment override → per-secret store field → defaultStore → legacy provider → "env".
127+
// Priority: per-secret store field → environment override → defaultStore → legacy provider → "env".
128+
// This matches the order in ResolveSecretStore so that setup and runtime agree on which store owns a secret.
123129
func resolveSecretStoreForSetup(entry config.SecretEntry, envName string, cfg *config.WorkflowConfig) string {
124-
// 1. Environment-level store override.
130+
// 1. Per-secret store field (highest priority).
131+
if entry.Store != "" {
132+
return entry.Store
133+
}
134+
// 2. Environment-level store override.
125135
if cfg.Environments != nil {
126136
if env, ok := cfg.Environments[envName]; ok && env.SecretsProvider != "" {
127137
return env.SecretsProvider
128138
}
129139
}
130-
// 2. Per-secret store field.
131-
if entry.Store != "" {
132-
return entry.Store
133-
}
134140
// 3. Default store from secretStores config.
135141
if cfg.Secrets != nil && cfg.Secrets.DefaultStore != "" {
136142
return cfg.Secrets.DefaultStore

0 commit comments

Comments
 (0)