feat: add Device Connect integration (on top of main)#370
Open
kavya-chennoju wants to merge 11 commits into
Open
feat: add Device Connect integration (on top of main)#370kavya-chennoju wants to merge 11 commits into
kavya-chennoju wants to merge 11 commits into
Conversation
Re-applies the Device Connect integration from strands-labs#52 onto main's architecture (mesh/ subsystem, robot.py Robot factory), rather than merging the divergent dev branch wholesale. What lands: - strands_robots/device_connect/: DeviceDriver adapters wrapping a Simulation / HardwareRobot (sim_driver, robot_driver, reachy_*), init_device_connect[_sync], GUIDE.md, setup.sh, smoke test. The drivers target main's Simulation API unchanged (start_policy, get_features, get_state, step, reset, _world/SimRobot fields). - robot.py: Robot("...").run() server hook attached by the factory. run() stops the auto-started mesh and brings the device online via Device Connect (the primary networking layer in server mode). - tools/robot_mesh.py: Device Connect dispatch layered UNDERNEATH main's existing safety gates. robot_mesh() still runs its rate limit, HITL approval (emergency_stop/broadcast), command validation, and audit first; only then does it try Device Connect, falling back to the built-in mesh when DC is unavailable or has no devices. DC dispatch re-applies the per-action validation/audit so it inherits the same safety. Gated by STRANDS_ROBOT_MESH_DC (default on; conftest turns it off so mesh unit tests stay hermetic and deterministic). - __init__.py / pyproject.toml: lazy DC exports + device-connect-edge / device-connect-agent-tools core deps. Tests: - Ports test_device_connect_{drivers,all_robots,integration}.py. Their stubs prefer the real strands / device_connect_agent_tools packages (installed here) instead of leaving leaking sys.modules mocks, and no longer delete strands_robots.tools.robot_mesh (which created a second module object and broke sibling tests' _resolve_mesh patches). - conftest.py disables the DC dispatch path during the suite. Behavior change documented in GUIDE.md: emergency_stop / broadcast are HITL-gated and fail closed when called as a bare script (no operator to approve); they run from within a Strands agent loop on approval. Verified locally: 1236 passed / 3 skipped (DC tests + main's robot_mesh safety, deep-mesh, and factory tests together); GUIDE D2D demo (peers / tell / emergency_stop) works end-to-end against device-connect PR strands-labs#52 packages. Local working branch for review; not pushed.
cagataycali
reviewed
Jun 8, 2026
There was a problem hiding this comment.
CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
…UIDE Addresses review feedback on strands-labs#370: - pyproject: move device-connect-{edge,agent-tools} out of core dependencies into a new [device-connect] extra, and include it in [all] - setup.sh: install [sim,device-connect] so the D2D demo still pulls in DC - README: document the new device-connect extra in the install table - GUIDE: clone strands-labs/robots.git instead of the fork feature branch
Clears the 29 code-scanning alerts on the new DC code: - __init__: add the 6 lazy DC exports to the TYPE_CHECKING block so __all__ names are statically defined (py/undefined-export) - reachy_transport + integration tests: document intentional empty excepts (py/empty-except) - drop unused imports (json, math) and unused 'result' locals in driver tests (py/unused-import, py/unused-local-variable) - all_robots test: probe optional deps via importlib.util.find_spec instead of side-effecting imports - robot_mesh: replace the global _dc_connected flag with a _dc_state cell (py/unused-global-variable)
7 tasks
robot_mesh could discover a Device Connect peer's advertised functions (e.g. the Reachy's nod/look/playMove) but tell/send rejected them: every command was forced through mesh.security.validate_command, whose ALLOWED_ACTIONS allowlist describes the SO-100/SO-101 policy-dispatch surface, not an arbitrary device's own function set. This blocked the Device Connect PRs from driving non-policy devices. Changes: - security.validate_device_rpc(function, params): a dedicated validator for device-native RPC. Enforces an identifier charset on the function name (no shell metacharacters/dots/slashes/control bytes), bounds the params object (identifier-safe keys, JSON-serialisable, <=64 KB), but deliberately does NOT apply ALLOWED_ACTIONS. - robot_mesh action='rpc' (+ function= param): validates via validate_device_rpc, then conn.invoke(target, function, params) directly. Inherits the existing rate-limit + audit machinery; falls back with a clear error when Device Connect is unavailable. - tests/test_device_rpc_validation.py: 18 unit tests covering accept, not-gated-by-allowlist, charset/size/serialisation rejections, and param-copy semantics. Verified live against a Reachy on the beta tenant (nod -> success) and that a malicious function name (e.g. 'rm -rf /') is rejected. lint clean (ruff check + format), mypy clean (only pre-existing device_connect_agent_tools import-untyped note), 94 security tests pass.
This was referenced Jun 9, 2026
feat(mesh): device-native RPC path for robot_mesh (action='rpc')
…ation Harden the Device Connect integration across the agent dispatch path, the device drivers, and the Reachy transport. All changes are defence-in-depth and preserve existing behaviour on the trusted-path defaults. - robot_mesh broadcast: dispatch the validated, operator-approved command through Device Connect instead of re-parsing the raw caller string, so the executed action cannot differ from the approved one. - robot_mesh rpc: route device-native rpc through the human-in-the-loop approval gate (surfacing the function name) so device-native functions are not invoked without explicit approval. - drivers: validate policy_provider against the existing allowlist in both robot and sim execute() before starting a policy, preventing inference from being pointed at an arbitrary endpoint. - drivers: add per-call caller authorization on state-mutating RPCs (execute/stop/step/reset) and validate the source of emergencyStop against an allowlist. Env-driven (DEVICE_CONNECT_RPC_ALLOW / DEVICE_CONNECT_ESTOP_ALLOW); permissive with a warning when unset, fail-closed when set. Uses get_rpc_source_device() from device-connect-edge. - reachy: validate playMove move_name against a strict charset before interpolating it into the daemon REST path (prevents path traversal / query injection). - reachy: support an optional bearer token (REACHY_DAEMON_TOKEN) on the daemon WebSocket and REST interfaces and warn loudly when the link is unauthenticated. - transport: make Device Connect secure by default. allow_insecure now defaults to False and must be opted into explicitly (arg or DEVICE_CONNECT_ALLOW_INSECURE); a warning is logged whenever insecure mode is active. Removed the agent-side setdefault that forced insecure mode process-wide. - GUIDE.md: document secure-by-default and the explicit insecure opt-in for local D2D trials. - CI: when a PR touches the Device Connect integration, pin device-connect-edge / agent-tools to source via UV_OVERRIDE so the matching framework version is exercised. - tests: add tests/test_device_connect_hardening.py covering all of the above.
The per-call caller ACL (DEVICE_CONNECT_RPC_ALLOW) was effectively
all-or-nothing on the agent path: device_connect_agent_tools clients are
anonymous, so the device always saw caller=None. Setting an allowlist
therefore denied every agent call (no allow-path was reachable), and the
behaviour wasn't documented.
Layer 1 — caller identity propagation (robot_mesh):
- _agent_identity()/_with_identity() stamp _dc_meta.source_device into the
DC command envelope from STRANDS_ROBOT_MESH_AGENT_ID (or
DEVICE_CONNECT_CLIENT_ID). Wired into tell/send/rpc/stop/broadcast/
emergency_stop. Anonymous callers unchanged (still fail-closed).
Layer 2 — honest authz semantics (_authz):
- Document that the caller id is only a hard boundary under authenticated
transport; under DEVICE_CONNECT_ALLOW_INSECURE it is self-asserted.
- Log a one-time advisory when an allowlist is enforced over insecure
transport.
Layer 3 — docs + test honesty:
- Extract resolve_allow_insecure() and fix the stale "defaults to True in
D2D" docstring (it defaults to False / secure).
- GUIDE.md: drop the contradictory DEVICE_CONNECT_ALLOW_INSECURE=true from
the mTLS Full-Infrastructure section; document the allowlist's agent-id
requirement and advisory-under-insecure semantics.
- Replace the tautological test_allow_insecure_defaults_false (it
re-implemented the logic) with tests that call the real resolver and
init_device_connect. Add coverage for the reachable anonymous-deny, the
insecure advisory, and _dc_meta identity propagation. 21 -> 29 tests.
…ardening fix(device-connect): security hardening for the Device Connect integration
…y-hardening fix(device-connect): make RPC caller-allowlist usable + honest
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Re-applies the Device Connect integration onto
main's architecture, as an alternative to #52 (which targetsdev).mainanddevdiverged structurally —maincarries the productionizedmesh/subsystem and aRobot()factory inrobot.py— so this branch ports the integration ontomainrather than merging thedev-based branch wholesale (a direct merge produced a 367-file diff with brokenrobot.py).What's included
strands_robots/device_connect/—DeviceDriveradapters wrapping aSimulation/HardwareRobot(sim_driver,robot_driver,reachy_*), plusinit_device_connect[_sync],GUIDE.md,setup.sh, and a control-loop smoke test. The drivers targetmain'sSimulationAPI unchanged (start_policy,get_features,get_state,step,reset,_world/SimRobotfields).robot.py—Robot("…").run()server hook attached by the factory.run()stops the auto-started mesh and brings the device online via Device Connect (the primary networking layer in server mode).tools/robot_mesh.py— Device Connect dispatch layered underneath the existing safety gates.robot_mesh()still runs its rate limit, HITL approval (emergency_stop/broadcast), command validation, and audit first; only then does it dispatch via Device Connect, falling back to the built-in mesh when DC is unavailable or has no devices. DC dispatch re-applies the per-action validation/audit so it inherits the same safety. Gated bySTRANDS_ROBOT_MESH_DC(default on; disabled in the test suite so mesh unit tests stay hermetic).__init__.py/pyproject.toml— lazy DC exports +device-connect-edge/device-connect-agent-toolscore deps.Behavior change
emergency_stop/broadcastare HITL-gated and fail closed when called as a bare script (no operator to approve); they run from within a Strands agent loop on approval.GUIDE.mddocuments this.Testing
main'srobot_meshsafety, deep-mesh, and factory tests.peers/tell/emergency_stop) verified end-to-end against the latestarm/device-connectpackages.Notes
Test-isolation fixes: the DC test stubs prefer the real
strands/device_connect_agent_toolspackages instead of leaving leakingsys.modulesmocks, and no longer deletestrands_robots.tools.robot_mesh(which created a second module object and broke sibling tests'_resolve_meshpatches).