get2knowio / get2knowio/deacon

feat: Add native reverse port and configuration plumbing for Chrome DevTools MCP browser automation

Open
#282 0 comments 0 reactions 0 assignees View on GitHub
enhancement feat
Dominant language
Rust
Stars
2
Forks
0
Avg merge
2h 36m
Merged PRs (30d)
78

Description

## Problem

An agent (e.g. Claude Code) running inside a `deacon`-managed devcontainer cannot drive a Chrome instance running on the developer's host desktop — there's no network path from the container to a host process, and no standard way to tell an in-container MCP client where to find it. This blocks browser-automation workflows (visual debugging, screenshot loops, page-navigation QA) that assume the agent can reach `chrome-devtools-mcp` pointed at the host browser's `--remote-debugging-port`.

**Note on the original ask:** the request specifies `deacon shell --browser`. That subcommand doesn't exist — `crates/deacon/src/cli.rs:245`'s `Commands` enum lists only `Up, Build, Exec, ReadConfiguration, Config, Templates, Upgrade, SetUp, RunUserCommands, Down, Doctor, Outdated, ForwardDaemon`. This issue reframes the flag as living on `deacon up` (and/or `deacon exec`), consistent with the real subcommand surface.

## Two problems bundled together — recommend splitting scope

The request bundles (1) a network-reachability problem and (2) an app-specific config-injection problem. These have very different risk profiles and should likely be split:

### 1. Container→host reachability (new plumbing, needs its own design)

This is the **reverse direction** of everything `deacon` currently does for port handling. The existing `015-auto-forward-ports` daemon (`crates/core/src/port_forward/{daemon.rs,relay.rs,registry.rs}`) solves *host→container* reachability: the host binds a loopback listener and, per connection, runs `docker exec -i nc/socat ...` to dial **into** the container's netns (`relay.rs:1-6,56`, the relay program dials `{dial_host}:{container_port}` from inside the container, driven by a host-side listener). There is currently **zero** code path for the opposite direction — confirmed via grep: no hits for `host.docker.internal`, `host-gateway`, `add-host`, or `extra_hosts`/`ExtraHosts` anywhere in `docker.rs`, `compose.rs`, `container.rs`, or `up/*`. Making a container process reach a host port is genuinely new work, not an extension of the existing daemon's direction.

This same container→host reachability gap is also the crux of a related open issue (**host clipboard bridge**, get2knowio/deacon#281) — worth designing once and sharing the underlying mechanism (e.g. a generic "reverse tunnel" primitive) rather than building two bespoke, direction-specific solutions. Recommend cross-referencing/consolidating design work between the two.

Candidate approaches (need a design spike, not assumed as decided):
- **`--add-host=host.docker.internal:host-gateway`** at container-create time (Linux; Docker Desktop macOS/Windows already auto-injects this), then have deacon write/inject a small in-container proxy (e.g. `socat TCP-LISTEN:,fork,reuseaddr TCP:host.docker.internal:`) so the agent can still dial `127.0.0.1:` as the request expects. Podman's rootless netns gateway needs separate handling.
- **Extend `relay.rs` for container-initiated dial-out**, mirroring the existing daemon/registry/marker lifecycle but reversing the dial direction — reuses proven daemon plumbing (`daemonize()`, SIGTERM-on-teardown, `user_data_folder`-scoped marker files) at the cost of nontrivial changes to `relay.rs`'s current host→container assumption.

### 2. MCP config injection — likely out of deacon's scope, should be a Feature instead

Writing `~/.config/Claude/mcp.json` with a `chrome-devtools` block hardcodes assumptions about one specific AI tool's config format into deacon core. Grep confirms **zero** existing references to `claude`, `Claude`, `mcp.json`, or `MCP` anywhere in the codebase — deacon has no precedent of special-casing any single IDE/agent/tool (consistent with the Consumer-Only Scope constitution: deacon implements the containers.dev spec surface, not app-specific tooling).

The sanctioned extension point for "install/configure app X inside the container" already exists: the **Feature mechanism** (`crates/core/src/features.rs` — `FeatureMetadata`, `InstallationPlan`, `FeatureMerger`; invoked via `install_features_for_compose` in `up/compose.rs:748` and `apply_features_and_lockfile` in `build/mod.rs:1610`). Writing an MCP config for `chrome-devtools-mcp` is exactly the shape of thing a Feature's `install.sh` should do — a workspace opts into a `chrome-devtools-mcp` Feature in `devcontainer.json`, and that Feature's install script writes whatever config file the agent it targets expects.

**What deacon *should* own**, if anything, is exposing the forwarded host port/address to the container in a discoverable way (e.g. via `remoteEnv`, following the existing `containerEnv`/`remoteEnv` PATH-append precedent in `container_env_probe.rs:593-648`) — e.g. a `DEACON_HOST_CDP_PORT` or similar env var — so a Feature or `postCreateCommand` can consume it without deacon needing to know anything about MCP, Claude, or any other specific agent.

## Security — this needs prominent, explicit treatment

The Chrome DevTools Protocol has **no built-in authentication**. Anything with network access to a CDP endpoint can execute arbitrary JavaScript in any open tab, read cookies/session storage for any site the user is logged into, and navigate to `file://` URLs to read local files — this is why Chrome's own docs and multiple public security advisories warn against binding `--remote-debugging-port` beyond loopback. Making that endpoint reachable from inside a devcontainer (which may run code from an untrusted repo, a compromised Feature, or a prompt-injected agent) is a materially larger exposure than the existing port-forward or clipboard-bridge proposals, both of which are loopback-only and narrowly scoped.

This should follow the same trust-boundary precedent already established for `--auto-forward`'s browser auto-open (`SECURITY.md:104-131`): forwarded-port binds are loopback-only, and the *machine owner* controls the sensitive knob (`DEACON_BROWSER` env var / `settings.json`, never workspace-sourced) even though the *workspace* can request the behavior (`onAutoForward`). Applied here:
- The flag enabling this bridge must be **explicit, opt-in, and off by default** — never auto-enabled by a workspace's `devcontainer.json`.
- Loud documentation (in `--help` and `SECURITY.md`) of exactly what the flag exposes: any process in the container gets full remote-control of the host browser session.
- No silent default port (the request suggests `9222`/`4222` ambiguously) — require the user to specify which host CDP port to bridge, and fail loudly if unreachable rather than silently no-oping (Principle IV).
- Consider whether deacon should refuse to proceed unless the host Chrome was launched with `--remote-debugging-address=127.0.0.1` (never `0.0.0.0`) and, ideally, `--remote-allow-origins` scoped — though deacon doesn't control how the user launches Chrome, so this may only be enforceable via a warning, not a hard check.

## Open questions

1. Is deacon launching/managing the host Chrome process in scope at all, or does the user launch it themselves (`google-chrome --remote-debugging-port=9222`) and deacon only bridges to an already-running instance? (Recommend: deacon does not manage host GUI apps — bridge-only.)
2. What's the actual reachability mechanism (see options above) — needs a design spike before implementation, ideally shared with #281's clipboard-bridge reachability work.
3. Should the forwarded port be discoverable via `remoteEnv`, a generated file under `.devcontainer/`, or both?
4. Does this need Podman-specific handling for the rootless netns gateway, matching the same open question flagged in #281?
5. Confirm the actual Claude Code MCP config path/schema before any Feature is written — `~/.config/Claude/mcp.json` in the original request has not been verified against current Claude Code conventions and may be stale/incorrect.

## Suggested implementation phases

- [ ] **Phase 0 — Design spike**: settle the container→host reachability mechanism (shared with #281), confirm it works across Docker Desktop (macOS/Windows), Docker on Linux, and flag Podman as a follow-up if not solvable in the same pass.
- [ ] **Phase 1 — Reachability only**: opt-in flag on `deacon up`/`exec` (e.g. `--host-cdp-port `), loopback-only bridge, port/address exposed to the container via `remoteEnv`, no MCP/Claude-specific code in deacon core. Security documentation in `SECURITY.md` from day one.
- [ ] **Phase 2 (separate effort, likely a community Feature, not deacon core)**: a `chrome-devtools-mcp` Dev Container Feature that consumes the exposed env var and writes the appropriate MCP config for whichever agent's format is targeted.

## Acceptance criteria (Phase 1 only — deacon core)

- Bridge is fully opt-in; `deacon up`/`exec` with no flag is byte-identical to today's behavior.
- Host-side bind (if any new listener is introduced) is loopback-only, matching the `port_forward` daemon's existing posture.
- The forwarded port/host address is discoverable inside the container via an env var (name TBD), with no deacon-side knowledge of MCP, Claude, or `chrome-devtools-mcp` baked in.
- `SECURITY.md` documents the exposure explicitly before merge, not as a follow-up.
- `down`/container teardown leaves no orphaned host-side process or listener, matching the `port_forward` daemon's cleanup guarantee.
- `cargo clippy --all-targets --all-features -- -D warnings` and `cargo fmt --all -- --check` clean; new integration tests correctly grouped in `.config/nextest.toml`.

Contributor guide

Open the contributing guide

Research direction

Start with the design spike shared with issue #281, then read crates/core/src/port_forward/{daemon.rs,relay.rs,registry.rs} and crates/deacon/src/cli.rs:245 to understand existing port and command lifecycles. Review up/compose.rs:748, build/mod.rs:1610, container_env_probe.rs:593-648, and SECURITY.md:104-131 for feature and environment conventions. Done means a documented, opt-in Phase 1 design with security boundaries, lifecycle cleanup, environment discovery, and test locations agreed before implementation.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker, rust
Domain
devops, infrastructure, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
28/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.