PLAN § v8.1.3 punkt 1-3, PM decisions in order 20260923T092223Z:
- punkt 1: a `site-packages` or `vendor` segment below cwd is foreign
like `node_modules`; a config dir's `skills/` is foreign like its
`plugins/`. Safe direction: a false foreign costs an extra finding.
- punkt 2: `~/.claude` and `$CLAUDE_CONFIG_DIR` both count; a leading
`~` in the variable is expanded. Anthropic's docs (claude-directory,
env-vars) do not say whether Claude Code expands it: not verified,
so both readings are foreign.
- punkt 3: realpathSync.native canonicalizes case on a case-insensitive
volume (measured: realpathSync keeps `sub`, .native gives `Sub`), so
NODE_MODULES/x is foreign and a case-mismatched own path is own
(closes § v8.1.2 punkt 4).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Version sync: package.json, .claude-plugin/plugin.json, README badge +
Recent versions row, CLAUDE.md header + v8.1.2 highlights, CHANGELOG
[8.1.2]. The own-working-tree header and CHANGELOG no longer claim that
node_modules and the plugin dir are the only install locations foreign
code lands in (independent review: Python venv, vendor/ still own) —
listed as Known open instead.
Gates on the staged tree: suite 2328 / 2322 pass / 0 fail / 6 skipped,
hooks 370/0, golden 109/7/4 61/61, av-surface 6/6, claude plugin
validate pass (1 known CLAUDE.md warning).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A target with no .git of its own under cwd shares cwd's git root, so the
v8.1.1 rule alone called it own: an installed package under a repo and a
plugin-cache copy still had their .llm-security-ignore / policy.json read.
Chosen (PM rule, order 20260922T192716Z): a target is additionally foreign
when the path from cwd to it has a node_modules segment, or when it lies
under $CLAUDE_CONFIG_DIR/plugins (default ~/.claude/plugins). Because those
are the two concrete places foreign code lands under a user's working
directory, the failure direction is safe (foreign means more findings), and
a general "no .git of its own" rule would shut out ordinary subdirectories
of the caller's own repo. git archive exports stay indistinguishable from
own subdirectories; documented as a known limit in the module header.
Red first: 6 unit + 6 orchestrator assertions failed before the fix;
known-positives (plain subdir, workspace package dir, rest of config dir)
passed before and after.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
isOwnWorkingTree() treated any target at or below cwd as own, so a foreign
clone under cwd (cwd = $HOME, a vendor clone in a project) still had its
.llm-security-ignore, policy.json and custom SIG rules read. The target must
now also share cwd's git root: nearest ancestor with a `.git` entry (dir for
a clone, file for a submodule/worktree), or none for both. No git spawn.
tmpdir stays foreign. Chosen per the PM order: it is exactly the line
between "my repo" and "something I fetched", and the failure direction is
safe (foreign => config ignored => more findings, never fewer).
Red first: tests/lib/own-working-tree.test.mjs 4 fail / 5 pass (the 5 are
known-positives), tests/scanners/nested-clone-scope.test.mjs 5 fail / 4 pass
on the old rule. Green after; reverting the git-root comparison turns 9 red.
Suite 2306 / 2300 pass / 0 fail / 6 skip; hooks 370/0 (implicit root
untouched); golden 109/7/4, 61/61.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
loadPolicy() read .llm-security/policy.json from whatever root it was
given, and every scanner passes the SCANNED TARGET: scan-orchestrator
(policyRoot = resolve(args.target)), entropy-scanner (thresholds and
suppression patterns), signature-scanner (sig.custom_rules_path and
enabled_families), trigger-scanner (phrase lists) and ast-taint-scanner
(enabled, python_path). A foreign/cloned target could raise its own
entropy thresholds, disable SIG families, supply its own SIG ruleset or
name the interpreter the AST scanner spawns — configuring the scan of
itself. Same defect class as S3b's .llm-security-ignore fix.
Chosen: move isOwnWorkingTree() to scanners/lib/own-working-tree.mjs (one
copy, reused by the orchestrator's ignore-file check) and make
loadPolicy() refuse an EXPLICIT root that is not the caller's own tree —
defaults plus one stderr line, same form as S3b — because one rule in one
function covers every scanner and a future call site cannot forget it.
The IMPLICIT root (CLAUDE_PROJECT_ROOT/cwd, what every hook uses) is the
caller's own project by construction and is read as before.
entropy-scanner's calibration.policy_source no longer reports an ignored
file as its source.
New tests/scanners/policy-scope.test.mjs was red on 0d37f5a (foreign
target: entropy finding silenced, custom SIG rule loaded, findings differ
from the same tree without policy.json, no stderr line) and is green now;
its own-tree scenario (known-positive) is green before and after. The 15
existing policy tests that placed own-tree fixtures under os.tmpdir() now
use tests/helpers/own-tree.mjs (fixture under $HOME, cwd set to it).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>