Skip to content

Commit 871c544

Browse files
authored
fix: asyncio issues with security analyzer + enable security analyzer in cli (#5356)
1 parent 92b38dc commit 871c544

File tree

5 files changed

+58
-3
lines changed

5 files changed

+58
-3
lines changed

openhands/controller/agent_controller.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,8 @@ async def _handle_observation(self, observation: Observation) -> None:
284284
self.agent.llm.metrics.merge(observation.llm_metrics)
285285

286286
if self._pending_action and self._pending_action.id == observation.cause:
287+
if self.state.agent_state == AgentState.AWAITING_USER_CONFIRMATION:
288+
return
287289
self._pending_action = None
288290
if self.state.agent_state == AgentState.USER_CONFIRMED:
289291
await self.set_agent_state_to(AgentState.RUNNING)
@@ -369,6 +371,7 @@ async def set_agent_state_to(self, new_state: AgentState) -> None:
369371
else:
370372
confirmation_state = ActionConfirmationStatus.REJECTED
371373
self._pending_action.confirmation_state = confirmation_state # type: ignore[attr-defined]
374+
self._pending_action._id = None # type: ignore[attr-defined]
372375
self.event_stream.add_event(self._pending_action, EventSource.AGENT)
373376

374377
self.state.agent_state = new_state

openhands/core/cli.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from openhands.controller import AgentController
1212
from openhands.controller.agent import Agent
1313
from openhands.core.config import (
14+
AppConfig,
1415
get_parser,
1516
load_app_config,
1617
)
@@ -20,6 +21,7 @@
2021
from openhands.events import EventSource, EventStream, EventStreamSubscriber
2122
from openhands.events.action import (
2223
Action,
24+
ActionConfirmationStatus,
2325
ChangeAgentStateAction,
2426
CmdRunAction,
2527
FileEditAction,
@@ -30,10 +32,12 @@
3032
AgentStateChangedObservation,
3133
CmdOutputObservation,
3234
FileEditObservation,
35+
NullObservation,
3336
)
3437
from openhands.llm.llm import LLM
3538
from openhands.runtime import get_runtime_cls
3639
from openhands.runtime.base import Runtime
40+
from openhands.security import SecurityAnalyzer, options
3741
from openhands.storage import get_file_store
3842

3943

@@ -45,6 +49,15 @@ def display_command(command: str):
4549
print('❯ ' + colored(command + '\n', 'green'))
4650

4751

52+
def display_confirmation(confirmation_state: ActionConfirmationStatus):
53+
if confirmation_state == ActionConfirmationStatus.CONFIRMED:
54+
print(colored('✅ ' + confirmation_state + '\n', 'green'))
55+
elif confirmation_state == ActionConfirmationStatus.REJECTED:
56+
print(colored('❌ ' + confirmation_state + '\n', 'red'))
57+
else:
58+
print(colored('⏳ ' + confirmation_state + '\n', 'yellow'))
59+
60+
4861
def display_command_output(output: str):
4962
lines = output.split('\n')
5063
for line in lines:
@@ -59,7 +72,7 @@ def display_file_edit(event: FileEditAction | FileEditObservation):
5972
print(colored(str(event), 'green'))
6073

6174

62-
def display_event(event: Event):
75+
def display_event(event: Event, config: AppConfig):
6376
if isinstance(event, Action):
6477
if hasattr(event, 'thought'):
6578
display_message(event.thought)
@@ -74,6 +87,8 @@ def display_event(event: Event):
7487
display_file_edit(event)
7588
if isinstance(event, FileEditObservation):
7689
display_file_edit(event)
90+
if hasattr(event, 'confirmation_state') and config.security.confirmation_mode:
91+
display_confirmation(event.confirmation_state)
7792

7893

7994
async def main():
@@ -119,12 +134,18 @@ async def main():
119134
headless_mode=True,
120135
)
121136

137+
if config.security.security_analyzer:
138+
options.SecurityAnalyzers.get(
139+
config.security.security_analyzer, SecurityAnalyzer
140+
)(event_stream)
141+
122142
controller = AgentController(
123143
agent=agent,
124144
max_iterations=config.max_iterations,
125145
max_budget_per_task=config.max_budget_per_task,
126146
agent_to_llm_config=config.get_agent_to_llm_config_map(),
127147
event_stream=event_stream,
148+
confirmation_mode=config.security.confirmation_mode,
128149
)
129150

130151
async def prompt_for_next_task():
@@ -143,14 +164,34 @@ async def prompt_for_next_task():
143164
action = MessageAction(content=next_message)
144165
event_stream.add_event(action, EventSource.USER)
145166

167+
async def prompt_for_user_confirmation():
168+
loop = asyncio.get_event_loop()
169+
user_confirmation = await loop.run_in_executor(
170+
None, lambda: input('Confirm action (possible security risk)? (y/n) >> ')
171+
)
172+
return user_confirmation.lower() == 'y'
173+
146174
async def on_event(event: Event):
147-
display_event(event)
175+
display_event(event, config)
148176
if isinstance(event, AgentStateChangedObservation):
149177
if event.agent_state in [
150178
AgentState.AWAITING_USER_INPUT,
151179
AgentState.FINISHED,
152180
]:
153181
await prompt_for_next_task()
182+
if (
183+
isinstance(event, NullObservation)
184+
and controller.state.agent_state == AgentState.AWAITING_USER_CONFIRMATION
185+
):
186+
user_confirmed = await prompt_for_user_confirmation()
187+
if user_confirmed:
188+
event_stream.add_event(
189+
ChangeAgentStateAction(AgentState.USER_CONFIRMED), EventSource.USER
190+
)
191+
else:
192+
event_stream.add_event(
193+
ChangeAgentStateAction(AgentState.USER_REJECTED), EventSource.USER
194+
)
154195

155196
event_stream.subscribe(EventStreamSubscriber.MAIN, on_event, str(uuid4()))
156197

openhands/core/config/security_config.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,9 @@ def __str__(self):
3232

3333
return f"SecurityConfig({', '.join(attr_str)})"
3434

35+
@classmethod
36+
def from_dict(cls, security_config_dict: dict) -> 'SecurityConfig':
37+
return cls(**security_config_dict)
38+
3539
def __repr__(self):
3640
return self.__str__()

openhands/core/config/utils.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
)
1919
from openhands.core.config.llm_config import LLMConfig
2020
from openhands.core.config.sandbox_config import SandboxConfig
21+
from openhands.core.config.security_config import SecurityConfig
2122

2223
load_dotenv()
2324

@@ -144,6 +145,12 @@ def load_from_toml(cfg: AppConfig, toml_file: str = 'config.toml'):
144145
)
145146
llm_config = LLMConfig.from_dict(nested_value)
146147
cfg.set_llm_config(llm_config, nested_key)
148+
elif key is not None and key.lower() == 'security':
149+
logger.openhands_logger.debug(
150+
'Attempt to load security config from config toml'
151+
)
152+
security_config = SecurityConfig.from_dict(value)
153+
cfg.security = security_config
147154
elif not key.startswith('sandbox') and key.lower() != 'core':
148155
logger.openhands_logger.warning(
149156
f'Unknown key in {toml_file}: "{key}"'

openhands/security/invariant/analyzer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ async def confirm(self, event: Event) -> None:
300300
)
301301
# we should confirm only on agent actions
302302
event_source = event.source if event.source else EventSource.AGENT
303-
await call_sync_from_async(self.event_stream.add_event, new_event, event_source)
303+
self.event_stream.add_event(new_event, event_source)
304304

305305
async def security_risk(self, event: Action) -> ActionSecurityRisk:
306306
logger.debug('Calling security_risk on InvariantAnalyzer')

0 commit comments

Comments
 (0)