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
4 changes: 1 addition & 3 deletions pkg/workflow/claude_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -389,9 +389,7 @@ func (e *ClaudeEngine) computeAllowedClaudeToolsString(tools map[string]any, saf
// Sort the allowed tools alphabetically for consistent output
sort.Strings(allowedTools)

if log.Enabled() {
claudeToolsLog.Printf("Generated allowed tools string with %d tools", len(allowedTools))
}
claudeToolsLog.Printf("Generated allowed tools string with %d tools", len(allowedTools))

return strings.Join(allowedTools, ",")
}
Expand Down
4 changes: 1 addition & 3 deletions pkg/workflow/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -517,9 +517,7 @@ func (c *Compiler) CompileWorkflowData(workflowData *WorkflowData, markdownPath
// Track compilation time for performance monitoring
startTime := time.Now()
defer func() {
if log.Enabled() {
log.Printf("Compilation completed in %v", time.Since(startTime))
}
log.Printf("Compilation completed in %v", time.Since(startTime))
}()

// Reset the step order tracker for this compilation
Expand Down
7 changes: 7 additions & 0 deletions pkg/workflow/concurrency_validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func validateConcurrencyGroupExpression(group string) error {

// validateBalancedBraces checks that all ${{ }} braces are balanced and properly closed
func validateBalancedBraces(group string) error {
concurrencyValidationLog.Print("Checking balanced braces in expression")
openCount := 0
i := 0
positions := []int{} // Track positions of opening braces for error reporting
Expand Down Expand Up @@ -111,6 +112,7 @@ func validateBalancedBraces(group string) error {
if openCount > 0 {
// Find the position of the first unclosed opening brace
pos := positions[0]
concurrencyValidationLog.Printf("Found %d unclosed brace(s) starting at position %d", openCount, pos)
return NewValidationError(
"concurrency",
"unclosed expression braces",
Expand All @@ -119,6 +121,7 @@ func validateBalancedBraces(group string) error {
)
}

concurrencyValidationLog.Print("Brace balance check passed")
return nil
}

Expand All @@ -128,6 +131,8 @@ func validateExpressionSyntax(group string) error {
expressionPattern := regexp.MustCompile(`\$\{\{([^}]*)\}\}`)
matches := expressionPattern.FindAllStringSubmatch(group, -1)

concurrencyValidationLog.Printf("Found %d expression(s) to validate", len(matches))

for _, match := range matches {
if len(match) < 2 {
continue
Expand Down Expand Up @@ -189,7 +194,9 @@ func validateExpressionContent(expr string, fullGroup string) error {

// Try to parse complex expressions with logical operators
if containsLogicalOperators(expr) {
concurrencyValidationLog.Print("Expression contains logical operators, performing deep validation")
if _, err := ParseExpression(expr); err != nil {
concurrencyValidationLog.Printf("Expression parsing failed: %v", err)
return NewValidationError(
"concurrency",
"invalid expression syntax",
Expand Down
4 changes: 4 additions & 0 deletions pkg/workflow/expression_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func BuildOr(left ConditionNode, right ConditionNode) ConditionNode {

// BuildAnd creates an AND node combining two conditions
func BuildAnd(left ConditionNode, right ConditionNode) ConditionNode {
expressionBuilderLog.Print("Building AND condition node")
return &AndNode{Left: left, Right: right}
}

Expand All @@ -81,6 +82,8 @@ func BuildReactionCondition() ConditionNode {
}
terms = append(terms, pullRequestCondition)

expressionBuilderLog.Printf("Created disjunction with %d event type terms", len(terms))

// Use DisjunctionNode to avoid deep nesting
return &DisjunctionNode{Terms: terms}
}
Expand Down Expand Up @@ -169,6 +172,7 @@ func BuildNotFromFork() *ComparisonNode {
}

func BuildSafeOutputType(outputType string) ConditionNode {
expressionBuilderLog.Printf("Building safe-output condition for output type: %s", outputType)
// Use !cancelled() && needs.agent.result != 'skipped' to properly handle workflow cancellation
// !cancelled() allows jobs to run when dependencies fail (for error reporting)
// needs.agent.result != 'skipped' prevents running when workflow is cancelled (dependencies get skipped)
Expand Down
22 changes: 21 additions & 1 deletion pkg/workflow/markdown_security_scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,15 @@ func (f SecurityFinding) String() string {
return fmt.Sprintf("[%s] %s", f.Category, f.Description)
}

// countCategories counts unique security finding categories
func countCategories(findings []SecurityFinding) int {
categories := make(map[SecurityFindingCategory]bool)
for _, f := range findings {
categories[f.Category] = true
}
return len(categories)
}

// ScanMarkdownSecurity scans markdown content for dangerous or malicious patterns.
// It automatically strips YAML frontmatter (delimited by ---) so that only the
// markdown body is scanned. Line numbers in returned findings are adjusted to
Expand All @@ -78,14 +87,21 @@ func ScanMarkdownSecurity(content string) []SecurityFinding {

// Strip frontmatter and get the line offset for correct line number reporting
markdownBody, lineOffset := stripFrontmatter(content)
markdownSecurityLog.Printf("Stripped frontmatter: %d line(s) removed, scanning %d bytes of markdown", lineOffset, len(markdownBody))

var findings []SecurityFinding

markdownSecurityLog.Print("Running unicode abuse detection")
findings = append(findings, scanUnicodeAbuse(markdownBody)...)
markdownSecurityLog.Print("Running hidden content detection")
findings = append(findings, scanHiddenContent(markdownBody)...)
markdownSecurityLog.Print("Running obfuscated links detection")
findings = append(findings, scanObfuscatedLinks(markdownBody)...)
markdownSecurityLog.Print("Running HTML abuse detection")
findings = append(findings, scanHTMLAbuse(markdownBody)...)
markdownSecurityLog.Print("Running embedded files detection")
findings = append(findings, scanEmbeddedFiles(markdownBody)...)
markdownSecurityLog.Print("Running social engineering detection")
findings = append(findings, scanSocialEngineering(markdownBody)...)

// Adjust line numbers to account for stripped frontmatter
Expand All @@ -98,7 +114,9 @@ func ScanMarkdownSecurity(content string) []SecurityFinding {
}

if len(findings) > 0 {
markdownSecurityLog.Printf("Found %d security issues in markdown content", len(findings))
markdownSecurityLog.Printf("Security scan complete: found %d issue(s) across %d categor(ies)", len(findings), countCategories(findings))
} else {
markdownSecurityLog.Print("Security scan complete: no issues found")
}

return findings
Expand Down Expand Up @@ -177,6 +195,8 @@ func scanUnicodeAbuse(content string) []SecurityFinding {
var findings []SecurityFinding
lines := strings.Split(content, "\n")

markdownSecurityLog.Printf("Scanning %d line(s) for unicode abuse", len(lines))

for lineNum, line := range lines {
lineNo := lineNum + 1

Expand Down
5 changes: 5 additions & 0 deletions pkg/workflow/redact_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func escapeSingleQuote(s string) string {
// CollectSecretReferences extracts all secret references from the workflow YAML
// This scans for patterns like ${{ secrets.SECRET_NAME }} or secrets.SECRET_NAME
func CollectSecretReferences(yamlContent string) []string {
secretMaskingLog.Printf("Scanning workflow YAML (%d bytes) for secret references", len(yamlContent))
secretsMap := make(map[string]bool)

// Pattern to match ${{ secrets.SECRET_NAME }} or secrets.SECRET_NAME
Expand All @@ -44,6 +45,8 @@ func CollectSecretReferences(yamlContent string) []string {
// Sort for consistent output
SortStrings(secrets)

secretMaskingLog.Printf("Found %d unique secret reference(s) in workflow", len(secrets))

return secrets
}

Expand All @@ -59,11 +62,13 @@ func (c *Compiler) generateSecretRedactionStep(yaml *strings.Builder, yamlConten
// If no secrets found, we still generate the step but it will be a no-op at runtime
// This ensures consistent step ordering and validation
if len(secretReferences) == 0 {
secretMaskingLog.Print("No secrets found, generating no-op redaction step")
// Generate a minimal no-op redaction step for validation purposes
yaml.WriteString(" - name: Redact secrets in logs\n")
yaml.WriteString(" if: always()\n")
yaml.WriteString(" run: echo 'No secrets to redact'\n")
} else {
secretMaskingLog.Printf("Generating redaction step for %d secret(s)", len(secretReferences))
yaml.WriteString(" - name: Redact secrets in logs\n")
yaml.WriteString(" if: always()\n")
fmt.Fprintf(yaml, " uses: %s\n", GetActionPin("actions/github-script"))
Expand Down
11 changes: 11 additions & 0 deletions pkg/workflow/safe_outputs_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ func (c *Compiler) extractSafeOutputsConfig(frontmatter map[string]any) *SafeOut

if output, exists := frontmatter["safe-outputs"]; exists {
if outputMap, ok := output.(map[string]any); ok {
safeOutputsConfigLog.Printf("Processing safe-outputs configuration with %d top-level keys", len(outputMap))
config = &SafeOutputsConfig{}

// Handle create-issue
issuesConfig := c.parseIssuesConfig(outputMap)
if issuesConfig != nil {
safeOutputsConfigLog.Print("Configured create-issue output handler")
config.CreateIssues = issuesConfig
}

Expand Down Expand Up @@ -89,6 +91,7 @@ func (c *Compiler) extractSafeOutputsConfig(frontmatter map[string]any) *SafeOut
// Handle create-pull-request
pullRequestsConfig := c.parsePullRequestsConfig(outputMap)
if pullRequestsConfig != nil {
safeOutputsConfigLog.Print("Configured create-pull-request output handler")
config.CreatePullRequests = pullRequestsConfig
}

Expand Down Expand Up @@ -126,6 +129,7 @@ func (c *Compiler) extractSafeOutputsConfig(frontmatter map[string]any) *SafeOut
}
}
config.AllowedDomains = domainStrings
safeOutputsConfigLog.Printf("Configured allowed-domains with %d domain(s)", len(domainStrings))
}
}

Expand Down Expand Up @@ -399,11 +403,18 @@ func (c *Compiler) extractSafeOutputsConfig(frontmatter map[string]any) *SafeOut
if outputMap, ok := output.(map[string]any); ok {
if _, exists := outputMap["threat-detection"]; !exists {
// Only apply default if threat-detection key doesn't exist
safeOutputsConfigLog.Print("Applying default threat-detection configuration")
config.ThreatDetection = &ThreatDetectionConfig{}
}
}
}
}

if config != nil {
safeOutputsConfigLog.Print("Successfully extracted safe-outputs configuration")
} else {
safeOutputsConfigLog.Print("No safe-outputs configuration found in frontmatter")
}

return config
}
Loading