Hermes's 14-day `[tool.uv] exclude-newer` quarantine applies to Hermes's own dependencies only (uv lock/sync, `hermes update`, LAZY_DEPS extras via `ensure()`). A plugin's declared `python_dependencies` install under the PLUGIN's policy: `install_specs(policy="plugin")` runs uv with `--no-config` from any cwd, still inside the core constraints file. Reverses item 3 of #118841, which ran the uv tier with cwd=<checkout> for every install so the quarantine reached plugin deps from any cwd. That made catalog re-pins floored on a <14-day release uninstallable (#120076: "only hindsight-client<=0.9.2 is available"; #114530 held on the same gate). Maintainer ruling (Teknium): "plugins dont have to abide by our 14 day rule btw. They can have their own security policy on that. Only hermes' dependencies themselves have to. We should recommend that they do this for their plugins and we should give guidance to plugin devs that they should though." - tools/lazy_deps.py: INSTALL_POLICIES ("core" | "plugin"); `_uv_policy_args` replaces `_uv_policy_cwd`; `_venv_pip_install(policy=)` defaults to core (ensure/LAZY_DEPS), `install_specs(policy=)` defaults to plugin. - hermes_cli/plugin_python_deps.py: `resolve()` passes policy="plugin". - Docs: developer guide "Dependency security policy" section, catalog README admission rule 9, AGENTS.md pinning policy — plugin authors are responsible for their deps and strongly recommended to pin upper bounds, floor on the oldest API-compatible version and run their own release quarantine (`uv --exclude-newer` in their CI); operators can set UV_EXCLUDE_NEWER. - Tests: the #118841 cwd test is replaced by two invariants — a plugin install carries `--no-config` and no checkout cwd (red on base), a core lazy install keeps the checkout cwd and no `--no-config`.
132 lines
7.7 KiB
Markdown
132 lines
7.7 KiB
Markdown
# Hermes Plugin Catalog
|
|
|
|
Curated, Nous-approved Hermes plugins. Each YAML file in this directory
|
|
(except `removed.yaml`) is one catalog entry, discoverable via
|
|
`hermes plugins catalog` / `hermes plugins search` and installable with
|
|
`hermes plugins install <name>`.
|
|
|
|
## Admission policy
|
|
|
|
Presence in this directory **is** the trust signal. The rules that keep it
|
|
meaningful:
|
|
|
|
1. **Human-merged gate.** Entries are added *only* via a PR to the
|
|
`hermes-agent` repository, reviewed and merged by a maintainer. There is
|
|
no self-serve registry, no automated ingestion.
|
|
2. **Exact SHA pins are mandatory.** Every entry pins a full 40-character
|
|
commit SHA. Branches, tags, and short SHAs are rejected by the loader.
|
|
Installs clone the repository and check out exactly that commit.
|
|
3. **No self-updating code.** A listed plugin must not fetch and replace
|
|
its own files (in-app "check for updates", signed release downloaders,
|
|
remote `plugin.js` loaders). The exact SHA pin *is* the trust model; a
|
|
self-updater lets an installed copy move to a commit nobody reviewed.
|
|
Updates reach users only through a SHA-bump PR here plus
|
|
`hermes plugins update <name>`. Keep the updater in the standalone
|
|
distribution if you want one; strip it from the catalog build.
|
|
4. **SHA bumps are new PRs.** Updating an entry's pin is a new PR whose diff
|
|
(old SHA → new SHA) is re-reviewed like any other change — reviewers are
|
|
expected to look at the upstream commit range being adopted.
|
|
5. **Owner-or-major-contributor submissions, or a maintainer-curated sweep.**
|
|
An entry may be submitted by the plugin repository's owner or a major
|
|
contributor to it; drive-by submissions of third-party repos are declined.
|
|
Hermes maintainers may also add entries in batches from a reviewed sweep
|
|
of community plugins (every pin validated and scanned at the pinned
|
|
commit, self-updater and credential-store checks run, English-first UI).
|
|
Authors of swept-in entries keep control: a PR from the owner adjusting
|
|
or removing their entry is accepted on request, and SHA bumps stay
|
|
owner-or-maintainer PRs under rule 4.
|
|
6. **Declared capabilities must match reality.** The `capabilities:` block
|
|
(tools, hooks, middleware, env vars) must match what the plugin actually
|
|
registers at the pinned commit. Validation fails the entry otherwise —
|
|
undeclared capability creep is treated as a security issue.
|
|
7. **The install scanner runs at admission.** `hermes plugins validate` includes
|
|
the `security scan` check: `dangerous` fails the entry; `caution` findings
|
|
appear as warnings in the CI log and the reviewer reads them before merging.
|
|
In exchange, installs at the pinned SHA accept `caution` without a prompt
|
|
(`dangerous` still blocks). Review the warnings; do not merge past them.
|
|
8. **Desktop plugins stay inside the SDK surface.** A `desktop/plugin.js` runs
|
|
in the Desktop renderer with the app's full authority (the loader isolates
|
|
errors, not capabilities), so a listed one may only use the plugin SDK:
|
|
no prototype patching (`X.prototype.y =`, `Object.defineProperty(...prototype`),
|
|
no `eval`/`new Function`, no `import()` of anything but `@hermes/plugin-sdk`
|
|
/ `react` (app bundle chunks, blob or http URLs included), no script-tag
|
|
injection, no reaching into the app's internal stores. `hermes plugins
|
|
validate` refuses these at admission (`desktop surface` check); a plugin
|
|
that needs a capability the SDK lacks asks for an SDK hook instead of
|
|
patching around it.
|
|
9. **Dependency security policy is the plugin's.** Hermes's 14-day
|
|
`exclude-newer` quarantine covers Hermes's own dependencies only; a plugin's
|
|
`python_dependencies` / `pyproject.toml` install under the plugin's policy
|
|
(no quarantine, still inside Hermes's core constraints). Reviewers read the
|
|
dependency list at the pinned SHA: bare floors (`>=X` with no upper bound)
|
|
and floors on the newest release get a request for the oldest
|
|
API-compatible floor plus an upper bound, and authors are strongly
|
|
recommended to run their own release quarantine (`uv --exclude-newer` in
|
|
their CI) — see the developer guide's *Dependency security policy*. A
|
|
recent floor alone is not grounds to hold an entry.
|
|
|
|
## Entry schema
|
|
|
|
```yaml
|
|
name: example-plugin # [a-z0-9_-]{1,64}, the catalog key
|
|
repo: https://github.com/owner/repo # https:// only
|
|
sha: <40-hex commit sha> # mandatory exact pin
|
|
subdir: "" # optional path within the repo
|
|
description: One-line description.
|
|
maintainer: OwnerName
|
|
tier: official # official | community (default community)
|
|
category: memory # desktop | memory | platform | web | tools | voice | automation | models | general
|
|
# (default desktop) — the shelf the entry sits on at /docs/plugins
|
|
requires_hermes: ">=0.19" # optional
|
|
docs_url: "" # optional
|
|
version: "1.4.0" # optional human label for the sha (quote it); shown as "1.4.0 @ abcd1234"
|
|
image: "" # optional https image on a GitHub host, 2:1 (e.g. 1200x600), e.g.
|
|
# https://raw.githubusercontent.com/owner/repo/<sha>/docs/banner.png
|
|
screenshots: [] # optional, up to 6 https images on a GitHub host; gallery on /docs/plugins/<name>
|
|
readme: true # optional, default true; the README at the PINNED SHA renders on /docs/plugins/<name>
|
|
platforms: [] # optional, e.g. [linux, macos]; empty = all
|
|
capabilities:
|
|
provides_tools: []
|
|
provides_hooks: []
|
|
provides_middleware: []
|
|
requires_env: []
|
|
```
|
|
|
|
`version`, `image`, `screenshots` and `readme` are cosmetic: none is parsed or
|
|
used to pick what installs. The sha stays the release; bump `version` in the
|
|
same PR that bumps `sha` so the label on the card matches the code. Images must
|
|
live on `raw.githubusercontent.com`, `github.com` or `*.githubusercontent.com`
|
|
so the Desktop catalog and the docs site never fetch from third-party hosts;
|
|
pin the raw URL to the entry's commit and the picture is as immutable as the
|
|
code.
|
|
|
|
Every entry gets a page at `https://hermes-agent.nousresearch.com/docs/plugins/<name>`
|
|
and every maintainer a page at `/docs/plugins/by/<maintainer>`, both generated
|
|
from these files at docs build time. `screenshots:` fills the page's gallery;
|
|
the build fetches the README (from `subdir` if set, else the repo root) **at the
|
|
pinned sha** — GitHub and GitLab repos, on by default, `readme: false` opts out — and renders it
|
|
through an allowlist (raw HTML dropped, links and images resolved against the
|
|
pinned tree). The page therefore shows the README the reviewer read, and it
|
|
changes only when a reviewed re-pin lands.
|
|
|
|
## removed.yaml — the blocklist
|
|
|
|
When an entry is pulled from the catalog for security or policy reasons, it
|
|
is recorded in `removed.yaml` with a reason and date. The installer refuses
|
|
to install anything matching a removed entry's name or repo URL, so a
|
|
malicious plugin cannot be re-installed from a stale identifier after
|
|
removal. Removals, like additions, land via reviewed PRs.
|
|
|
|
Delisting is different from removal: an entry that is merely unmaintained, superseded, or
|
|
squatting a name it is not affiliated with is deleted from the catalog (plain file removal,
|
|
users who already installed it are unaffected) and is welcome back under a distinct name.
|
|
|
|
## Names
|
|
|
|
The catalog key and the manifest `name:` are what users search, install, and — for memory
|
|
providers — put in `memory.provider`. A `memory` / `exclusive` entry must not reuse the name of
|
|
another provider or of a well-known upstream project it is not affiliated with: two providers
|
|
registering the same `register_memory_provider` name make `memory.provider` ambiguous
|
|
(whichever loads last wins). Reviewers check the registered provider name, not just the file
|
|
name, and the affiliated project gets the bare key.
|