Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
python - <<'PY'
import ast
import os
import re
import tomllib
from pathlib import Path

Expand Down Expand Up @@ -72,6 +73,19 @@ jobs:
f"module version {module_version!r} does not match project version "
f"{project_version!r}"
)

manifest_text = Path("plugin.yaml").read_text(encoding="utf-8")
manifest_match = re.search(
r'^version:\s*["\']?([^"\'\s]+)["\']?\s*$',
manifest_text,
re.MULTILINE,
)
manifest_version = manifest_match.group(1) if manifest_match else None
if manifest_version != project_version:
raise SystemExit(
f"plugin manifest version {manifest_version!r} does not match "
f"project version {project_version!r}"
)
PY

- name: Install release checks
Expand Down
27 changes: 26 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,30 @@ uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.2.0] - 2026-08-05

### Added

- Add a native Hermes v0.20 directory-plugin entry point with `hermes plugin-guard scan`,
`installed`, and `rules` operator commands while preserving the existing `hpg` CLI.
- Add the read-only `plugin_guard_review_candidate` model tool for installed, disabled plugins.
- Bind the native model-tool review to private before-and-after endpoint digests and reject
traversal, symlinks, special files, executable binaries, oversized trees, excessive depth,
unreadable activation state, and endpoint changes detected during review.

### Security

- Restrict the model tool to canonical candidates below `HERMES_HOME/plugins`; arbitrary paths,
enabled plugins, rule exclusions, output files, and threshold overrides are not exposed.
- Return only a bounded projection of findings, omitting source, messages, evidence, dependency
strings, filenames, secrets, internal digests, and absolute paths from the model conversation.
- Run strict native scans in a single resource-bounded worker, include runtime-capable directories
that the general-purpose CLI normally ignores, and fail closed when analysis is incomplete.
- Reject plugin families that Hermes can load through memory, cron-scheduler, or model-provider
paths outside the general `plugins.enabled` allow-list.
- Document that the native integration is a review aid rather than an install or activation gate;
Hermes v0.20 does not expose a plugin-admission lifecycle hook.

## [0.1.4] - 2026-08-01

### Added
Expand Down Expand Up @@ -95,7 +119,8 @@ uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Configurable failure thresholds and per-rule command-line exclusions.
- Composite GitHub Action and pinned continuous-integration workflow.

[Unreleased]: https://github.com/mauricemohr88-debug/hermes-plugin-guard/compare/v0.1.4...HEAD
[Unreleased]: https://github.com/mauricemohr88-debug/hermes-plugin-guard/compare/v0.2.0...HEAD
[0.2.0]: https://github.com/mauricemohr88-debug/hermes-plugin-guard/compare/v0.1.4...v0.2.0
[0.1.4]: https://github.com/mauricemohr88-debug/hermes-plugin-guard/compare/v0.1.3...v0.1.4
[0.1.3]: https://github.com/mauricemohr88-debug/hermes-plugin-guard/compare/v0.1.2...v0.1.3
[0.1.2]: https://github.com/mauricemohr88-debug/hermes-plugin-guard/compare/v0.1.1...v0.1.2
Expand Down
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
include __init__.py
include CHANGELOG.md
include CODE_OF_CONDUCT.md
include CONTRIBUTING.md
include plugin.yaml
include SECURITY.md
recursive-include docs *.svg
recursive-include tests *.json *.py *.txt *.yaml
Expand Down
64 changes: 56 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ basic repository hygiene. Target plugin code is read as data: it is never import
This is an unofficial community project. It is not affiliated with, endorsed by, or maintained
by Nous Research.

![20-second terminal demo of installing and running Hermes Plugin Guard](https://raw.githubusercontent.com/mauricemohr88-debug/hermes-plugin-guard/v0.1.4/docs/demo.gif)
![20-second CLI demo recorded with Hermes Plugin Guard v0.1.4](https://raw.githubusercontent.com/mauricemohr88-debug/hermes-plugin-guard/v0.1.4/docs/demo.gif)

## Five-minute beta test

Expand All @@ -40,16 +40,18 @@ Python 3.11 or newer and [pipx](https://pipx.pypa.io/stable/) are required.
with the rule IDs that were useful, noisy, or missing. A public plugin URL is helpful but not
required.

The scan stays on your computer. `hpg` reads target files as data, does not import or execute
target plugin code, makes no network requests, includes no telemetry, and uploads neither source
code nor results. Do not paste private code, credentials, or unsanitized paths into a public issue.
The standalone `hpg` scan stays on your computer. It reads target files as data, does not import
or execute target plugin code, makes no network requests, includes no telemetry, and uploads
neither source code nor results. The optional native Hermes tool has a different, explicitly
documented privacy boundary below. Do not paste private code, credentials, or unsanitized paths
into a public issue.

Already installed? Use `pipx upgrade hermes-plugin-guard`. For a reproducible installation
directly from the tagged source, install the v0.1.4 GitHub release:
directly from the tagged source, install the v0.2.0 GitHub release:

```bash
pipx install \
"git+https://github.com/mauricemohr88-debug/hermes-plugin-guard.git@v0.1.4"
"git+https://github.com/mauricemohr88-debug/hermes-plugin-guard.git@v0.2.0"
```

## Why this exists
Expand Down Expand Up @@ -86,7 +88,7 @@ Alternatively, install reproducibly from the tagged GitHub source:

```bash
pipx install \
"git+https://github.com/mauricemohr88-debug/hermes-plugin-guard.git@v0.1.4"
"git+https://github.com/mauricemohr88-debug/hermes-plugin-guard.git@v0.2.0"
```

Or install from a local checkout:
Expand All @@ -99,6 +101,52 @@ python -m pip install .

Both `hpg` and `hermes-plugin-guard` invoke the same command.

## Native Hermes v0.20 integration

Hermes Plugin Guard can also be installed as a native, review-only Hermes plugin from Git. Keep
it disabled during installation, then enable the guard itself without granting tool-override
permission:

```bash
hermes plugins install mauricemohr88-debug/hermes-plugin-guard --no-enable
hermes plugins enable hermes-plugin-guard --no-allow-tool-override
```

In a new Hermes process you can use the operator CLI:

```bash
hermes plugin-guard rules
hermes plugin-guard scan /path/to/a/plugin --fail-on high
```

The safest manual review sequence for another Git plugin is:

```bash
hermes plugins install owner/repository --no-enable
hermes plugin-guard installed plugin-name --fail-on high
hermes plugins enable plugin-name --no-allow-tool-override
```

Hermes also receives one read-only model tool, `plugin_guard_review_candidate`. It accepts only an
installed plugin key below `HERMES_HOME/plugins`, refuses currently enabled plugins, applies fixed
high-severity policy, and returns at most 20 findings. The strict native path scans runtime-capable
directories such as `tests`, `build`, and `generated`, rejects unsupported executable binaries,
and runs the analyzer in a single resource-bounded worker. Its response contains only rule IDs,
severity, opaque location IDs, bounded line numbers, and bounded counts; it omits filenames,
source, finding messages, evidence, dependency strings, secrets, internal tree digests, and
absolute paths. That bounded response becomes part of the active Hermes conversation and may
therefore be sent to the configured model provider. Exclusive, model-provider, and
Hermes-detected legacy memory or cron-scheduler plugins use separate activation paths. Those
candidates are rejected by this tool rather than being incorrectly described as disabled.

This integration does **not** intercept or replace Hermes' native install, update, enable, or load
paths. Hermes v0.20 has no third-party plugin-admission hook, so the guard cannot honestly enforce
a scan before every activation. The native surface is a convenient review step; the operator still
makes the activation decision. Its private before/after digest detects endpoint changes during a
review, but it is not an atomic filesystem snapshot or sandbox. A generic
[upstream admission-policy proposal](https://github.com/NousResearch/hermes-agent/issues/64182#issuecomment-5078498045)
is already registered with the Hermes plugin-interface tracker.

## Usage

Scan one plugin directory:
Expand Down Expand Up @@ -182,7 +230,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: mauricemohr88-debug/hermes-plugin-guard@v0.1.4
- uses: mauricemohr88-debug/hermes-plugin-guard@v0.2.0
with:
path: path/to/plugin
fail-on: high
Expand Down
13 changes: 13 additions & 0 deletions __init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Hermes directory-plugin entry point.

Hermes loads a Git-installed plugin directly from the repository root. Keep
this shim relative so it always uses the scanner shipped in the same checkout
instead of an unrelated globally installed package.
"""

if __package__:
from .src.hermes_plugin_guard.hermes_plugin import register
else: # Imported directly by repository tooling such as pytest collection.
from hermes_plugin_guard.hermes_plugin import register

__all__ = ["register"]
4 changes: 2 additions & 2 deletions docs/demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,14 @@ def main() -> None:
pause(450, cursor=True)
type_line("$ pipx install hermes-plugin-guard", CYAN, step=1, delay=80)
pause(850, cursor=True)
reveal(" installed package hermes-plugin-guard 0.1.4", MUTED, duration=650)
reveal(" installed package hermes-plugin-guard 0.2.0", MUTED, duration=650)
reveal(" apps now available: hpg, hermes-plugin-guard", GREEN, duration=850)
reveal("", duration=350)

# The report below is the exact text format produced by the safe fixture.
type_line("$ hpg scan ~/.hermes/plugins/my-plugin", CYAN, step=1, delay=70)
pause(1_150, cursor=True)
reveal("hermes-plugin-guard 0.1.4", MUTED, duration=500)
reveal("hermes-plugin-guard 0.2.0", MUTED, duration=500)
reveal("Scanned 1 plugin(s), 4 file(s) — 0 finding(s)", TEXT, duration=600)
reveal("Summary: no findings", MUTED, duration=600)
reveal("Result: PASS (no finding at or above high)", GREEN, duration=950)
Expand Down
11 changes: 6 additions & 5 deletions docs/releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ distribution.

## Release checklist

1. Update the version in `pyproject.toml` and `src/hermes_plugin_guard/__init__.py`.
1. Update the version in `pyproject.toml`, `src/hermes_plugin_guard/__init__.py`, and
`plugin.yaml`.
2. Move the relevant changelog entries from `Unreleased` into the dated release section.
3. Run the local release checks:

Expand All @@ -50,7 +51,7 @@ distribution.

4. Merge the release commit and wait for CI to pass on the default branch.
5. Create and publish a GitHub Release using a matching `vMAJOR.MINOR.PATCH` tag. For package
version `0.1.1`, the tag must be `v0.1.1`.
version `0.2.0`, the tag must be `v0.2.0`.
6. Confirm that the `Publish to PyPI` workflow completed successfully. When publishing is enabled,
also confirm that the project page lists both the wheel and source distribution. Before then,
the publish job should show as skipped.
Expand All @@ -60,9 +61,9 @@ distribution.
pipx run hermes-plugin-guard --version
```

The workflow rejects mismatched release tags, `pyproject.toml` versions, and module versions. It
also reruns linting and tests, validates distribution metadata, and separates the unprivileged
build job from the OIDC-enabled publish job.
The workflow rejects mismatched release tags, `pyproject.toml` versions, module versions, and
native `plugin.yaml` versions. It also reruns linting and tests, validates distribution metadata,
and separates the unprivileged build job from the OIDC-enabled publish job.

## Recovery

Expand Down
2 changes: 1 addition & 1 deletion docs/social-card.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions plugin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
manifest_version: 1
name: hermes-plugin-guard
version: "0.2.0"
description: "Static review of candidate Hermes plugins; target code is never executed."
author: "Maurice Mohr"
kind: standalone
provides_tools:
- plugin_guard_review_candidate
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "hermes-plugin-guard"
version = "0.1.4"
version = "0.2.0"
description = "Static, no-execution security checks for Hermes Agent plugins"
readme = "README.md"
requires-python = ">=3.11"
Expand Down
2 changes: 1 addition & 1 deletion src/hermes_plugin_guard/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@
from .scanner import scan

__all__ = ["Finding", "ScanResult", "Severity", "scan"]
__version__ = "0.1.4"
__version__ = "0.2.0"
Loading