Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions pkg/cli/compile_config.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package cli

import (
"regexp"
)

// CompileConfig holds configuration options for compiling workflows
type CompileConfig struct {
MarkdownFiles []string // Files to compile (empty for all files)
Expand Down Expand Up @@ -55,3 +59,104 @@ type ValidationResult struct {
Warnings []ValidationError `json:"warnings"`
CompiledFile string `json:"compiled_file,omitempty"`
}

// Regex patterns for detecting potential secret key names
var (
// Match uppercase snake_case identifiers that look like secret names (e.g., MY_SECRET_KEY, GITHUB_TOKEN, API_KEY)
// Excludes common workflow-related keywords
secretNamePattern = regexp.MustCompile(`\b([A-Z][A-Z0-9]*_[A-Z0-9_]+)\b`)

// Match PascalCase identifiers ending with security-related suffixes (e.g., GitHubToken, ApiKey, DeploySecret)
pascalCaseSecretPattern = regexp.MustCompile(`\b([A-Z][a-z0-9]*(?:[A-Z][a-z0-9]*)*(?:Token|Key|Secret|Password|Credential|Auth))\b`)

// Common non-sensitive workflow keywords to exclude from redaction
commonWorkflowKeywords = map[string]bool{
"GITHUB": true,
"ACTIONS": true,
"WORKFLOW": true,
"RUNNER": true,
"JOB": true,
"STEP": true,
"MATRIX": true,
"ENV": true,
"PATH": true,
"HOME": true,
"SHELL": true,
"INPUTS": true,
"OUTPUTS": true,
"NEEDS": true,
"STRATEGY": true,
"CONCURRENCY": true,
"IF": true,
"WITH": true,
"USES": true,
"RUN": true,
"WORKING_DIRECTORY": true,
"CONTINUE_ON_ERROR": true,
"TIMEOUT_MINUTES": true,
}
)

// sanitizeErrorMessage removes potential secret key names from error messages to prevent
// information disclosure via logs. This prevents exposing details about an organization's
// security infrastructure by redacting secret key names that might appear in error messages.
func sanitizeErrorMessage(message string) string {
if message == "" {
return message
}

// Redact uppercase snake_case patterns (e.g., MY_SECRET_KEY, API_TOKEN)
sanitized := secretNamePattern.ReplaceAllStringFunc(message, func(match string) string {
// Don't redact common workflow keywords
if commonWorkflowKeywords[match] {
return match
}
return "[REDACTED]"
})

// Redact PascalCase patterns ending with security suffixes (e.g., GitHubToken, ApiKey)
sanitized = pascalCaseSecretPattern.ReplaceAllString(sanitized, "[REDACTED]")

return sanitized
}

// sanitizeValidationResults creates a sanitized copy of validation results with all
// error and warning messages sanitized to remove potential secret key names.
// This is applied at the JSON output boundary to ensure no sensitive information
// is leaked regardless of where error messages originated.
func sanitizeValidationResults(results []ValidationResult) []ValidationResult {
if results == nil {
return nil
}

sanitized := make([]ValidationResult, len(results))
for i, result := range results {
sanitized[i] = ValidationResult{
Workflow: result.Workflow,
Valid: result.Valid,
CompiledFile: result.CompiledFile,
Errors: make([]ValidationError, len(result.Errors)),
Warnings: make([]ValidationError, len(result.Warnings)),
}

// Sanitize all error messages
for j, err := range result.Errors {
sanitized[i].Errors[j] = ValidationError{
Type: err.Type,
Message: sanitizeErrorMessage(err.Message),
Line: err.Line,
}
}

// Sanitize all warning messages
for j, warn := range result.Warnings {
sanitized[i].Warnings[j] = ValidationError{
Type: warn.Type,
Message: sanitizeErrorMessage(warn.Message),
Line: warn.Line,
}
}
}

return sanitized
}
10 changes: 8 additions & 2 deletions pkg/cli/compile_orchestrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,10 @@ func CompileWorkflows(config CompileConfig) ([]*workflow.WorkflowData, error) {

// Output JSON if requested
if jsonOutput {
jsonBytes, err := json.MarshalIndent(validationResults, "", " ")
// Sanitize validation results before JSON marshaling to prevent logging of sensitive information
// This removes potential secret key names from error messages at the output boundary
sanitizedResults := sanitizeValidationResults(validationResults)
jsonBytes, err := json.MarshalIndent(sanitizedResults, "", " ")
if err != nil {
return workflowDataList, fmt.Errorf("failed to marshal JSON: %w", err)
}
Expand Down Expand Up @@ -980,7 +983,10 @@ func CompileWorkflows(config CompileConfig) ([]*workflow.WorkflowData, error) {

// Output JSON if requested
if jsonOutput {
jsonBytes, err := json.MarshalIndent(validationResults, "", " ")
// Sanitize validation results before JSON marshaling to prevent logging of sensitive information
// This removes potential secret key names from error messages at the output boundary
sanitizedResults := sanitizeValidationResults(validationResults)
jsonBytes, err := json.MarshalIndent(sanitizedResults, "", " ")
if err != nil {
return workflowDataList, fmt.Errorf("failed to marshal JSON: %w", err)
}
Expand Down