While reading SECURITY.md against the implementation, three statements did not hold. None of these is a vulnerability report — the two that touch real behaviour have been sent through private vulnerability reporting separately. These three are documentation only.
1. Supported Versions table is two majors behind
| Version | Supported |
|---------|-----------|
| 0.3.x | Yes |
| < 0.3 | No |
Current release is 0.9.19. Read literally, the table says no currently-published version is supported. Suggested fix: state the supported line as the current minor, or replace the table with "latest release only".
2. sanitize_label() is described as HTML-escaping; it does not escape
SECURITY.md says:
security.sanitize_label() strips control characters, caps at 256 chars, and HTML-escapes all node labels and edge titles before pyvis embeds them.
Three parts of that sentence don't match the code:
sanitize_label() does not escape. Its own docstring says so, and tests/test_security.py asserts it explicitly in test_sanitize_label_passthrough_html_chars.
pyvis is not a dependency of this project and appears nowhere in the source tree — only in this sentence.
- The escaping is real, but it lives in
graphify/exporters/html.py (_html.escape on the interpolated fields, plus the JS-side esc() helper).
So the defense is genuinely there — the documentation just credits the wrong function and a library you don't use. Worth fixing precisely because a reader auditing this file will grep for sanitize_label, see it doesn't escape, and reasonably conclude there's an XSS hole when there isn't one.
Also minor: SECURITY.md names "edge titles" as covered by the 256-char cap and control-char strip, but edge title in exporters/html.py is HTML-escaped without going through sanitize_label, so it has neither.
3. followlinks=False is described as literal and repo-wide
Symlink traversal | os.walk(..., followlinks=False) is explicit throughout detect.py.
In detect.py:1261 it is followlinks=follow_symlinks — a parameter that defaults to False. The posture is correct; the wording ("explicit throughout") is stale. Separately extract.py:5104 passes followlinks=True, guarded by a _resolves_under_root containment check — outside detect.py and therefore outside the literal claim, but a reader takes the sentence as a global property.
Why file this at all
SECURITY.md here is unusually detailed and is clearly meant to be audited — that's why the mismatches are worth correcting rather than shrugging at. A threat table that names a specific function is a promise that the function is the thing doing the work.
How this was found
Static read of SECURITY.md against graphify/security.py, graphify/exporters/html.py, graphify/detect.py, graphify/extract.py, and tests/test_security.py. Test suite was not executed (no uv in the audit environment); the security.py checks were exercised by importing the module directly, since it is stdlib-only.
While reading
SECURITY.mdagainst the implementation, three statements did not hold. None of these is a vulnerability report — the two that touch real behaviour have been sent through private vulnerability reporting separately. These three are documentation only.1. Supported Versions table is two majors behind
Current release is
0.9.19. Read literally, the table says no currently-published version is supported. Suggested fix: state the supported line as the current minor, or replace the table with "latest release only".2.
sanitize_label()is described as HTML-escaping; it does not escapeSECURITY.mdsays:Three parts of that sentence don't match the code:
sanitize_label()does not escape. Its own docstring says so, andtests/test_security.pyasserts it explicitly intest_sanitize_label_passthrough_html_chars.pyvisis not a dependency of this project and appears nowhere in the source tree — only in this sentence.graphify/exporters/html.py(_html.escapeon the interpolated fields, plus the JS-sideesc()helper).So the defense is genuinely there — the documentation just credits the wrong function and a library you don't use. Worth fixing precisely because a reader auditing this file will grep for
sanitize_label, see it doesn't escape, and reasonably conclude there's an XSS hole when there isn't one.Also minor:
SECURITY.mdnames "edge titles" as covered by the 256-char cap and control-char strip, but edgetitleinexporters/html.pyis HTML-escaped without going throughsanitize_label, so it has neither.3.
followlinks=Falseis described as literal and repo-wideIn
detect.py:1261it isfollowlinks=follow_symlinks— a parameter that defaults toFalse. The posture is correct; the wording ("explicit throughout") is stale. Separatelyextract.py:5104passesfollowlinks=True, guarded by a_resolves_under_rootcontainment check — outsidedetect.pyand therefore outside the literal claim, but a reader takes the sentence as a global property.Why file this at all
SECURITY.mdhere is unusually detailed and is clearly meant to be audited — that's why the mismatches are worth correcting rather than shrugging at. A threat table that names a specific function is a promise that the function is the thing doing the work.How this was found
Static read of
SECURITY.mdagainstgraphify/security.py,graphify/exporters/html.py,graphify/detect.py,graphify/extract.py, andtests/test_security.py. Test suite was not executed (nouvin the audit environment); thesecurity.pychecks were exercised by importing the module directly, since it is stdlib-only.