NousResearch/hermes-agent

Security fixes for automated skill review role handling

Cluster 25839 · agent. Data as of

Nothing open on GitHub. Fix merged in #53226 on 26 Jun 2026. Check it covers your case.

The short answer

0open issues
0open PRs
3items grouped
Known?
Yes. 3 related items grouped here, 1 issue already closed.
Fix event
A fix was merged to main in PR #53226. Check it covers your case. No release data is tracked.
Tracker
The maintainers' tracker marks this resolved, deciding PR #53226. That is separate from the fix event.
Still open
Nothing open on GitHub.

These members relate to role assignment issues in automated/background skill review processes, where incorrect role usage could allow unintended skill modifications. Fixes address using the appropriate system role and respecting pinned skills during background review forks.

Open issues0

No open issues.

Open PRs0

No open pull requests.

Fixes on record

Merged to main. A stamp counts the issues a fix is linked to.

  • #53226 fix(skills): background review fork respects pinned skills

    merged to main

    files: tests/tools/test_skill_manager_tool.py, tools/skill_manager_tool.py

    Show the diff
    • tests/tools/test_skill_manager_tool.py
      @@ -912,6 +912,67 @@ def test_background_review_refuses_to_patch_external_skill(self, tmp_path):
               assert "OLD_MARKER" in (skill_dir / "SKILL.md").read_text()
               assert "NEW_MARKER" not in (skill_dir / "SKILL.md").read_text()
       
      +    def test_background_review_refuses_to_patch_pinned_skill(self, tmp_path):
      +        """#25839: the autonomous review fork respects pin like the curator
      +        does — a pinned skill is off-limits to background maintenance, even
      +        for patch/edit (which a foreground user-directed call is allowed to
      +        perform). Without a user in the loop there is no one to consent."""
      +        from tools.skill_provenance import (
      +            BACKGROUND_REVIEW,
      +            reset_current_write_origin,
      +            set_current_write_origin,
      +        )
      +
      +        def _fake_get_record(skill_name):
      +            return {"pinned": True} if skill_name == "my-skill" else {"pinned": False}
      +
      +        with _skill_dir(tmp_path):
      +            _create_skill("my-skill", VALID_SKILL_CONTENT)
      +            token = [redacted:secret-assignment]
      +            try:
      +                with patch("tools.skill_usage.get_record", side_effect=_fake_get_record):
      +                    raw = skill_manage(
      +                        action="patch",
      +                        name="my-skill",
      +                        old_string="Do the thing.",
      +                        new_string="Do the new thing.",
      +                    )
      +            finally:
      +                reset_current_write_origin(token)
      +
      +        result = json.loads(raw)
      +        assert result["success"] is False
      +        assert "pinned" in result["error"].lower()
      +
      +    def test_background_review_unpinned_skill_not_blocked_by_pin_guard(self, tmp_path):
      +        """The pin guard must not over-block: an unpinned agent-owned skill is
      +        still writable by the review fork."""
      +        from tools.skill_provenance import (
      +            BACKGROUND_REVIEW,
      +            reset_current_write_origin,
      +            set_current_write_origin,
      +        )
      +
      +        with _skill_dir(tmp_path):
      +            _create_skill("my-skill", VALID_SKILL_CONTENT)
      +            token = [redacted:secret-assignment]
      +            try:
      +                with patch(
      +                    "tools.skill_usage.get_record",
      +                    side_effect=lambda n: {"pinned": False},
      +                ):
      +                    raw = skill_manage(
      +                        action="patch",
      +                        name="my-skill",
      +                        old_string="Do the thing.",
      +                        new_string="Do the new thing.",
      +                    )
      +            finally:
      +                reset_current_write_origin(token)
      +
      +        result = json.loads(raw)
      +        assert result["success"] is True
      +
       
       
       # ---------------------------------------------------------------------------
    • tools/skill_manager_tool.py
      @@ -254,6 +254,27 @@ def _background_review_write_guard(
           except Exception:
               return None
       
      +    # Pin must be respected by autonomous maintenance. The curator already
      +    # skips pinned skills from every auto-transition; the background review
      +    # fork is the same kind of autonomous, no-user-present actor, so it must
      +    # not write to a pinned skill either (issue #25839). This is stricter than
      +    # the foreground ``_pinned_guard`` (which only blocks deletion) precisely
      +    # because there is no user in the loop to consent to an edit here.
      +    try:
      +        from tools import skill_usage
      +        if skill_usage.get_record(name).get("pinned"):
      +            return {
      +                "success": False,
      +                "error": (
      +                    f"Refusing background curator {action} for pinned skill "
      +                    f"'{name}': pinned skills are off-limits to autonomous "
      +                    "maintenance. Ask the user to run "
      +                    f"`hermes curator unpin {name}` if they want it changed."
      +                ),
      +            }
      +    except Exception:
      +        logger.debug("pinned skill guard lookup failed for %s", name, exc_info=True)
      +
           try:
               from agent.skill_utils import is_external_skill_path
               if is_external_skill_path(skill_dir):

1 PR closed without merging

From the close comments: superseded by another PR 1

  • #25934 fix(security): use system role for automated review prompts (#25839) · superseded by another PR → #25839, #53226, #17276
Closed issues · 1
  • Closed#25839System impersonates user role, tricking parallel agent instances into modifying skills without consent
How this cluster was grouped
  • Link evidence (structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
  • Pairwise link density 0.67: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. 1.00 means every pair is linked.

Pick up a Lab task

Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.

Open the Lab

At a glance

Subsystem
agent
Merged PRs
#53226
Closed issues
1
Attempts
1 PR closed without merging
Fix link checked
26 Jun 2026

For your agent

The same facts, typed.