Skip to content

Pin ACP npx -y launch commands instead of resolving floating npm latest at runtime #3357

@Fieldnote-Echo

Description

@Fieldnote-Echo

Summary

The ACP provider defaults currently launch the three ACP backends with unversioned npx -y <package> commands. That means the package version is resolved from npm at agent-launch time, not reviewed at commit time, lock time, or image build time. For ACP agents that run with high-permission modes, this creates an avoidable supply-chain risk: a compromised, poisoned, or unexpectedly broken latest publish would be fetched and executed on the next launch.

This is hardening, not a report of an active exploit. The goal is to bring the npm ACP launchers under the same version discipline the repo already applies elsewhere (the uv exclude-newer = "7 days" guardrail in [tool.uv] covers the Python lock, but not these runtime npx launches).

Where

openhands-sdk/openhands/sdk/settings/acp_providers.py, the launch defaults:

  • L100 ("npx", "-y", "@agentclientprotocol/claude-agent-acp"), default_session_mode="bypassPermissions"
  • L111 ("npx", "-y", "@zed-industries/codex-acp"), default_session_mode="full-access"
  • L122 ("npx", "-y", "@google/gemini-cli", "--acp"), default_session_mode="yolo"

Each provider pairs floating runtime resolution with a permission-disabling session mode, so this is not a low-stakes dev convenience path.

The agent-server Dockerfile already pins these for its bundled install (openhands-agent-server/openhands/agent_server/docker/Dockerfile L174-176: @...@0.30.0, @...@0.11.1, @...@0.38.0), so the build-time version and the version actually launched at runtime can diverge.

Separately, the OpenAPI example at openhands-agent-server/openhands/agent_server/conversation_router_acp.py:53 uses npx -y claude-agent-acp with the unscoped name. That name is unregistered on npm (404 today), so the example is both broken and a dependency-confusion footgun: the name is registerable by anyone, after which npx -y would fetch and run it. It should use the scoped, versioned package like the providers.

Proposed change

Pin each launch command to a reviewed version, aligned with the bundled Dockerfile versions so build and runtime agree (and bring gemini-cli to a patched >= 0.39.1):

("npx", "-y", "@google/gemini-cli@<pinned>", "--acp")

Fix the router example to the scoped, versioned name.

Tradeoff

Version strings in source are not Dependabot-tracked, so they need manual bumps (reasonable for three launchers, and consistent with the Dockerfile already pinning them by hand). The heavier alternative is launching the CLIs from a committed lockfile install rather than npx, which pins the full transitive tree; the version pin is the smaller step that closes the floating-latest gap.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions