Skip to content

Add option to enable image pasting via X11 clipboard#44

Merged
nezhar merged 1 commit into
mainfrom
issue-28
Mar 15, 2026
Merged

Add option to enable image pasting via X11 clipboard#44
nezhar merged 1 commit into
mainfrom
issue-28

Conversation

@nezhar

@nezhar nezhar commented Mar 15, 2026

Copy link
Copy Markdown
Collaborator

Adds support for enabling image pasting via the X11 clipboard in agent containers, controlled by a new --paste-images flag. It introduces logic to mount the X11 socket and set the DISPLAY environment variable when this flag is enabled, and includes comprehensive tests to ensure correct behavior in various scenarios.

X11 clipboard support for image pasting:

  • Added a new _x11_volumes_and_env helper to return the X11 socket mount and DISPLAY environment variable for containerized agents.
  • Introduced a --paste-images flag to the run command, enabling X11 clipboard support for image pasting if the DISPLAY environment variable is set.
  • Updated the run command logic to mount the X11 socket and inject the DISPLAY variable into the container environment when --paste-images is used and DISPLAY is set; otherwise, a warning is shown and X11 is skipped.

Testing enhancements:

  • Added tests for _x11_volumes_and_env and for the --paste-images flag, covering cases where DISPLAY is set, unset, and when the flag is not used, to ensure correct mounting and environment handling.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added --paste-images command-line option to enable pasting images via X11 clipboard.
    • Requires DISPLAY environment variable to be set; displays warning if not configured and skips the feature gracefully.

@coderabbitai

coderabbitai Bot commented Mar 15, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The changes add X11 clipboard support to the run command, enabling image pasting into containers. A new --paste-images flag detects the DISPLAY environment variable and forwards X11 socket volumes and DISPLAY configuration to containers when available, with graceful degradation when DISPLAY is unset.

Changes

Cohort / File(s) Summary
X11 Paste-Image Support
src/vibepod/commands/run.py
Introduces _x11_volumes_and_env() helper function and adds --paste-images CLI option to the run command. When enabled and DISPLAY is set, X11 socket volumes and environment are forwarded to containers; otherwise, a warning is emitted and feature is skipped.
X11 Feature Tests
tests/test_run.py
Adds comprehensive test coverage for _x11_volumes_and_env() function, paste_images flag behavior with DockerManager, DISPLAY environment handling, and verification of volume injection into container configuration.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 Whiskers twitch with glee, a clipboard we shall share,
X11 sockets forwarded with utmost care,
Paste those images swift, from display to screen,
The finest clipboard magic we've ever seen! 🎨✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add option to enable image pasting via X11 clipboard' clearly and concisely summarizes the main change—adding a new --paste-images option to enable X11-based image pasting functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch issue-28
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Mar 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds a --paste-images flag to the run command that mounts the host X11 socket (/tmp/.X11-unix) and forwards the DISPLAY environment variable into the agent container, enabling clipboard-based image pasting. The implementation is clean and well-tested, but there are a couple of correctness and safety concerns to address before merging.

Key changes:

  • New _x11_volumes_and_env(display) helper returns the X11 bind-mount and DISPLAY env entry.
  • --paste-images flag (default False) added to run(); when set, the flag reads DISPLAY from the host environment, warns gracefully if unset, and otherwise extends extra_volumes and merged_env with the X11 data.
  • Three integration tests and two unit tests cover the happy path, the no-DISPLAY warning path, and the opt-out default.

Issues found:

  • The primary integration test (test_paste_images_flag_adds_x11_volumes_and_env) asserts the socket mount is present but never checks that DISPLAY is injected into the env dict passed to run_agent. This gap means the merged_env.update(x11_env) line could be silently removed without any test catching it.
  • The code validates that DISPLAY is set but does not verify that /tmp/.X11-unix actually exists on the host before attempting to mount it. A remote DISPLAY value (e.g. remote-host:0) would cause Docker to silently mount an empty directory or fail with an opaque error.
  • Mounting the X11 socket grants the container unrestricted access to the entire host display — keylogging, screenshots, synthetic input injection — which is a significant privilege for an agent container and deserves an explicit runtime warning or documentation note.

Confidence Score: 3/5

  • Safe to merge after fixing the missing env assertion in the test and adding an existence check for /tmp/.X11-unix; the X11 security surface should at minimum be documented.
  • The feature logic is correct for the happy path, the warning on missing DISPLAY is handled, and tests cover the main branches. However, a test coverage gap (DISPLAY not asserted in env dict) means the env injection half of the feature is untested, and the absence of a socket-path existence check can produce silent runtime failures. The X11 privilege concern does not block merging but warrants acknowledgement.
  • tests/test_run.py (missing env assertion) and src/vibepod/commands/run.py (missing socket path validation)

Important Files Changed

Filename Overview
src/vibepod/commands/run.py Adds _x11_volumes_and_env helper and --paste-images flag; logic correctly gates X11 mounting on DISPLAY being set, but the socket path /tmp/.X11-unix is not validated for existence before mounting, and the security implications of full X11 display access are undocumented.
tests/test_run.py Adds well-structured tests for the new --paste-images flag and helper, covering DISPLAY set/unset/default cases; however, the primary integration test is missing an assertion that the DISPLAY env var is actually injected into the container environment, leaving a gap where the env injection code could be silently broken.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["run() called with --paste-images"] --> B{paste_images == True?}
    B -- No --> E[Continue without X11]
    B -- Yes --> C{DISPLAY env var set?}
    C -- No --> D["warning: skipping X11 forwarding"]
    C -- Yes --> F["_x11_volumes_and_env(display)"]
    F --> G["volumes: /tmp/.X11-unix → /tmp/.X11-unix rw"]
    F --> H["env: DISPLAY=<value>"]
    G --> I["extra_volumes.extend(x11_vols)"]
    H --> J["merged_env.update(x11_env)"]
    I --> K["manager.run_agent(..., extra_volumes, env=merged_env)"]
    J --> K
    D --> E
    E --> K
Loading

Last reviewed commit: 61b48e1

Comment thread tests/test_run.py
Comment on lines +225 to +227
assert ("/tmp/.X11-unix", "/tmp/.X11-unix", "rw") in captured.get(
"extra_volumes", []
), f"X11 socket not found in extra_volumes: {captured.get('extra_volumes')}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Test does not assert DISPLAY env var is injected

test_paste_images_flag_adds_x11_volumes_and_env only verifies that the X11 socket appears in extra_volumes, but it never checks that DISPLAY is present in the env dict passed to run_agent. The captured kwargs already contain env (it is passed as env=merged_env), so the assertion can be added alongside the volume check.

Without this assertion, the merged_env.update(x11_env) line in run.py could be silently deleted and this test would still pass, leaving the container missing the DISPLAY variable entirely.

Suggested change
assert ("/tmp/.X11-unix", "/tmp/.X11-unix", "rw") in captured.get(
"extra_volumes", []
), f"X11 socket not found in extra_volumes: {captured.get('extra_volumes')}"
assert ("/tmp/.X11-unix", "/tmp/.X11-unix", "rw") in captured.get(
"extra_volumes", []
), f"X11 socket not found in extra_volumes: {captured.get('extra_volumes')}"
assert captured.get("env", {}).get("DISPLAY") == ":0", (
f"DISPLAY not injected into container env: {captured.get('env')}"
)

Comment on lines +309 to +316
if paste_images:
display = os.environ.get("DISPLAY", "")
if not display:
warning("--paste-images requires DISPLAY to be set; skipping X11 forwarding")
else:
x11_vols, x11_env = _x11_volumes_and_env(display)
extra_volumes.extend(x11_vols)
merged_env.update(x11_env)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No validation that /tmp/.X11-unix exists before mounting

DISPLAY being set does not guarantee the X11 socket directory exists on the host. Remote display values (e.g. DISPLAY=remote-host:0) are perfectly valid and will have no corresponding local socket. If /tmp/.X11-unix does not exist, Docker will either silently create an empty directory and mount it (useless for X11) or fail with an opaque error, with no helpful message to the user.

Consider checking path existence before adding the mount:

if paste_images:
    display = os.environ.get("DISPLAY", "")
    if not display:
        warning("--paste-images requires DISPLAY to be set; skipping X11 forwarding")
    elif not Path("/tmp/.X11-unix").exists():
        warning("--paste-images: /tmp/.X11-unix not found; skipping X11 forwarding")
    else:
        x11_vols, x11_env = _x11_volumes_and_env(display)
        extra_volumes.extend(x11_vols)
        merged_env.update(x11_env)

Comment on lines +152 to +156
def _x11_volumes_and_env(display: str) -> tuple[list[tuple[str, str, str]], dict[str, str]]:
"""Return X11 socket volumes and DISPLAY env for paste-image support."""
volumes: list[tuple[str, str, str]] = [("/tmp/.X11-unix", "/tmp/.X11-unix", "rw")]
env: dict[str, str] = {"DISPLAY": display}
return volumes, env

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

X11 socket mounted read-write grants full display access to container

Mounting /tmp/.X11-unix into the agent container with rw mode gives the container unrestricted access to the host X11 display. This goes well beyond clipboard reading — the container process can capture keystrokes from any X11 window, take screenshots, and inject synthetic input events into other applications running on the same display.

For a tool that executes untrusted or AI-generated code inside containers, this is a meaningful privilege escalation. At a minimum, the help text and/or a runtime warning should inform the user of the full scope of access being granted. Functionally, connecting to an X11 socket does require a writable file-descriptor to the socket file, so ro is not a workable alternative here; the concern is primarily one of documentation and informed consent.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
tests/test_run.py (1)

185-227: Consider extracting the duplicated stub class and adding DISPLAY env assertion.

The _CapturingDockerManager class is duplicated three times. Also, the tests verify extra_volumes but don't assert that DISPLAY is passed in the container's env dictionary when paste_images=True.

♻️ Proposed refactor to reduce duplication and improve assertions

Extract the capturing manager and add an env assertion:

class _CapturingDockerManager:
    """Stub that captures run_agent kwargs."""

    def __init__(self) -> None:
        self.captured: dict = {}

    def ensure_network(self, name: str) -> None:
        pass

    def networks_with_running_containers(self) -> list[str]:
        return []

    def pull_image(self, image: str) -> None:
        pass

    def ensure_proxy(self, **kwargs) -> None:
        pass

    def run_agent(self, **kwargs) -> object:
        self.captured.update(kwargs)
        container = type(
            "_Container",
            (),
            {
                "name": "vibepod-claude-test",
                "id": "abc123",
                "status": "running",
                "attrs": {"NetworkSettings": {"Networks": {}}},
                "reload": lambda self: None,
                "labels": {},
                "logs": lambda self, **kw: b"",
            },
        )()
        return container

Then in test_paste_images_flag_adds_x11_volumes_and_env, add:

assert captured.get("env", {}).get("DISPLAY") == ":0", "DISPLAY should be passed to container env"

Also applies to: 230-271, 274-315

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_run.py` around lines 185 - 227, Tests duplicate the
_CapturingDockerManager stub and only assert extra_volumes; refactor by
extracting a single reusable _CapturingDockerManager that stores kwargs (e.g.,
self.captured updated in run_agent) and replace the three in-test copies with
that class, and add an assertion in
test_paste_images_flag_adds_x11_volumes_and_env to check that
captured.get("env", {}).get("DISPLAY") == ":0" so the DISPLAY env is passed when
paste_images=True; keep references to run_agent, captured, and extra_volumes
when making the changes.
src/vibepod/commands/run.py (1)

309-316: Consider adding X11 authentication forwarding for broader compatibility.

The current implementation mounts the X11 socket and passes DISPLAY, which works for simple cases. However, many X11 setups also require XAUTHORITY for authentication. Without it, containers may fail to connect to the X server with "authorization required" errors.

♻️ Proposed enhancement to support XAUTHORITY

In _x11_volumes_and_env:

 def _x11_volumes_and_env(display: str) -> tuple[list[tuple[str, str, str]], dict[str, str]]:
     """Return X11 socket volumes and DISPLAY env for paste-image support."""
     volumes: list[tuple[str, str, str]] = [("/tmp/.X11-unix", "/tmp/.X11-unix", "rw")]
     env: dict[str, str] = {"DISPLAY": display}
+    xauthority = os.environ.get("XAUTHORITY")
+    if xauthority and Path(xauthority).exists():
+        volumes.append((xauthority, xauthority, "ro"))
+        env["XAUTHORITY"] = xauthority
     return volumes, env
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/vibepod/commands/run.py` around lines 309 - 316, The current X11
forwarding only mounts the X11 socket and sets DISPLAY, which fails for setups
that require XAUTHORITY; update the paste_images handling and the helper
_x11_volumes_and_env to detect and forward XAUTHORITY: when paste_images is true
and DISPLAY is set, check for XAUTHORITY env var (or default to ~/.Xauthority),
if the file exists add its path to extra_volumes (bind-mount) and set
merged_env["XAUTHORITY"] accordingly; ensure _x11_volumes_and_env returns both
the socket volume list and any XAUTHORITY volume plus the env mapping so code
that currently uses x11_vols, x11_env can mount and export the auth file in
addition to DISPLAY.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/vibepod/commands/run.py`:
- Around line 309-316: The current X11 forwarding only mounts the X11 socket and
sets DISPLAY, which fails for setups that require XAUTHORITY; update the
paste_images handling and the helper _x11_volumes_and_env to detect and forward
XAUTHORITY: when paste_images is true and DISPLAY is set, check for XAUTHORITY
env var (or default to ~/.Xauthority), if the file exists add its path to
extra_volumes (bind-mount) and set merged_env["XAUTHORITY"] accordingly; ensure
_x11_volumes_and_env returns both the socket volume list and any XAUTHORITY
volume plus the env mapping so code that currently uses x11_vols, x11_env can
mount and export the auth file in addition to DISPLAY.

In `@tests/test_run.py`:
- Around line 185-227: Tests duplicate the _CapturingDockerManager stub and only
assert extra_volumes; refactor by extracting a single reusable
_CapturingDockerManager that stores kwargs (e.g., self.captured updated in
run_agent) and replace the three in-test copies with that class, and add an
assertion in test_paste_images_flag_adds_x11_volumes_and_env to check that
captured.get("env", {}).get("DISPLAY") == ":0" so the DISPLAY env is passed when
paste_images=True; keep references to run_agent, captured, and extra_volumes
when making the changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c9571d6c-0644-44d1-945d-65adc195d7a2

📥 Commits

Reviewing files that changed from the base of the PR and between 1e8039a and 61b48e1.

📒 Files selected for processing (2)
  • src/vibepod/commands/run.py
  • tests/test_run.py

@nezhar nezhar merged commit 291abfb into main Mar 15, 2026
20 checks passed
@nezhar nezhar deleted the issue-28 branch March 20, 2026 13:55
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.

1 participant