-
Notifications
You must be signed in to change notification settings - Fork 432
Normalize Pi threat-detection models before Copilot fallback #41545
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,6 +34,17 @@ func (c *Compiler) buildDetectionEngineExecutionStep(data *WorkflowData) []strin | |
| if hasThreatDetectionEngineConfig { | ||
| engineConfig = data.SafeOutputs.ThreatDetection.EngineConfig | ||
| } | ||
| // Preserve the original engine identity before Pi is normalized to Copilot for | ||
| // detection. Precedence matches runtime engine resolution: explicit | ||
| // threat-detection.engine.id overrides the main engine config, which overrides | ||
| // the legacy top-level AI field. | ||
| originalEngineID := data.AI | ||
| if data.EngineConfig != nil && data.EngineConfig.ID != "" { | ||
| originalEngineID = data.EngineConfig.ID | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Precedence mismatch vs 💡 Details and suggested fix
engineID = data.AI // primary
if engineID == "" && data.EngineConfig != nil && data.EngineConfig.ID != "" {
engineID = data.EngineConfig.ID // fallback only when data.AI is empty
}But the new code at line 42–43 does: originalEngineID := data.AI
if data.EngineConfig != nil && data.EngineConfig.ID != "" {
originalEngineID = data.EngineConfig.ID // unconditional override — opposite priority
}The comment on lines 37–40 claims this matches runtime resolution ( Impact: if Fix: mirror originalEngineID := data.AI
if originalEngineID == "" && data.EngineConfig != nil && data.EngineConfig.ID != "" {
originalEngineID = data.EngineConfig.ID // fallback only when data.AI is empty
}Also update the comment on line 37 to match actual behaviour. |
||
| } | ||
| if hasThreatDetectionEngineConfig && data.SafeOutputs.ThreatDetection.EngineConfig.ID != "" { | ||
| originalEngineID = data.SafeOutputs.ThreatDetection.EngineConfig.ID | ||
| } | ||
|
|
||
| // Get the engine instance | ||
| engine, err := c.getAgenticEngine(engineSetting) | ||
|
|
@@ -87,6 +98,13 @@ func (c *Compiler) buildDetectionEngineExecutionStep(data *WorkflowData) []strin | |
| if detectionEngineConfig.APITarget == "" && data.EngineConfig != nil && data.EngineConfig.APITarget != "" { | ||
| detectionEngineConfig.APITarget = data.EngineConfig.APITarget | ||
| } | ||
| if engineSetting == "copilot" && originalEngineID == "pi" { | ||
| // Pi requires provider/model syntax (for example "copilot/gpt-5.4"), but the | ||
| // Copilot CLI expects only the model ID. extractPiModelID preserves bare model | ||
| // names unchanged, so empty or already-normalized values keep their current | ||
| // fallback behavior while provider-scoped Pi models become Copilot-compatible. | ||
| detectionEngineConfig.Model = extractPiModelID(detectionEngineConfig.Model) | ||
| } | ||
|
|
||
| // Create minimal WorkflowData for threat detection. | ||
| // SandboxConfig with AWF enabled ensures the engine runs inside the firewall. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1269,6 +1269,21 @@ func TestCopilotDetectionDefaultModel(t *testing.T) { | |
| shouldContainModel: true, | ||
| expectedModel: "gpt-4", | ||
| }, | ||
| { | ||
| name: "pi engine threat detection normalizes provider-scoped model for copilot fallback", | ||
| data: &WorkflowData{ | ||
| AI: "pi", | ||
| EngineConfig: &EngineConfig{ | ||
| ID: "pi", | ||
| Model: "copilot/gpt-5.4", | ||
| }, | ||
| SafeOutputs: &SafeOutputsConfig{ | ||
| ThreatDetection: &ThreatDetectionConfig{}, | ||
| }, | ||
| }, | ||
| shouldContainModel: true, | ||
| expectedModel: "gpt-5.4", | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test only covers the aligned 💡 Suggested additional test caseA case where normalization is driven by a threat-detection-specific engine config (exercises the {
name: "pi threat-detection engine config normalizes provider-scoped model for copilot fallback",
data: &WorkflowData{
AI: "copilot", // main engine is copilot; TD override is pi
SafeOutputs: &SafeOutputsConfig{
ThreatDetection: &ThreatDetectionConfig{
EngineConfig: &EngineConfig{
ID: "pi",
Model: "copilot/gpt-5.4",
},
},
},
},
shouldContainModel: true,
expectedModel: "gpt-5.4",
},This ensures the third branch of |
||
| }, | ||
| { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] The third branch of A user who writes 💡 Suggested test case to add after this one{
name: "pi engine via explicit threat-detection engine config normalizes provider-scoped model",
data: &WorkflowData{
AI: "claude",
SafeOutputs: &SafeOutputsConfig{
ThreatDetection: &ThreatDetectionConfig{
EngineConfig: &EngineConfig{
ID: "pi",
Model: "copilot/gpt-5.4",
},
},
},
},
shouldContainModel: true,
expectedModel: "gpt-5.4",
},This ensures the explicit |
||
| name: "copilot engine with threat detection engine config with custom model", | ||
| data: &WorkflowData{ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/diagnose] The
data.EngineConfig.IDoverride here lacks theoriginalEngineID == ""guard present ingetThreatDetectionEngineID, so the two precedence rules diverge.In
getThreatDetectionEngineID(threat_detection_external.go:124),data.AIwins if non-empty, withdata.EngineConfig.IDused only as a fallback. Heredata.EngineConfig.IDalways overridesdata.AI— the inverse. The comment above claims precedence matches runtime resolution, but it doesn't.For the reported bug both fields are
"pi"so the inconsistency is invisible, but it becomes a latent correctness hazard if the two fields ever diverge.💡 Suggested fix
Add the same empty-check guard used in
getThreatDetectionEngineID:This brings the precedence in line with the comment and with the existing engine-resolution logic.