Skip to content

test(verify-sdk): exercise Jev threat classification through conversations - #6035

Open
smolpaws wants to merge 3 commits into
OpenHands:claude/verify-sdk-skillfrom
smolpaws:feat/verify-jev-security-analyzer
Open

smolpaws wants to merge 3 commits into
OpenHands:claude/verify-sdk-skillfrom
smolpaws:feat/verify-jev-security-analyzer

Conversation

@smolpaws

@smolpaws smolpaws commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

HUMAN:
This PR documents the upcoming Jev security analyzer. It's just an interim PR to the verify-sdk PR branch.


AGENT:

Summary

S13.jev → Conversation + JevSecurityAnalyzer
├─ loopback classifier → check request shape and risk mapping
├─ live TypeSafe       → check classification with a scripted actor
├─ run / arun          → analysis → confirmation → reject threats
├─ ConfirmRisky        → harmless marker runs; destructive action waits
└─ persist / resume + encrypted request-model round trip

Verification-only PR into #5750; product code is #6034 and docs are OpenHands/docs#957.

Why

Exercise the Jev analyzer through public conversations without mixing verification changes into its product PR.

Issue Number

Refs #6033

How to Test

Previously recorded verification:

Reproduction commands, results, and limits

Run with the product checkout's environment:

export CONTROL_SDK_PYTHON=/absolute/product/checkout/.venv/bin/python
export TMPDIR=/tmp
export SDK_VERIFY_HOME=/tmp/jev-sdk-verification
export PATH="$PWD/.agents/skills/verify-sdk/scripts:$PATH"
# TYPESAFE_API_KEY supplied securely by the caller
sdk-map run --file S13 --only S13.jev

The loopback and live blocks passed against product 9d0f25928. Existing S13 analyzer recipes passed. Crafted threat commands are always rejected; only a harmless marker executes in the ConfirmRisky scenario. Smoke testing is not benchmark/calibration evidence; a benign warning has rated MEDIUM for injection suspicion.

sdk-map check passes in the disposable product integration checkout. In this verification-only checkout the new product unit-test path is missing until the product lands; that dependency is intentional. Without TYPESAFE_API_KEY the live block exits 3 / blocked. The runner records the actor as scripted; the second block calls real Jev. Product evidence: https://github.com/smolpaws/software-agent-sdk/blob/feat/jev-security-analyzer/.pr/jev/README.md

Video/Screenshots

No UI change; reproduction evidence is above.

Type

  • Docs / chore

Notes

Depends on product #6034. Live mode needs TYPESAFE_API_KEY; without it the recipe is blocked. Product-dependent test paths are intentionally absent from this checkout. Smoke results are not benchmark/calibration evidence; a benign warning has rated MEDIUM.

Visual explanation by Codex using HumanLayer show-me.

Co-authored-by: openhands <openhands@all-hands.dev>
@enyst
enyst requested a review from all-hands-bot October 16, 2026 19:17
@enyst
enyst marked this pull request as ready for review October 16, 2026 19:24

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was posted by an AI agent (OpenHands).

Scope: this is a verify-sdk skill change (.agents/skills/verify-sdk/**) inside OpenHands/software-agent-sdk, which owns the SDK and the in-process feature map the skill maintains, so the change belongs here. No product code is touched; the analyzer itself is PR #6034.

What I verified on head 23a4613:

  • Head SHA matches the requested revision; PR is open, not merged, and no longer draft.
  • All check runs on this head are green, but cross-tests is green only because its tj-actions/changed-files filter covers tests/**, openhands/**, pyproject.toml, uv.lock, .github/workflows/tests.yml — not .agents/**. The gate therefore reports no cross changes and the job's test step never runs on this PR. Green CI here is not evidence for the new map entry.
  • python .agents/skills/verify-sdk/scripts/sdk_map.py check exits 1 on this head:
    {"ok": false, "files": 21, "ids": 459, "untested": 29, "problems": {"S13-security-confirmation.md": ["line 25: tests/sdk/security/test_jev_analyzer.py does not exist"]}}
  • The same command on the base branch (172bf438a) exits 0 with ids: 458. The failure is introduced by this PR.
  • tests/cross/test_verify_sdk_skill.py::test_committed_map_is_valid (which asserts sdk-map check returns 0) fails on this head and passes on the base branch:
    AssertionError: {'S13-security-confirmation.md': ['line 25: tests/sdk/security/test_jev_analyzer.py does not exist']}

Why this matters: SKILL.md names sdk-map check the read-only health check that "validates the map's structure and IDs, the index counts, the baseline line, and that every path the map cites still exists", and the repository's own cross test enforces it. The PR description says the missing product unit-test path is intentional and resolves when the product lands, but the map already has a convention for that case: a sub-feature without a unit test is written Unit: **none (untested)** and counted in the untested total, which is how the other untested rows stay checkable. Citing a not-yet-existing path leaves the committed-map validation red for every checkout that does not also carry the product branch — including the base branch this PR merges into (claude/verify-sdk-skill), and main if the verify-sdk branch lands before #6034.

Non-blocking notes, not findings: the recipe's scripted and live blocks are consistent with the product analyzer's contract at #6034 head 5a94bc2 (batched {index}.{detector} Noul questions over state.actions, api_key serialized through the canonical secret context), and the S13.jev and Gotchas prose matches that implementation (commentary is sent externally, masking does not sanitize the request, NeverConfirm still executes HIGH). The PR text says the blocks passed against product 9d0f25928, which is no longer the product head; re-running against 5a94bc2 would keep the evidence current.

One actionable finding is attached inline.

🔄 CHANGES REQUESTED

- `S13.policy-rails`: `PolicyRailSecurityAnalyzer` returns HIGH for fetch piped to a shell, raw-disk `dd`/`mkfs`, and catastrophic `rm` of `/` or home, LOW for safe commands, using the tree-sitter shell AST (quoted, path-qualified and `sh -c` nested verbs resolved; depth bound and parse errors map to UNKNOWN). Unit: `tests/sdk/security/defense_in_depth/test_policy_rails.py`, `tests/sdk/security/defense_in_depth/test_shell_semantics.py`, `tests/sdk/security/defense_in_depth/test_shell_parser_bypasses.py`, `tests/sdk/security/test_shell_ast.py`, `tests/sdk/security/test_shell_parser.py`, `tests/sdk/security/test_shell_parser_node_shapes.py`. Example: `examples/01_standalone_sdk/47_defense_in_depth_security.py`. Real-LLM: none.
- `S13.ensemble`: `EnsembleSecurityAnalyzer(analyzers=[...])` takes the maximum concrete risk, fails closed to HIGH on a child exception, ignores UNKNOWN next to a concrete verdict unless `propagate_unknown=True`, rejects an empty list, and serializes with nested children. Unit: `tests/sdk/security/defense_in_depth/test_ensemble.py`, `tests/sdk/security/defense_in_depth/test_serialization.py`. Example: `examples/01_standalone_sdk/47_defense_in_depth_security.py`. Real-LLM: none.
- `S13.toolshield`: `ToolShieldLLMSecurityAnalyzer(llm=..., history_window, safety_experiences)` asks a separate guardrail LLM about the action plus recent history, wraps untrusted fields in escaped XML, accepts only one standalone risk label (smuggled or conflicting labels give UNKNOWN); helpers detect active MCP tools and load ToolShield safety experiences. Unit: `tests/sdk/security/test_toolshield_llm_analyzer.py` (7 tests skip without the `toolshield` extra). Example: none. Real-LLM: none.
- `S13.jev`: `JevSecurityAnalyzer` sends dangerous-operation and prompt-injection Noul questions for all pending actions in one TypeSafe request. It separates executable arguments from summary/thought/plaintext reasoning, maps the maximum probability to LOW/MEDIUM/HIGH (0.2/0.5 boundaries), records both scores, uses canonical secret serialization, and rates missing credentials or malformed/incomplete responses HIGH. Unit: `tests/sdk/security/test_jev_analyzer.py`. Example: none. Real-LLM: none.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entry cites Unit: tests/sdk/security/test_jev_analyzer.py, which does not exist on this head — that file is added by the product PR #6034, not here. As a result sdk-map check exits 1 (line 25: tests/sdk/security/test_jev_analyzer.py does not exist) and tests/cross/test_verify_sdk_skill.py::test_committed_map_is_valid fails; both pass on the base branch 172bf438a. CI does not catch it because the cross-tests job's changed-files filter excludes .agents/**, so its test step is skipped for this PR.

Since the map already has a convention for a sub-feature with no unit test, use Unit: **none (untested)** here (and let sdk-map check --fix-counts update the untested total in README.md), then change it to the real path once the product test lands. That keeps the committed map valid in every checkout instead of only one that also carries the product branch.

smolpaws and others added 2 commits October 26, 2026 07:21
Co-authored-by: openhands <openhands@all-hands.dev>
Co-authored-by: openhands <openhands@all-hands.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants