Summary
The Stage 1 sensitive-directory check in detect.py silently drops every file beneath a directory named secrets/, .secrets/, or credentials/ — including legitimate programming-language source that just happens to live in a package with that name. The files vanish from the graph with no trace and no way to override.
This is the same false-positive class as #1225 (which fixed the Stage 3 keyword heuristic), but at the directory level, which #1225 did not touch.
Repro
repo/
internal/secrets/vault.go # real Go source package
app/services/credentials/manager.py # real Python module
secrets/db.json # actual credential store
internal/secrets/vault.go and credentials/manager.py are dropped from the graph. A quick check:
from pathlib import Path
from graphify.detect import _is_sensitive
_is_sensitive(Path("internal/secrets/vault.go")) # True (wrong — this is source)
_is_sensitive(Path("app/services/credentials/manager.py"))# True (wrong — this is source)
_is_sensitive(Path("secrets/db.json")) # True (correct — real secret store)
secrets/ and credentials/ are just as often real source packages (Go internal/secrets, a credentials/ service module) as credential stores, so pruning them wholesale by name loses legitimate source.
Expected
Genuine programming-language source under an ambiguous secrets//credentials/ dir should be graphed, exactly the way Stage 3 already refuses to drop a .py/.rb source file on a keyword hit (#1225). Secret-prone data/config files (.env, .json, .yaml, key material) inside those dirs, and dedicated credential-store dirs (.ssh, .gnupg, .aws, .gcloud), must stay flagged.
Proposed fix
Give Stage 1 the same source-code carve-out Stage 3 has: split _SENSITIVE_DIRS into dedicated credential stores (always skipped) and ambiguous bare-name dirs (skip everything except genuine source, via the existing classify_file(...) == CODE and ext not in _SECRET_PRONE_DATA_EXTS predicate). Credential stores keep their protection and .graphifyinclude still cannot rescue them.
Summary
The Stage 1 sensitive-directory check in
detect.pysilently drops every file beneath a directory namedsecrets/,.secrets/, orcredentials/— including legitimate programming-language source that just happens to live in a package with that name. The files vanish from the graph with no trace and no way to override.This is the same false-positive class as #1225 (which fixed the Stage 3 keyword heuristic), but at the directory level, which #1225 did not touch.
Repro
internal/secrets/vault.goandcredentials/manager.pyare dropped from the graph. A quick check:secrets/andcredentials/are just as often real source packages (Gointernal/secrets, acredentials/service module) as credential stores, so pruning them wholesale by name loses legitimate source.Expected
Genuine programming-language source under an ambiguous
secrets//credentials/dir should be graphed, exactly the way Stage 3 already refuses to drop a.py/.rbsource file on a keyword hit (#1225). Secret-prone data/config files (.env,.json,.yaml, key material) inside those dirs, and dedicated credential-store dirs (.ssh,.gnupg,.aws,.gcloud), must stay flagged.Proposed fix
Give Stage 1 the same source-code carve-out Stage 3 has: split
_SENSITIVE_DIRSinto dedicated credential stores (always skipped) and ambiguous bare-name dirs (skip everything except genuine source, via the existingclassify_file(...) == CODE and ext not in _SECRET_PRONE_DATA_EXTSpredicate). Credential stores keep their protection and.graphifyincludestill cannot rescue them.