Skip to content

Commit 92d06e0

Browse files
Copilotpelikhan
andauthored
fix: inject detection guard condition into custom steps and improve test ordering assertions
- Inject detectionStepCondition into custom threat detection steps (pre/post) unless the user already provides an if: condition. This ensures custom steps are skipped when detection_guard says run_detection is false. - Update test to verify post-step ordering relative to id: detection_agentic_execution (stable engine step marker) instead of just "Setup threat detection" - Add TestCustomThreatDetectionStepsGuardCondition to explicitly test condition injection and preservation Agent-Logs-Url: https://github.com/github/gh-aw/sessions/db33799b-37fd-4489-96df-09ea24078ed9 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
1 parent 2d47731 commit 92d06e0

2 files changed

Lines changed: 65 additions & 12 deletions

File tree

pkg/workflow/threat_detection.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package workflow
33
import (
44
"encoding/json"
55
"fmt"
6+
"maps"
67
"strings"
78

89
"github.com/github/gh-aw/pkg/constants"
@@ -567,10 +568,21 @@ await main();`
567568
}
568569

569570
// buildCustomThreatDetectionSteps builds YAML steps from user-configured threat detection steps.
571+
// It injects the detection guard condition into each step unless an explicit if: condition is
572+
// already set, ensuring custom steps only run when the detection_guard determines that detection
573+
// should proceed and preventing unexpected side effects in runs with no agent outputs to analyze.
570574
func (c *Compiler) buildCustomThreatDetectionSteps(steps []any) []string {
571575
var result []string
572576
for _, step := range steps {
573577
if stepMap, ok := step.(map[string]any); ok {
578+
// Inject the detection guard condition unless the user already provided an if: condition.
579+
if _, hasIf := stepMap["if"]; !hasIf {
580+
// Clone the map to avoid mutating the original config.
581+
injected := make(map[string]any, len(stepMap)+1)
582+
maps.Copy(injected, stepMap)
583+
injected["if"] = detectionStepCondition
584+
stepMap = injected
585+
}
574586
if stepYAML, err := ConvertStepToYAML(stepMap); err == nil {
575587
result = append(result, stepYAML)
576588
}

pkg/workflow/threat_detection_test.go

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -447,15 +447,16 @@ func TestThreatDetectionStepsOrdering(t *testing.T) {
447447
stepsString := strings.Join(steps, "")
448448

449449
postStepPos := strings.Index(stepsString, "Custom Post Scan")
450-
setupStepPos := strings.Index(stepsString, "Setup threat detection")
450+
// Use the engine execution step ID as the stable marker for the engine step boundary
451+
engineStepPos := strings.Index(stepsString, "id: detection_agentic_execution")
451452
uploadStepPos := strings.Index(stepsString, "Upload threat detection log")
452453
concludeStepPos := strings.Index(stepsString, "Parse and conclude threat detection")
453454

454455
if postStepPos == -1 {
455456
t.Error("Expected to find 'Custom Post Scan' step")
456457
}
457-
if setupStepPos == -1 {
458-
t.Error("Expected to find 'Setup threat detection' step")
458+
if engineStepPos == -1 {
459+
t.Error("Expected to find 'id: detection_agentic_execution' engine step")
459460
}
460461
if uploadStepPos == -1 {
461462
t.Error("Expected to find 'Upload threat detection log' step")
@@ -464,9 +465,9 @@ func TestThreatDetectionStepsOrdering(t *testing.T) {
464465
t.Error("Expected to find 'Parse and conclude threat detection' step")
465466
}
466467

467-
// Verify ordering: post-steps should come after setup and before upload/conclude
468-
if postStepPos < setupStepPos {
469-
t.Errorf("Custom post-steps should come after 'Setup threat detection'. Got post-step at position %d, setup at position %d", postStepPos, setupStepPos)
468+
// Verify ordering: post-steps should come after the engine execution step
469+
if postStepPos < engineStepPos {
470+
t.Errorf("Custom post-steps should come after engine execution step. Got post-step at position %d, engine at position %d", postStepPos, engineStepPos)
470471
}
471472
if postStepPos > uploadStepPos {
472473
t.Errorf("Custom post-steps should come before 'Upload threat detection log'. Got post-step at position %d, upload at position %d", postStepPos, uploadStepPos)
@@ -501,7 +502,7 @@ func TestThreatDetectionStepsOrdering(t *testing.T) {
501502

502503
preStepPos := strings.Index(stepsString, "Custom Pre Step")
503504
postStepPos := strings.Index(stepsString, "Custom Post Step")
504-
setupStepPos := strings.Index(stepsString, "Setup threat detection")
505+
engineStepPos := strings.Index(stepsString, "id: detection_agentic_execution")
505506
uploadStepPos := strings.Index(stepsString, "Upload threat detection log")
506507

507508
if preStepPos == -1 {
@@ -510,13 +511,16 @@ func TestThreatDetectionStepsOrdering(t *testing.T) {
510511
if postStepPos == -1 {
511512
t.Error("Expected to find 'Custom Post Step'")
512513
}
514+
if engineStepPos == -1 {
515+
t.Error("Expected to find 'id: detection_agentic_execution' engine step")
516+
}
513517

514-
// pre-steps before setup, post-steps after setup but before upload
515-
if preStepPos > setupStepPos {
516-
t.Errorf("Pre-steps should come before 'Setup threat detection'. Got pre=%d, setup=%d", preStepPos, setupStepPos)
518+
// pre-steps before engine, post-steps after engine but before upload
519+
if preStepPos > engineStepPos {
520+
t.Errorf("Pre-steps should come before engine execution step. Got pre=%d, engine=%d", preStepPos, engineStepPos)
517521
}
518-
if postStepPos < setupStepPos {
519-
t.Errorf("Post-steps should come after 'Setup threat detection'. Got post=%d, setup=%d", postStepPos, setupStepPos)
522+
if postStepPos < engineStepPos {
523+
t.Errorf("Post-steps should come after engine execution step. Got post=%d, engine=%d", postStepPos, engineStepPos)
520524
}
521525
if postStepPos > uploadStepPos {
522526
t.Errorf("Post-steps should come before 'Upload threat detection log'. Got post=%d, upload=%d", postStepPos, uploadStepPos)
@@ -528,6 +532,43 @@ func TestThreatDetectionStepsOrdering(t *testing.T) {
528532
})
529533
}
530534

535+
func TestCustomThreatDetectionStepsGuardCondition(t *testing.T) {
536+
compiler := NewCompiler()
537+
538+
t.Run("injects detection guard condition when no if: present", func(t *testing.T) {
539+
steps := []any{
540+
map[string]any{
541+
"name": "No If Step",
542+
"run": "echo hello",
543+
},
544+
}
545+
result := compiler.buildCustomThreatDetectionSteps(steps)
546+
stepsStr := strings.Join(result, "")
547+
if !strings.Contains(stepsStr, detectionStepCondition) {
548+
t.Errorf("Expected detection guard condition to be injected, got:\n%s", stepsStr)
549+
}
550+
})
551+
552+
t.Run("preserves user-provided if: condition", func(t *testing.T) {
553+
userCondition := "always()"
554+
steps := []any{
555+
map[string]any{
556+
"name": "User If Step",
557+
"if": userCondition,
558+
"run": "echo hello",
559+
},
560+
}
561+
result := compiler.buildCustomThreatDetectionSteps(steps)
562+
stepsStr := strings.Join(result, "")
563+
if strings.Contains(stepsStr, detectionStepCondition) {
564+
t.Error("Expected detection guard condition NOT to be injected when user provides if:")
565+
}
566+
if !strings.Contains(stepsStr, userCondition) {
567+
t.Errorf("Expected user if: condition %q to be preserved, got:\n%s", userCondition, stepsStr)
568+
}
569+
})
570+
}
571+
531572
func TestBuildDetectionEngineExecutionStepWithThreatDetectionEngine(t *testing.T) {
532573
compiler := NewCompiler()
533574

0 commit comments

Comments
 (0)