-
Notifications
You must be signed in to change notification settings - Fork 544
feat(security): emit SecurityAnalysisEvent with the analyzer's verdict per action batch #5182
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
base: main
Are you sure you want to change the base?
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 |
|---|---|---|
|
|
@@ -50,6 +50,7 @@ | |
| CondensationRequest, | ||
| ) | ||
| from openhands.sdk.event.error_classification import AGENT_OUTCOME | ||
| from openhands.sdk.event.security import SecurityAnalysisEvent | ||
| from openhands.sdk.llm import ( | ||
| LLM, | ||
| ImageContent, | ||
|
|
@@ -1044,7 +1045,10 @@ async def _astep( | |
| ) | ||
|
|
||
| def _requires_user_confirmation( | ||
| self, state: ConversationState, action_events: list[ActionEvent] | ||
| self, | ||
| state: ConversationState, | ||
| action_events: list[ActionEvent], | ||
| on_event: ConversationCallbackType | None = None, | ||
| ) -> bool: | ||
| """ | ||
| Decide whether user confirmation is needed to proceed. | ||
|
|
@@ -1068,12 +1072,25 @@ def _requires_user_confirmation( | |
| # If a security analyzer is registered, use it to grab the risks of the actions | ||
| # involved. If not, we'll set the risks to UNKNOWN. | ||
| if state.security_analyzer is not None: | ||
| risks = [ | ||
| risk | ||
| for _, risk in state.security_analyzer.analyze_pending_actions( | ||
| action_events | ||
| analyses = state.security_analyzer.analyze_actions(action_events) | ||
| risks = [analysis.risk for _, analysis in analyses] | ||
| # Record the verdict regardless of what the policy does with it, so | ||
| # the log and UI show what the analyzer said even under NeverConfirm. | ||
| if on_event is not None: | ||
| on_event( | ||
| SecurityAnalysisEvent( | ||
| analyzer=state.security_analyzer.__class__.__name__, | ||
| policy=state.confirmation_policy.__class__.__name__, | ||
| risks={ | ||
| action.id: analysis.risk for action, analysis in analyses | ||
| }, | ||
| details={ | ||
| action.id: analysis.details | ||
| for action, analysis in analyses | ||
| if analysis.details is not None | ||
| }, | ||
|
Comment on lines
+1087
to
+1091
Member
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. 🔴 Critical [P1]: mask registered secrets before emitting analyzer details.
Reproduced using only a synthetic value: register Please apply the conversation secret registry's recursive model masking to the completed AI-generated feedback from OpenHands on behalf of the requesting user. |
||
| ) | ||
| ) | ||
| ] | ||
| else: | ||
| risks = [risk.SecurityRisk.UNKNOWN] * len(action_events) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| from typing import Any | ||
|
|
||
| from pydantic import Field | ||
| from rich.text import Text | ||
|
|
||
| from openhands.sdk.event.base import Event | ||
| from openhands.sdk.event.types import EventID, SourceType | ||
| from openhands.sdk.security.risk import SecurityRisk | ||
|
|
||
|
|
||
| class SecurityAnalysisEvent(Event): | ||
| """Risk levels the configured security analyzer assigned to pending actions. | ||
|
|
||
| Emitted once per batch of actions the agent is about to execute, whenever a | ||
| security analyzer is configured on the conversation, regardless of whether | ||
| the confirmation policy then asks for confirmation. This makes the | ||
| analyzer's verdict visible in the event log and UI even when the policy | ||
| (for example ``NeverConfirm``) never consults it. | ||
|
|
||
| This event is not shown to the LLM. | ||
| """ | ||
|
|
||
| source: SourceType = "environment" | ||
|
|
||
| analyzer: str = Field( | ||
| description="Kind of the security analyzer that produced these risks." | ||
| ) | ||
| policy: str = Field( | ||
| description="Kind of the confirmation policy in force when the actions " | ||
| "were analyzed." | ||
| ) | ||
| risks: dict[EventID, SecurityRisk] = Field( | ||
| description="Risk level per analyzed action, keyed by ActionEvent id." | ||
| ) | ||
| details: dict[EventID, dict[str, Any]] = Field( | ||
| default_factory=dict, | ||
| description="Optional analyzer-specific detail per action, keyed by " | ||
| "ActionEvent id (for example probabilities, confidence, or a rationale).", | ||
| ) | ||
|
|
||
| @property | ||
| def visualize(self) -> Text: | ||
| text = Text() | ||
| text.append("Security Analysis\n", style="bold") | ||
| text.append(f"analyzer={self.analyzer} policy={self.policy}\n") | ||
| for action_id, risk in self.risks.items(): | ||
| text.append(f" {action_id}: {risk.value}\n") | ||
| return text |
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.
🔴 Critical [P1]: preserve existing batch-analyzer overrides when changing the dispatch API.
This replaces the call to
analyze_pending_actions()withanalyze_actions(). The compatibility wrapper only preserves callers of the old method, not custom analyzers that override it: the agent now silently ignores their batch verdicts.I reproduced this in live Haiku-driven conversations on base and head with an otherwise unchanged analyzer:
Under
ConfirmRisky(), base stops atwaiting_for_confirmationwith 0 file writes. This head finishes with 1 actual file write, and the new audit says LOW, even though calling that same analyzer's batch method still returns HIGH. This can bypass confirmation for existing custom integrations that implement batch/context-dependent assessment.Please preserve the old override contract when bridging to detailed analyses (without analyzing twice), or provide an explicit migration rather than silently selecting a different verdict source. Add a regression exercising a legacy batch override through agent dispatch and asserting both the confirmation outcome and the emitted verdict.
AI-generated feedback from OpenHands on behalf of the requesting user.