All plugins

hermes-wslc-sandbox

Run Hermes' terminal, file and execute_code tools in a Linux container on Microsoft's WSL Containers (wslc) runtime, so Windows users who run WSL Containers get a sandbox without Docker Desktop. Disclosure — the plugin spawns the wslc CLI (one short-lived process per command, plus one long-lived container per environment) and every call runs inside that container; containers have outbound network access unless docker_network is false; persistent sandboxes are left stopped on disk until removed; host environment variables enter the container only when named in docker_forward_env, and only paths listed in docker_volumes are bind-mounted. wslc rejects --cap-drop, --security-opt, --pids-limit, --init and --storage-opt, so this sandbox is not capability-dropped the way the Docker backend's is, container_disk is reported but not enforced, and dangerous-command approval deliberately stays enabled for this backend. The plugin reads no credentials, stores none, and makes no network calls of its own.

Yicheng-MarkToolsv0.1.0Updated 12 days ago

About

Run Hermes' terminal, file and execute_code tools in a Linux container on Microsoft's WSL Containers (wslc) runtime, so Windows users who run WSL Containers get a sandbox without Docker Desktop. Disclosure — the plugin spawns the wslc CLI (one short-lived process per command, plus one long-lived container per environment) and every call runs inside that container; containers have outbound network access unless docker_network is false; persistent sandboxes are left stopped on disk until removed; host environment variables enter the container only when named in docker_forward_env, and only paths listed in docker_volumes are bind-mounted. wslc rejects --cap-drop, --security-opt, --pids-limit, --init and --storage-opt, so this sandbox is not capability-dropped the way the Docker backend's is, container_disk is reported but not enforced, and dangerous-command approval deliberately stays enabled for this backend. The plugin reads no credentials, stores none, and makes no network calls of its own.

hermes-wslc-sandbox

Run Hermes' terminal, file and execute_code tools inside a WSL Containers (wslc)
sandbox — a Linux container managed by Microsoft's container runtime for WSL, with no Docker
Desktop involved.

# config.yaml
terminal:
  backend: wslc

Requires Windows. WSL Containers is a Windows feature; on any other platform the backend
reports itself unavailable rather than failing at command time.

Why this exists

terminal.backend: docker on Windows needs a Docker-compatible engine, which in practice
means Docker Desktop. Windows users who already run WSL Containers had no supported way to
use it as Hermes' sandbox — the only workaround was an unofficial docker.exe shim, which
breaks whenever Hermes changes how it calls the CLI. This plugin is the third-party
integration path the terminal environment provider
guide

describes: it registers wslc as a backend, so the config value above works like any
built-in one.

Requirements

  • Windows 10/11 with WSL 3.0.3+ (wsl --update, then wslc --version should work).
  • A Linux container image. WSL Containers does not run Windows images, and it pulls from
    your own registry configuration — it does not read Docker Desktop's settings. Pre-pull with
    wslc pull <image> if your registry needs credentials.

Install

Hermes resolves wslc.exe through HERMES_WSLC_BINARY, then PATH, then
C:\Program Files\WSL\wslc.exe.

git clone --depth 1 https://github.com/Yicheng-Mark/hermes-windows-plugins /tmp/hermes-windows-plugins
cp -r /tmp/hermes-windows-plugins/wslc-sandbox ~/.hermes/plugins/hermes-wslc-sandbox
hermes plugins enable hermes-wslc-sandbox
hermes config set terminal.backend wslc
hermes doctor          # shows the wslc rows, including what the sandbox cannot harden

Configuration

The backend reads the same container-config keys every Hermes container backend gets, so it
needs no new settings:

| Key | Effect |
|---|---|
| terminal.container_image | The Linux image to run. |
| container_cpu, container_memory | --cpus, --memory. |
| container_persistent | Keep the container between sessions (see below). |
| docker_volumes | Bind mounts, e.g. - D:\work\repo:/workspace. Read-write unless you append :ro. |
| docker_env, docker_forward_env | Variables for the container. Only names you list are copied from the host environment. |
| docker_network | false runs the container with --network none. |
| container_disk | Reported, not enforced — wslc has no --storage-opt. |

What the sandbox does and does not do

Does: one container per environment, kept alive with sleep infinity and driven through
wslc exec. Commands, file tools and execute_code run inside it; the working directory
persists across calls; stdin, exit codes and merged output behave the way the core expects.

Persistence uses the container's own writable layer. With container_persistent: true the
sandbox is stopped, not deleted, at teardown, and the next session starts the same
container and finds its files. Non-persistent environments are removed. List leftovers with
wslc ps -a; remove one with wslc rm -f <name>. Containers this plugin owns carry
hermes.backend=wslc labels.

Not hardened the way Docker is. wslc run rejects --cap-drop, --security-opt,
--pids-limit, --init and --storage-opt (all verified against 3.0.1; the backend probes
run --help rather than assuming). A wslc sandbox therefore does not drop capabilities or
set no-new-privileges, and dangerous-command approval deliberately stays enabled for
this backend — skip_container_guards is False, unlike the container backends that can
harden themselves. If you bind-mount host paths, the container can write to them.

Disclosure

  • The plugin spawns the wslc CLI: one short-lived process per command, plus one long-lived
    container per environment. --rm is not used, so stopped persistent sandboxes stay on disk
    until you remove them.
  • Containers have outbound network access unless docker_network: false.
  • Host environment variables enter the container only when named in docker_forward_env;
    bind mounts expose only the paths you list in docker_volumes.
  • The plugin reads no credentials, stores none, and makes no network calls of its own;
    image pulls are performed by wslc against your registry configuration.
  • Nothing is sent to third parties, and there is no telemetry.
  • It imports Hermes' own spawn helper (tools.environments.base_output._popen_bash) and
    bash_argv, the same seam the in-tree backends use. It does not patch, wrap or rebind any
    core function or module attribute.

Development

pip install -e ".[test]"
python -m pytest tests/ -q                     # unit tests: no Hermes, no wslc needed

# Contract check against a real Hermes checkout:
python scripts/check_hermes_contract.py --hermes-source D:\path\to\hermes-agent

# End-to-end against a real runtime and a locally present image:
set HERMES_WSLC_LIVE=1
set HERMES_WSLC_TEST_IMAGE=<image already pulled with `wslc pull`>
set HERMES_SOURCE=D:\path\to\hermes-agent
python -m pytest tests/test_live_wslc.py -v

tests/test_wslc_cli.py pins the CLI contract with output captured from a real wslc —
including the localized help text and the metadata blob it stores in its own label. See
VALIDATION.md for exactly what was verified, on which versions.

Layout

__init__.py                  plugin entry: register(ctx)
plugin.yaml                  manifest (kind: backend)
hermes_wslc_sandbox/
  wslc.py                    CLI adapter — stdlib only, unit-tested without Hermes
  config.py                  container naming and env merging — stdlib only
  environment.py             WslcEnvironment(BaseEnvironment): lifecycle + _run_bash
  provider.py                WslcProvider: classification, probe, factory
tests/                       unit tests (always) + live tests (opt-in)
scripts/check_hermes_contract.py

Licence: MIT.

README from the reviewed commit 5bad3fb; it updates when the author re-pins.

More by Yicheng-Mark

15 plugins by Yicheng-Mark in the catalog.

Yicheng-Markhermes-dpapi-secretsResolve Hermes environment variables at startup from a single secret file encrypted with the Windows Data Protection API, instead of keeping provider keys in plaintext in .env or auth.json. Config is secrets.dpapi.map, an ENV_VAR -> store-name mapping (bare names or dpapi:// names) plus an optional file path (default <HERMES_HOME>/secrets.dpapi). The file is one DPAPI blob keyed by the current Windows user profile, so it is unreadable by another Windows account and worthless off the machine; it is written by the bundled CLI (python -m hermes_dpapi_secrets set NAME, value read from a prompt or stdin, never from argv) and only read at runtime. Mapped names that are missing or empty are skipped with a warning; a file that is not ours, a blob this user cannot decrypt, or a payload that is not a flat string map is reported as an error with nothing injected. Disclosure - this source decrypts a file for every Hermes process that enables it: resolved values become environment variables of the Hermes process and its children, exactly like .env entries, so anything that can read that environment can see them. User-scope encryption only (the machine-wide flag is never used and is not exposed), and the at-rest protection is a bar for backups, cloud sync and dotfiles, not a boundary against code already running as the same user. Stdlib-only (ctypes): no Python dependencies, no subprocesses, no network calls, no self-update. Windows only. Applying fetched values stays the orchestrator's job; the plugin never writes os.environ itself. Disclosure — decrypts a DPAPI-encrypted secret file (default <HERMES_HOME>/secrets.dpapi) at startup for every Hermes process that enables it, and the resolved values become environment variables of Hermes and its child processes like `.env` entries; the encryption is per Windows user and protects backups, cloud sync and copied files, not against other code running as the same user. Local only: no network calls, no subprocesses.CommunitytoolsYicheng-Markhermes-wincredResolve Hermes environment variables from the current user's Windows Credential Manager at startup, instead of keeping provider keys in plaintext in .env or auth.json. Config is secrets.wincred.map, an ENV_VAR -> generic-credential-target mapping (bare names or wincred:// names); each value is read with CredReadW in the current user's vault and handed to the secret-source orchestrator, which applies it with the usual precedence, provenance and conflict rules. Values may be UTF-8 or UTF-16LE (cmdkey and the Credential UI write UTF-16); anything that does not decode to text, or that contains interior NULs, is skipped with a warning instead of being injected as a mangled key. Disclosure - this source reads your Windows Credential Manager: only the generic credentials named in the map, with no enumeration of the vault. Resolved values become environment variables of the Hermes process and its children, exactly like .env entries, so anything that can read that environment can see them. The package is read-only and stdlib-only (ctypes): it never creates, updates or deletes credentials, writes no files, starts no subprocesses, makes no network calls and does not self-update. Windows only. Credential Manager is user-scoped, so this raises the at-rest bar for backups, cloud sync and dotfiles; it is not a boundary against code already running as the same user. Applying fetched values stays the orchestrator's job; the plugin never writes os.environ itself. Disclosure — when enabled, reads only the Windows Credential Manager generic credentials named in secrets.wincred.map (no vault enumeration) and injects them as environment variables of the Hermes process and its children, exactly like .env entries; read-only, stdlib ctypes, no subprocesses, no network, no file writes; user-scoped at-rest protection, not a boundary against code already running as the same user.CommunitytoolsYicheng-Markhermes-windows-appsAnswer "what is installed on this Windows machine": the software inventory behind Apps & Features, swept straight from the four Uninstall registry keys (the machine's 64-bit and 32-bit views plus the user's), so a program is found even when the Settings page hides it - a per-user install that never shows on the machine page came from the user view on the writing machine. Each row carries version, publisher, size in MB, install date and which view it lives in; the same DisplayName in two views is a 32/64-bit dual install, marked instead of listed twice. Entries the publisher asked to hide (runtime packages, update rollups) are filtered out unless asked for. Details one program with every field its publisher wrote and a reading of the uninstall command (MSI product code, setup executable, appx, script) so the next step is known before anything is run; the install folder is checked against disk. Use it for "what did that installer actually register", for inventory before a cleanup, and for "which of the two installs does uninstalling remove". Read-only: registry keys are enumerated and uninstall strings are reported, never run; no network, no file writes, no elevation. The sweep is pure registry - 102 entries in seven milliseconds on the writing machine, the whole table in ten, no PowerShell, no WMI. The plugin declares no Python dependencies. Windows only. Disclosure - the tools report program names, versions, publishers, sizes, install folders and uninstall commands, and what is hidden from Apps & Features, to the model; treat the output like the machine's own Apps & Features page.CommunitytoolsYicheng-Markhermes-windows-devicesAnswer "which device on this Windows machine is unhappy, and what does it need" on Windows: win_device_table lists every present PnP device with its class (System, Net, HIDClass, Display), its status, its manufacturer, its stable device id, and its problem code in words - 28 is one with no driver installed, 10 one that cannot start, 43 one Windows stopped after it reported a failure, 22 one the person switched off on purpose and is not broken - filtering by name or id substring, by class, or to the devices that need attention; devices no longer plugged in are history, left out unless asked for. win_device_detail takes one device by id or name substring and adds the driver behind it from the signed-driver table - name, version, date, provider, signer, INF file, signed or not - or the absence of one, which for code 28 is the same fact said twice; a disabled device gets a note saying it is on purpose, not an alarm. Use it for the Device Manager yellow-mark question without opening Device Manager, for finding what an uninstalled program left behind, and for what changed after an update. Read-only: nothing is enabled, disabled, updated or reinstalled. Both answers come from CIM (Win32_PnPEntity and Win32_PnPSignedDriver, joined by device id), whose property names, enum values and ids do not change with the display language; the human-facing device names localize and are carried through as data, never parsed. The plugin declares no Python dependencies; there is no network call and no file write. Windows only. Disclosure — the tools report device names, manufacturers, device ids and driver versions to the model, and those reveal the machine's hardware inventory and what is plugged into it; treat the output like a Device Manager window opened on their machine.Communitytools

Developer Mode

Explore built-in Hermes toolsets and actions.

HermesAgent

Developer?

Create a Hermes extension for Desktop or agent workflows.

Developer Section