fix: bare plural "tokens" in a prose file is no longer treated as a secret

TOKENS.md / tokens.md read as design-token reference docs, not credential
dumps, unlike singular "token.md" or another keyword's bare plural
("secrets.md"), which still exclude. The .dart/.ts design-token source
files from the original report are already covered by the existing
graphable-source exemption; this closes the remaining prose-note gap.

Fixes #3527

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A6anVY9hgp5ph5qLXRmc2p
This commit is contained in:
HARSHAVARDHAN-RAJU5andClaude Sonnet 5 committed 2026-09-13 12:54:44 +05:30
1 parent fe66389083
commit 64e85524ef
3 files changed
+47 -1

No files matched your search

+1
View File
@@ -4,6 +4,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu
## 0.9.61 (unreleased)
- Fix: a prose note whose stem is bare plural "tokens" (`TOKENS.md`) is no longer dropped as a credential dump — it reads as a design-token reference doc, unlike singular `token.md` or another keyword's bare plural (`secrets.md`), which still exclude (#3527, thanks @conradkelly).
- Fix: `graphify.serve` now imports cleanly on Python 3.12 and 3.13. The `chinese` extra pins `jieba-py` from 3.12 onward (0.9.60 mistakenly kept the old `jieba` until 3.14, and its invalid regex escapes are a hard error on 3.12+), and the jieba import now suppresses the tokenizer's `SyntaxWarning` regardless of message or line so it never escalates under `-W error`.
- Fix: the git hook's rebuild-root guard now rejects a symlink-loop or dangling `.graphify_root` on Python 3.13, whose `Path.resolve()` no longer raises on a loop — the saved root must resolve to a real directory inside the repo before it is adopted.
+12 -1
View File
@@ -183,6 +183,13 @@ _GENERIC_KEYWORD_PATTERNS = [
# stays excluded — see _is_prose_note.
_PROSE_EXTS = frozenset({".md", ".markdown", ".rst", ".org", ".adoc", ".tex"})
# Bare PLURAL "tokens" in a prose file (TOKENS.md) reads as a design-token
# reference doc, not a credential dump — unlike every other generic keyword's
# plural (secrets.md, passwords.md, credentials.md still read as dumps) and
# unlike the singular "token.md", which stays excluded (#3527). Scoped to
# prose extensions only: "tokens.txt" is still a plausible secret store.
_BARE_PLURAL_TOKENS = re.compile(r'(?<![a-zA-Z0-9])tokens(?![a-zA-Z])', re.IGNORECASE)
# Data/serialization extensions that commonly ARE secret stores when their name
# hits a generic keyword (credentials.json, secrets.yaml, token.toml) or they sit
# in an ambiguous sensitive dir (secrets/db.json). These stay subject to the
@@ -208,10 +215,14 @@ def _is_prose_note(path: Path) -> bool:
"""A prose/note file (.md/.rst/...) whose stem is a multi-word topic slug is
exempt from the generic-keyword drop (#2106). A stem that IS exactly a bare
keyword (secrets / token / passwords) is NOT exempt — that still reads as a
credential dump."""
credential dump. The one exception is bare plural "tokens" (TOKENS.md),
which reads as a design-token reference doc rather than a secret store
(#3527)."""
if path.suffix.lower() not in _PROSE_EXTS:
return False
stem = Path(path.name).stem.lstrip('.') or Path(path.name).stem
if _BARE_PLURAL_TOKENS.fullmatch(stem):
return True
return not any(p.fullmatch(stem) for p in _GENERIC_KEYWORD_PATTERNS)
+34
View File
@@ -3412,6 +3412,40 @@ def test_sensitive_bare_keyword_prose_still_dropped():
assert not _is_sensitive(Path("token-lifecycle.md")) # multi-word slug indexed
@pytest.mark.parametrize("path", [
"TOKENS.md",
"tokens.md",
"tokens.rst",
])
def test_sensitive_bare_plural_tokens_prose_indexed(path):
"""Bare plural "tokens" in a prose file is a design-token reference doc,
not a credential dump — unlike "token.md" (singular) or "secrets.md"
(another keyword's bare plural), which still read as dumps (#3527)."""
from graphify.detect import _is_sensitive
assert not _is_sensitive(Path(path))
def test_sensitive_bare_plural_tokens_still_flagged_outside_prose():
"""The plural exemption is scoped to prose extensions only — "tokens.txt"
is still a plausible secret store and stays excluded (#3527)."""
from graphify.detect import _is_sensitive
assert _is_sensitive(Path("tokens.txt"))
assert _is_sensitive(Path("tokens.json"))
@pytest.mark.parametrize("path", [
"app/lib/theme/shell_tokens.dart",
"src/design/tokens.ts",
"src/hard-tokens.ts",
])
def test_sensitive_design_token_source_files_indexed(path):
"""Genuine design-token source files (.dart/.ts) are graphable source and
exempt from the generic-keyword drop regardless of the bare/plural rules
above — they were the headline repro in #3527."""
from graphify.detect import _is_sensitive
assert not _is_sensitive(Path(path))
# ── #2232 / #2184: committed dotenv templates (.env.example etc.) are graphable ──
@pytest.mark.parametrize("path", [