Skip to content

Shell-hook allowlist keys on the command string, so an approved hook script can be edited and stays trusted #57558

Description

@shiftedx

Shell-hook allowlist keys on the command string, so an approved hook script can be edited and stays trusted

Affected versions

Confirmed in 0.18.0 (sdist from PyPI). Same code path is present in 0.17.0.

What happens

The shell-hook allowlist grants trust to an (event, command) string pair. The
approval gate never checks the on-disk script's contents, so once a hook is
approved you can edit the script it points at and it keeps running without a
re-prompt. The approval record does store the script mtime at approval time, but
only hermes hooks list / hermes hooks doctor read it, and they only print an
advisory warning. They do not re-prompt or revoke.

Where

agent/shell_hooks.py:

  • _is_allowlisted(event, command) (lines 664-671) matches only on the stored
    event and command strings:

    def _is_allowlisted(event: str, command: str) -> bool:
        data = load_allowlist()
        return any(
            isinstance(e, dict)
            and e.get("event") == event
            and e.get("command") == command
            for e in data.get("approvals", [])
        )
  • _record_approval (lines 746-761) captures script_mtime_at_approval, but
    nothing in the gate consults it.

  • Registration path (lines 244-268): if _is_allowlisted(...) is true, the hook
    is registered with no prompt.

  • _spawn (around lines 419-517) then runs
    shlex.split(os.path.expanduser(spec.command)) with no content check.

hermes_cli/hooks.py:

  • hermes hooks list (lines 88-99) and hermes hooks doctor (lines 347-355)
    compare script_mtime_iso(command) against script_mtime_at_approval and
    print a "script modified since approval" line. This is advisory only; neither
    re-prompts nor removes the allowlist entry.

There is no content hash of the script anywhere in agent/shell_hooks.py.

Minimal repro

  1. Configure a shell hook whose command runs a script, e.g. pre_tool_call ->
    /path/hook.sh.
  2. Approve it once (answer y at the TTY prompt, or use --accept-hooks). An
    entry {event, command, script_mtime_at_approval, ...} is written to
    ~/.hermes/shell-hooks-allowlist.json.
  3. Edit /path/hook.sh so it does something different. The command string is
    unchanged; only the file's contents and mtime change.
  4. Run again. _is_allowlisted(event, command) returns true, the hook registers
    with no prompt, and _spawn executes the edited script with full user
    credentials.
  5. hermes hooks doctor prints a mtime-drift warning but the hook keeps firing;
    the allowlist entry is never revoked and the user is never re-prompted.

Expected vs actual

  • Expected: an approved hook whose on-disk script has changed should require
    re-approval before it runs again.
  • Actual: trust is keyed on the command string only, so editing the script at
    the same path retains approval; the mtime drift is surfaced only if the user
    happens to run hooks doctor / hooks list.

Suggested direction

Capture a content hash (for example sha256) of the resolved script at approval
time and include it in the _is_allowlisted gate, so a changed script fails the
gate and triggers a re-prompt. The mtime can stay as the cheap advisory signal.


This report was prepared with AI assistance.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existsarea/authAuthentication, OAuth, credential poolscomp/agentCore agent runtime: loop, agent_init, prompt builder, context-compression, responses endpointcomp/cliCLI entry point, hermes_cli/, setup wizardsweeper:risk-security-boundarySweeper risk: may affect sandboxing, auth, credentials, or sensitive datatype/securitySecurity vulnerability or hardening

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions