TL;DR: Follow-up to the v1.6.x guards: ijfw_is_project_writable() correctly gates the CLAUDE.md/AGENTS.md writes, but the cold-scan trigger (session-start.sh ~line 1052) runs outside that gate. With a session cwd of $HOME, the project-type detector still profiles the entire home directory (file-tree walk → possible macOS TCC prompts) and writes the profile (personal top-level dir names like finance, posts, social as signals) into ~/.ijfw/project.type. domain-manifest:load / extension:deploy-lazy also run unconditionally with --project-root $HOME. Suggest applying the existing guard to all scanner spawns and adding it inside the detector/runner as defense-in-depth.
Summary
First: the v1.6.0/1.6.1 hardening is good. ijfw_is_project_writable()
(fail-closed, physical-path comparison) correctly prevents the worst outcome —
authoring ~/CLAUDE.md / ~/AGENTS.md from a $HOME-rooted session. Verified
in current main.
But the guard is applied only to the config-write call sites. The scan
call sites later in the same hook are not gated:
-
claude/hooks/scripts/session-start.sh (~line 1051):
if [ -n "$COLD_SCAN_TRIGGER" ]; then
bash "$COLD_SCAN_TRIGGER" "$(pwd -P 2>/dev/null)" >/dev/null 2>&1 || true
fi
No ijfw_is_project_writable check — runs for any cwd including $HOME.
-
Same for the detached domain-manifest:load and extension:deploy-lazy
spawns (~lines 1076–1086), which pass --project-root "$(pwd -P)"
unconditionally.
-
Downstream, none of the chain self-guards:
cold-scan-trigger.sh — only checks the arg is a directory.
mcp-server/src/cold-scan-runner.mjs — if (!opts.projectRoot) exit(0) only.
mcp-server/src/project-type-detector.js — detect() accepts any root
(String(projectRoot || process.cwd())), no homedir()// refusal, no
project-marker requirement.
Impact when cwd = $HOME
- The detector walks/profiles the home directory tree (in my v1.5.6 case:
~2,033 files sampled). On macOS this kind of walk is what fires TCC
permission prompts (Documents/Desktop/iCloud) attributed to the host app —
the same symptom class as the fixed indexer bug.
- The resulting profile — including personal top-level directory names
(finance, posts, social, …) and matched personal filenames as signals —
is persisted to $HOME/.ijfw/project.type.
- A junk classification of the home folder ("type: content, confidence 0.887")
is cached where future tooling may consume it.
This is meaningfully milder than the pre-1.6.0 behavior (it no longer lands in
an agent-instructions file — that's fixed), but it's the same root-cause class:
a session-start scanner trusting pwd as a project root.
Suggested fix
- Gate the cold-scan trigger and the
domain-manifest:load /
extension:deploy-lazy spawns behind the existing
ijfw_is_project_writable (or a scan-specific variant — even read-only
profiling of $HOME is undesirable).
- Add the same refusal inside
cold-scan-runner.mjs and/or
project-type-detector.js (realpath(root) === homedir() || root === '/'
→ refuse; optionally require a project marker). The v1.6.1 indexer fix put
the guard in the script itself, which is the right pattern — callers
shouldn't each have to remember.
- Consider centralizing: one shared "safe scan root" helper (shell + JS)
consumed by every session-start scanner, so the next scanner added can't
reintroduce this class of bug.
Acceptance criteria
Summary
First: the v1.6.0/1.6.1 hardening is good.
ijfw_is_project_writable()(fail-closed, physical-path comparison) correctly prevents the worst outcome —
authoring
~/CLAUDE.md/~/AGENTS.mdfrom a$HOME-rooted session. Verifiedin current
main.But the guard is applied only to the config-write call sites. The scan
call sites later in the same hook are not gated:
claude/hooks/scripts/session-start.sh(~line 1051):No
ijfw_is_project_writablecheck — runs for any cwd including$HOME.Same for the detached
domain-manifest:loadandextension:deploy-lazyspawns (~lines 1076–1086), which pass
--project-root "$(pwd -P)"unconditionally.
Downstream, none of the chain self-guards:
cold-scan-trigger.sh— only checks the arg is a directory.mcp-server/src/cold-scan-runner.mjs—if (!opts.projectRoot) exit(0)only.mcp-server/src/project-type-detector.js—detect()accepts any root(
String(projectRoot || process.cwd())), nohomedir()//refusal, noproject-marker requirement.
Impact when cwd =
$HOME~2,033 files sampled). On macOS this kind of walk is what fires TCC
permission prompts (Documents/Desktop/iCloud) attributed to the host app —
the same symptom class as the fixed indexer bug.
(
finance,posts,social, …) and matched personal filenames as signals —is persisted to
$HOME/.ijfw/project.type.is cached where future tooling may consume it.
This is meaningfully milder than the pre-1.6.0 behavior (it no longer lands in
an agent-instructions file — that's fixed), but it's the same root-cause class:
a session-start scanner trusting
pwdas a project root.Suggested fix
domain-manifest:load/extension:deploy-lazyspawns behind the existingijfw_is_project_writable(or a scan-specific variant — even read-onlyprofiling of
$HOMEis undesirable).cold-scan-runner.mjsand/orproject-type-detector.js(realpath(root) === homedir() || root === '/'→ refuse; optionally require a project marker). The v1.6.1 indexer fix put
the guard in the script itself, which is the right pattern — callers
shouldn't each have to remember.
consumed by every session-start scanner, so the next scanner added can't
reintroduce this class of bug.
Acceptance criteria
$HOME, no scan of the home tree occurs and$HOME/.ijfw/project.typeis not created/updated./and ancestors of$HOME.detect()/cold-scan-runner.mjsrefuse$HOME//even when invokeddirectly with that root (defense-in-depth, not just caller gating).
domain-manifest:loadandextension:deploy-lazyare not spawned with--project-root $HOME.$HOME, run session-start with cwd=$HOME,assert no
project.typewritten and no detector walk performed.