Problem
Discovery skips symlinked and junctioned intermediate directories with no finding, no counter,
and no header line. Entire subtrees vanish from the audit with zero disclosure.
This matters most on Windows, where a directory junction — a common shape in real trees — tests as
a symlink under Git Bash. An operator whose fleet root reaches repositories through a junction gets
a report that says nothing about them and nothing about the omission.
reference/security-review.md asserts "Filesystem discovery is bounded and does not follow symbolic
links" without disclosing that the skip is silent, so the security posture reads as complete when
the reporting is not.
Evidence
plugins/repo-fleet-hygiene/skills/audit/scripts/audit-fleet.sh, inside discover_repositories
(locate by content):
for child in "$dir"/* "$dir"/.[!.]* "$dir"/..?*; do
[[ -d "$child" && ! -L "$child" ]] || continue
The continue is the only trace. DISCOVERY_SKIP_PATHS / DISCOVERY_NONREPO_COUNT /
DISCOVERY_UNREADABLE_COUNT are all untouched on this branch, so the Discovery skips: N non-repository, M unreadable header line cannot reflect it either.
Empirically, under Git Bash on Windows 11, a directory created with mklink /J satisfies both
[[ -d ]] and [[ -L ]], so it takes this branch:
$ cmd //c "mklink /J junc ..\target"
$ [[ -d junc ]] && echo yes # yes
$ [[ -L junc ]] && echo yes # yes
Symlinked/junctioned roots are already handled separately — a CLI root hard-fails, a configured
root degrades to stale-config-entry. That path is correct and is not in scope here.
Proposed change
Keep the non-following behavior — it is correct. Record the skipped paths in a
DISCOVERY_SYMLINK_PATHS array beside the existing DISCOVERY_SKIP_PATHS, and emit them as an
UNKNOWN finding alongside the existing discovery-skip disclosure, stating that Windows directory
junctions test as symlinks. Add the count to the discovery-skips header line. Update
security-review.md to say the skip is disclosed rather than silent.
A --follow-links escape hatch is explicitly not required; disclosure is the requirement.
Acceptance criteria
Problem
Discovery skips symlinked and junctioned intermediate directories with no finding, no counter,
and no header line. Entire subtrees vanish from the audit with zero disclosure.
This matters most on Windows, where a directory junction — a common shape in real trees — tests as
a symlink under Git Bash. An operator whose fleet root reaches repositories through a junction gets
a report that says nothing about them and nothing about the omission.
reference/security-review.mdasserts "Filesystem discovery is bounded and does not follow symboliclinks" without disclosing that the skip is silent, so the security posture reads as complete when
the reporting is not.
Evidence
plugins/repo-fleet-hygiene/skills/audit/scripts/audit-fleet.sh, insidediscover_repositories(locate by content):
The
continueis the only trace.DISCOVERY_SKIP_PATHS/DISCOVERY_NONREPO_COUNT/DISCOVERY_UNREADABLE_COUNTare all untouched on this branch, so theDiscovery skips: N non-repository, M unreadableheader line cannot reflect it either.Empirically, under Git Bash on Windows 11, a directory created with
mklink /Jsatisfies both[[ -d ]]and[[ -L ]], so it takes this branch:Symlinked/junctioned roots are already handled separately — a CLI root hard-fails, a configured
root degrades to
stale-config-entry. That path is correct and is not in scope here.Proposed change
Keep the non-following behavior — it is correct. Record the skipped paths in a
DISCOVERY_SYMLINK_PATHSarray beside the existingDISCOVERY_SKIP_PATHS, and emit them as anUNKNOWNfinding alongside the existing discovery-skip disclosure, stating that Windows directoryjunctions test as symlinks. Add the count to the discovery-skips header line. Update
security-review.mdto say the skip is disclosed rather than silent.A
--follow-linksescape hatch is explicitly not required; disclosure is the requirement.Acceptance criteria
UNKNOWNfinding namingthe path.
Finding:assertion inaudit-fleet.test.sh(
scripts/check-fleet-finding-test-coverage.sh --checkenforces this).security-review.mdno longer implies the non-following skip is silent.