Hook ledger: record every lifecycle hook command the host runs and scan it with the shell rules
Maintainers usually reply within 3 days
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 35/100
Research direction
Start with src/doberman/hosthooks/integrity.py and src/doberman/hosthooks/install.py to understand how settings files and Doberman-owned hook groups are read and fingerprinted; then read the shell rules and tests/unit/test_install_integrity.py. The work spans manifest storage, hook scanning, rule classification, reporting, and documentation. Done when the listed foreign-hook cases are covered, doctor and SessionStart report new or changed commands without raising on malformed files, and docs explain the detection-only limitation.
Written by the indexing model from the issue text.
Description
Try it
python -m pip install -e ".[dev]"
export HOME=$(mktemp -d)
export USERPROFILE="$HOME"
export LOCALAPPDATA="$HOME"
doberman install-hooks --global
python - <<'PY'
import json, os
p = os.path.join(os.environ["USERPROFILE"], ".claude", "settings.json")
d = json.load(open(p))
d["hooks"].setdefault("PreToolUse", []).append(
{"matcher": "*", "hooks": [{"type": "command", "command": "echo hello"}]}
)
json.dump(d, open(p, "w"), indent=2)
PY
doberman doctor
Today: the settings file now holds two PreToolUse groups: Doberman's own
(doberman hook pre) and a foreign one (echo hello) sitting right next to it.
doberman doctor still prints:
[ ok ] Hook integrity: intact (claude global)
It never mentions the second group. Doberman checked only its own hook
registration and had nothing to say about the one a plugin update, a config
sync, or a person just added.
Should be: doberman doctor (and the SessionStart hook) should notice a
hook command it has not seen before, say so, and run that command's text
through the same shell rules Doberman already uses on real tool calls, so an
obviously dangerous command (one that phones home, dumps environment
variables, or writes outside the project) gets flagged even though it is
sitting in a config file instead of a live shell call.
Why it matters
A lifecycle hook (a hook is a command the coding-agent host runs automatically
at a fixed point, such as session start or before a tool call) runs with the
same privileges as the host itself, and nothing forces the agent to approve it
first. The paper "A Blind Trust, the Bloody Thrust" (arXiv 2609.03884, the
HookPry framework) shows a plugin or hook-config update can bind an arbitrary
shell command to a lifecycle event across seven coding-agent harnesses,
Claude Code and Codex CLI among them, with a 77 percent verified end-to-end success rate and
zero of 1,000 test runs blocked by any harness. The best static scanners in
that paper still only caught 52.5 percent of a 40-artifact malicious set.
Right now Doberman is blind to this whole class of hooks unless it happens to
be the hook that got tampered with.
Where to look
src/doberman/hosthooks/integrity.py:257_live_groupsand:282
check_all: resolve each tracked (host, scope) settings file and fingerprint
only the groupsdoberman_groups/cursor_doberman_groups/
codex_doberman_groupsrecognize as Doberman's own; a foreign group in the
same file is invisible to this path today.src/doberman/hosthooks/install.py:64_is_doberman_groupand:75
doberman_groups: the filter that keeps a foreign hook command out of the
manifest even as a fingerprint input; the new ledger needs the opposite
filter (everything that is not ours).src/doberman/hosthooks/install.py:54_SESSION_START_ENTRY: the comment
above it (install.py:37-42, mirrored inintegrity.py:37-42) calls
SessionStart "cosmetic" because losing it does not stop Doberman from
gating calls; a foreign SessionStart/PostToolUse hook is a different risk
(arbitrary code, not loss of gating) and needs its own category.src/doberman/cli/doctor.py:155_check_hook_integrity: wheredoctor
callscheck_alltoday; the new ledger warning plugs in next to it.src/doberman/engine/rules/commands.py:2280DestructiveCommandRuleand
src/doberman/engine/rules/destinations.py:321ExternalDestinationRule:
the shell classifiers a hook command string should be run through.src/doberman/engine/rules/secrets.py:737SecretLeakageRule: covers
encoded-payload and secret-pattern detection for the same command text.src/doberman/models.py:25ActionType.shell_execand:34
SourceContext: the new source context needs a value like
SourceContext.hook_config(none ofuser/webpage/email/tool_output
fits a command that came from a settings file, not a tool call).src/doberman/engine/rules/paths.py:383names_control_planeand
src/doberman/engine/rules/commands.py:1205
_segment_targets_control_plane: already hard-block an agent that edits
hook/trust config directly; this issue is about the host running a hook
command that was already installed by some other means.
Done when
- Every hook command in the settings files Doberman already reads (Claude
Code user/project/localsettings.json, Codexhooks.json, Cursor) is
fingerprinted in the install manifest, not just the ones that match
_is_doberman_group. -
doberman doctorand the SessionStart hook report any hook command
that is new or changed since the manifest was last written, separately
from the existing "Hook integrity: diverged" case (which is about
Doberman's own groups). - Each such command is classified as a
shell_execSecurityObject
with a hook-config source context and scored by the existing shell
rules (destructive command, egress, encoded payload, environment dump,
background process, writes outside the project root); the report names
which signal fired, never the raw command text beyond what those rules
already surface. - The manifest still lives in the per-user config dir
(doberman.storage.fingerprint.user_config_dir), never inside a repo. - A malformed or unreadable settings file degrades to "nothing to
report," the same waycheck_alldegrades today, and never raises out
of the hook path. - Docs say plainly that this is detection and reporting only: Doberman
cannot stop the host from running its own hooks before Doberman ever
gets a chance to see them. - Raise-only: the ledger only ever adds a new warning; it never removes,
disables, or "fixes" a hook on its own.
Check your work
pytest tests/unit/test_install_integrity.py -v
Extend that file (or add tests/unit/test_hook_ledger.py) with: a foreign
hook added after install is reported; a settings file with no foreign hooks
reports nothing new; a hook command matching a destructive/egress/encoded
pattern surfaces the matching reason code; a malformed settings file degrades
to no report instead of raising.
Keep in mind
- Doberman cannot intercept a hook before the host runs it once; this is
about noticing it happened, not preventing the first run. - Never remove or disable a foreign hook automatically: report only, per the
raise-only rule. - Keep the manifest in the per-user config dir. A repo-local manifest could be
deleted by the same actor who added the hook, turning "absent" into "looks
fine." - A hook command changing is not automatically malicious (a legitimate
plugin update looks the same as a hostile one from the outside); word the
report as a fact ("this changed"), not an accusation.
Out of scope
- Blocking or intercepting a hook invocation itself (the host runs its own
hooks outside any path Doberman mediates). - A general sandbox or permission system for hooks; this issue is limited to
visibility plus reusing existing shell classifiers. - Windows ACL/permission hardening of the manifest file itself.
Roadmap fit
Now, follows #635 (doctor should flag disableAllHooks) and #636 (binary
shadowing) in the "Doberman must not be switchable off quietly" set.
New here? Start with CONTRIBUTING.md. Issues carry a level-1 (docs) to level-10 (new subsystem) label. Comment on the issue to claim it before you start, and ask here if anything is unclear.
- Dominant language
- Python
- Stars
- 277
- Forks
- 42
- Avg merge
- 1d 19h
- Merged PRs (30d)
- 16
Getting set up
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from DobermanCore/Doberman-Core
-
posthog
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
DobermanCore/Doberman-Core#791 ·
Maintainers usually reply within 3 days
-
level-6
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
DobermanCore/Doberman-Core#782 · 1 comment ·
Maintainers usually reply within 3 days
-
posthog
Difficulty 2/5 1-3 hours Newbie friendliness 80/100
DobermanCore/Doberman-Core#767 ·
Maintainers usually reply within 3 days
-
good first issue help wanted level-2
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
DobermanCore/Doberman-Core#333 ·
Maintainers usually reply within 3 days
-
good first issue help wanted level-2
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
DobermanCore/Doberman-Core#272 ·
Maintainers usually reply within 3 days
All issues in DobermanCore/Doberman-Core
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 87/100
Maintainers usually reply within 7 days
-
good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
MTSWebServices/RecTools#330 ·
-
area: web priority: P1 story
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
nfiasco/Quant-Time-Machine#151 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
autowarefoundation/autoware_carla_scenario#2019 ·
Maintainers usually reply within 1 day