Skip to content

Cold-scan/type-detector runs outside the v1.6.0 cwd guard — still profiles $HOME and writes personal dir names to ~/.ijfw/project.type #18

Description

@jessemsullivan

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.mjsif (!opts.projectRoot) exit(0) only.
    • mcp-server/src/project-type-detector.jsdetect() accepts any root
      (String(projectRoot || process.cwd())), no homedir()// refusal, no
      project-marker requirement.

Impact when cwd = $HOME

  1. 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.
  2. 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.
  3. 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

  1. 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).
  2. 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.
  3. 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

  • With session cwd = $HOME, no scan of the home tree occurs and
    $HOME/.ijfw/project.type is not created/updated.
  • Same for cwd = / and ancestors of $HOME.
  • detect()/cold-scan-runner.mjs refuse $HOME// even when invoked
    directly with that root (defense-in-depth, not just caller gating).
  • domain-manifest:load and extension:deploy-lazy are not spawned with
    --project-root $HOME.
  • Normal project detection (root contains a real project) unchanged.
  • Regression test: sandboxed $HOME, run session-start with cwd=$HOME,
    assert no project.type written and no detector walk performed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions