Skip to content

Commit 2d47731

Browse files
Copilotpelikhan
andauthored
feat: add pre-steps and post-steps support to threat detection job
- Add `PostSteps` field to `ThreatDetectionConfig` for post-execution steps - Change `Steps` field semantics: now runs BEFORE engine execution (pre-steps) - Update `HasRunnableDetection` to check both `Steps` and `PostSteps` - Update `parseThreatDetectionConfig` to parse `post-steps` frontmatter field - Update `buildDetectionJobSteps` to place Steps before and PostSteps after engine execution - Update JSON schema to add `post-steps` field and update `steps` description - Update documentation in threat-detection.md and frontmatter-full.md - Update existing test and add new tests for pre/post step ordering Agent-Logs-Url: https://github.com/github/gh-aw/sessions/afee0692-364c-4f5e-b0d1-7add5d35cdca Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
1 parent 5ebc5b2 commit 2d47731

5 files changed

Lines changed: 271 additions & 66 deletions

File tree

docs/src/content/docs/reference/frontmatter-full.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5620,10 +5620,14 @@ safe-outputs:
56205620

56215621
# Option 2: undefined
56225622

5623-
# Array of extra job steps to run after detection
5623+
# Array of extra job steps to run before engine execution
56245624
# (optional)
56255625
steps: []
56265626

5627+
# Array of extra job steps to run after engine execution
5628+
# (optional)
5629+
post-steps: []
5630+
56275631
# Runner specification for the detection job. Overrides agent.runs-on for the
56285632
# detection job only. Defaults to agent.runs-on.
56295633
# (optional)

docs/src/content/docs/reference/threat-detection.md

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,10 @@ safe-outputs:
7777
threat-detection:
7878
enabled: true # Enable/disable detection
7979
prompt: "Focus on SQL injection" # Additional analysis instructions
80-
steps: # Custom detection steps
80+
steps: # Custom steps run before engine execution
81+
- name: Setup Security Gateway
82+
run: echo "Connecting to security gateway..."
83+
post-steps: # Custom steps run after engine execution
8184
- name: Custom Security Check
8285
run: echo "Running additional checks"
8386
```
@@ -90,7 +93,8 @@ safe-outputs:
9093
| `prompt` | string | Custom instructions appended to default detection prompt |
9194
| `engine` | string/object/false | AI engine config (`"copilot"`, full config object, or `false` for no AI) |
9295
| `runs-on` | string/array/object | Runner for the detection job (default: inherits from workflow `runs-on`) |
93-
| `steps` | array | Additional GitHub Actions steps to run after AI analysis |
96+
| `steps` | array | Additional GitHub Actions steps to run **before** AI analysis (pre-steps) |
97+
| `post-steps` | array | Additional GitHub Actions steps to run **after** AI analysis (post-steps) |
9498

9599
## AI-Based Detection (Default)
96100

@@ -186,13 +190,32 @@ safe-outputs:
186190

187191
## Custom Detection Steps
188192

189-
Add specialized security scanning tools alongside or instead of AI detection:
193+
Add specialized security scanning tools alongside or instead of AI detection. You can run steps **before** the AI engine (for setup, gateway connections, etc.) and steps **after** (for additional scanning based on AI results).
194+
195+
### Pre-Steps (`steps:`)
196+
197+
Steps defined under `steps:` run **before** the AI engine executes. Use these for setup tasks such as connecting to a private AI gateway, installing security tools, or preparing artifacts.
190198

191199
```yaml wrap
192200
safe-outputs:
193201
create-pull-request:
194202
threat-detection:
195203
steps:
204+
- name: Connect to Security Gateway
205+
run: |
206+
echo "Setting up secure connection to analysis gateway..."
207+
# Authentication and connection setup
208+
```
209+
210+
### Post-Steps (`post-steps:`)
211+
212+
Steps defined under `post-steps:` run **after** the AI engine completes its analysis. Use these for additional security scanning, reporting, or cleanup.
213+
214+
```yaml wrap
215+
safe-outputs:
216+
create-pull-request:
217+
threat-detection:
218+
post-steps:
196219
- name: Run Security Scanner
197220
run: |
198221
echo "Scanning agent output for threats..."
@@ -206,11 +229,11 @@ safe-outputs:
206229

207230
**Available Artifacts:** Custom steps have access to `/tmp/gh-aw/threat-detection/prompt.txt` (workflow prompt), `agent_output.json` (safe output items), and `aw.patch` (git patch file).
208231

209-
**Execution Order:** Download artifacts → Run AI analysis (if enabled) → Execute custom steps → Upload detection log.
232+
**Execution Order:** Download artifacts → Execute pre-steps (`steps:`) → Run AI analysis (if enabled) → Execute post-steps (`post-steps:`) → Upload detection log.
210233

211234
## Example: LlamaGuard Integration
212235

213-
Use Ollama with LlamaGuard 3 for specialized threat detection:
236+
Use Ollama with LlamaGuard 3 for specialized threat detection running after AI analysis:
214237

215238
```yaml wrap
216239
---
@@ -219,7 +242,7 @@ engine: copilot
219242
safe-outputs:
220243
create-pull-request:
221244
threat-detection:
222-
steps:
245+
post-steps:
223246
- name: Ollama LlamaGuard 3 Scan
224247
uses: actions/github-script@v8
225248
with:
@@ -261,7 +284,7 @@ safe-outputs:
261284
threat-detection:
262285
prompt: "Check for authentication bypass vulnerabilities"
263286
engine: copilot
264-
steps:
287+
post-steps:
265288
- name: Static Analysis
266289
run: |
267290
# Run static analysis tool
@@ -273,6 +296,24 @@ safe-outputs:
273296
path: /tmp/gh-aw/threat-detection/aw.patch
274297
```
275298

299+
## Example: Private AI Gateway
300+
301+
Connect to a private AI gateway before running the detection engine:
302+
303+
```yaml wrap
304+
safe-outputs:
305+
create-pull-request:
306+
threat-detection:
307+
steps:
308+
- name: Connect to AI Gateway
309+
run: |
310+
# Authenticate and set up connection to private AI gateway
311+
echo "Setting up gateway connection..."
312+
./scripts/setup-gateway.sh
313+
engine:
314+
id: copilot
315+
```
316+
276317
## Error Handling
277318

278319
**When Threats Are Detected:**

pkg/parser/schemas/main_workflow_schema.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7756,7 +7756,14 @@
77567756
},
77577757
"steps": {
77587758
"type": "array",
7759-
"description": "Array of extra job steps to run after detection",
7759+
"description": "Array of extra job steps to run before engine execution",
7760+
"items": {
7761+
"$ref": "#/$defs/githubActionsStep"
7762+
}
7763+
},
7764+
"post-steps": {
7765+
"type": "array",
7766+
"description": "Array of extra job steps to run after engine execution",
77607767
"items": {
77617768
"$ref": "#/$defs/githubActionsStep"
77627769
}

pkg/workflow/threat_detection.go

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ var threatLog = logger.New("workflow:threat_detection")
1414
// ThreatDetectionConfig holds configuration for threat detection in agent output
1515
type ThreatDetectionConfig struct {
1616
Prompt string `yaml:"prompt,omitempty"` // Additional custom prompt instructions to append
17-
Steps []any `yaml:"steps,omitempty"` // Array of extra job steps
17+
Steps []any `yaml:"steps,omitempty"` // Array of extra job steps to run before engine execution
18+
PostSteps []any `yaml:"post-steps,omitempty"` // Array of extra job steps to run after engine execution
1819
EngineConfig *EngineConfig `yaml:"engine-config,omitempty"` // Extended engine configuration for threat detection
1920
EngineDisabled bool `yaml:"-"` // Internal flag: true when engine is explicitly set to false
2021
RunsOn string `yaml:"runs-on,omitempty"` // Runner override for the detection job
@@ -24,7 +25,7 @@ type ThreatDetectionConfig struct {
2425
// that actually executes. Returns false when the engine is disabled and no
2526
// custom steps are configured, since the job would have nothing to run.
2627
func (td *ThreatDetectionConfig) HasRunnableDetection() bool {
27-
return !td.EngineDisabled || len(td.Steps) > 0
28+
return !td.EngineDisabled || len(td.Steps) > 0 || len(td.PostSteps) > 0
2829
}
2930

3031
// IsDetectionJobEnabled reports whether a detection job should be created for
@@ -108,13 +109,20 @@ func (c *Compiler) parseThreatDetectionConfig(outputMap map[string]any) *ThreatD
108109
}
109110
}
110111

111-
// Parse steps field
112+
// Parse steps field (pre-execution steps, run before engine execution)
112113
if steps, exists := configMap["steps"]; exists {
113114
if stepsArray, ok := steps.([]any); ok {
114115
threatConfig.Steps = stepsArray
115116
}
116117
}
117118

119+
// Parse post-steps field (post-execution steps, run after engine execution)
120+
if postSteps, exists := configMap["post-steps"]; exists {
121+
if postStepsArray, ok := postSteps.([]any); ok {
122+
threatConfig.PostSteps = postStepsArray
123+
}
124+
}
125+
118126
// Parse runs-on field
119127
if runOn, exists := configMap["runs-on"]; exists {
120128
if runOnStr, ok := runOn.(string); ok {
@@ -144,7 +152,7 @@ func (c *Compiler) parseThreatDetectionConfig(outputMap map[string]any) *ThreatD
144152
}
145153
}
146154

147-
threatLog.Printf("Threat detection configured with custom prompt: %v, custom steps: %v", threatConfig.Prompt != "", len(threatConfig.Steps) > 0)
155+
threatLog.Printf("Threat detection configured with custom prompt: %v, custom pre-steps: %v, custom post-steps: %v", threatConfig.Prompt != "", len(threatConfig.Steps) > 0, len(threatConfig.PostSteps) > 0)
148156
return threatConfig
149157
}
150158
}
@@ -186,21 +194,26 @@ func (c *Compiler) buildDetectionJobSteps(data *WorkflowData) []string {
186194
// Step 3: Prepare files - copies agent output files to expected paths
187195
steps = append(steps, c.buildPrepareDetectionFilesStep()...)
188196

189-
// Step 4: Setup threat detection (github-script)
197+
// Step 4: Custom pre-steps if configured (run before engine execution)
198+
if len(data.SafeOutputs.ThreatDetection.Steps) > 0 {
199+
steps = append(steps, c.buildCustomThreatDetectionSteps(data.SafeOutputs.ThreatDetection.Steps)...)
200+
}
201+
202+
// Step 5: Setup threat detection (github-script)
190203
steps = append(steps, c.buildThreatDetectionAnalysisStep(data)...)
191204

192-
// Step 5: Engine execution (AWF, no network)
205+
// Step 6: Engine execution (AWF, no network)
193206
steps = append(steps, c.buildDetectionEngineExecutionStep(data)...)
194207

195-
// Step 6: Custom steps if configured
196-
if len(data.SafeOutputs.ThreatDetection.Steps) > 0 {
197-
steps = append(steps, c.buildCustomThreatDetectionSteps(data.SafeOutputs.ThreatDetection.Steps)...)
208+
// Step 7: Custom post-steps if configured (run after engine execution)
209+
if len(data.SafeOutputs.ThreatDetection.PostSteps) > 0 {
210+
steps = append(steps, c.buildCustomThreatDetectionSteps(data.SafeOutputs.ThreatDetection.PostSteps)...)
198211
}
199212

200-
// Step 7: Upload detection-artifact
213+
// Step 8: Upload detection-artifact
201214
steps = append(steps, c.buildUploadDetectionLogStep(data)...)
202215

203-
// Step 8: Parse results, log extensively, and set job conclusion (single JS step)
216+
// Step 9: Parse results, log extensively, and set job conclusion (single JS step)
204217
steps = append(steps, c.buildDetectionConclusionStep()...)
205218

206219
threatLog.Printf("Generated %d detection job step lines", len(steps))

0 commit comments

Comments
 (0)