mkdocs-easylinks-plugin 0.2.2__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/CHANGELOG.md +9 -0
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/PKG-INFO +1 -1
- mkdocs_easylinks_plugin-0.2.3/mkdocs_easylinks/__init__.py +8 -0
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/mkdocs_easylinks/plugin.py +30 -5
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/mkdocs_easylinks_plugin.egg-info/PKG-INFO +1 -1
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/pyproject.toml +1 -1
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/tests/test_plugin.py +38 -0
- mkdocs_easylinks_plugin-0.2.2/mkdocs_easylinks/__init__.py +0 -3
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/.github/workflows/publish.yml +0 -0
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/.github/workflows/test.yml +0 -0
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/.gitignore +0 -0
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/LICENSE +0 -0
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/README.md +0 -0
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/mkdocs_easylinks_plugin.egg-info/SOURCES.txt +0 -0
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/mkdocs_easylinks_plugin.egg-info/dependency_links.txt +0 -0
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/mkdocs_easylinks_plugin.egg-info/entry_points.txt +0 -0
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/mkdocs_easylinks_plugin.egg-info/requires.txt +0 -0
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/mkdocs_easylinks_plugin.egg-info/top_level.txt +0 -0
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/setup.cfg +0 -0
- {mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/tests/__init__.py +0 -0
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.2.3] - 2026-04-29
|
|
6
|
+
|
|
7
|
+
### Security
|
|
8
|
+
- Expanded log sanitizer to escape ANSI escape sequences, NUL bytes, vertical tab, form feed, and Unicode line separators (U+2028/U+2029) in addition to `\n`, `\r`, and `\t`
|
|
9
|
+
- Path safety check now rejects absolute and drive-relative paths (e.g. `/etc/passwd`, `C:\…`, `\\server\share\…`) in addition to `..` traversal
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- `mkdocs_easylinks.__version__` is now read from installed package metadata, eliminating drift between `pyproject.toml` and the module attribute
|
|
13
|
+
|
|
5
14
|
## [0.2.2] - 2026-04-13
|
|
6
15
|
|
|
7
16
|
### Security
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"""MkDocs EasyLinks Plugin - Simplified cross-referencing by filename."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = version("mkdocs-easylinks-plugin")
|
|
7
|
+
except PackageNotFoundError: # pragma: no cover
|
|
8
|
+
__version__ = "unknown"
|
|
@@ -17,9 +17,28 @@ from mkdocs.config.defaults import MkDocsConfig
|
|
|
17
17
|
logger = logging.getLogger("mkdocs.plugins.easylinks")
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
_LOG_ESCAPES = {"\n": "\\n", "\r": "\\r", "\t": "\\t"}
|
|
21
|
+
|
|
22
|
+
|
|
20
23
|
def _sanitize_log(value: str) -> str:
|
|
21
|
-
"""
|
|
22
|
-
|
|
24
|
+
"""Escape control characters and other non-printable codepoints before logging.
|
|
25
|
+
|
|
26
|
+
Filenames can legally contain ANSI escape sequences, NULs, vertical tabs,
|
|
27
|
+
form feeds, or Unicode line separators on POSIX systems. Logging them
|
|
28
|
+
verbatim would let an attacker spoof log lines, recolor output, or
|
|
29
|
+
manipulate a terminal that tails the build log.
|
|
30
|
+
"""
|
|
31
|
+
out = []
|
|
32
|
+
for c in value:
|
|
33
|
+
if c in _LOG_ESCAPES:
|
|
34
|
+
out.append(_LOG_ESCAPES[c])
|
|
35
|
+
elif c.isprintable():
|
|
36
|
+
out.append(c)
|
|
37
|
+
elif ord(c) <= 0xff:
|
|
38
|
+
out.append(f"\\x{ord(c):02x}")
|
|
39
|
+
else:
|
|
40
|
+
out.append(f"\\u{ord(c):04x}")
|
|
41
|
+
return "".join(out)
|
|
23
42
|
|
|
24
43
|
|
|
25
44
|
class EasyLinksConfig(Config):
|
|
@@ -128,10 +147,16 @@ class EasyLinksPlugin(BasePlugin[EasyLinksConfig]):
|
|
|
128
147
|
"""Return True if src_path stays within the docs root.
|
|
129
148
|
|
|
130
149
|
MkDocs supplies src_path as a relative path (e.g. ``subdir/page.md``).
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
150
|
+
Absolute paths (``/etc/passwd``, ``C:\\Windows\\...``, UNC paths) and
|
|
151
|
+
relative paths that escape the docs root after normalization (e.g.
|
|
152
|
+
``../../etc/passwd``) would both produce traversal links in the
|
|
153
|
+
output and must be rejected.
|
|
134
154
|
"""
|
|
155
|
+
# os.path.isabs alone is not enough: on Python 3.13+ Windows it returns
|
|
156
|
+
# False for drive-relative paths like ``/etc/passwd`` or ``\foo``, which
|
|
157
|
+
# would still escape the docs root once a drive is resolved.
|
|
158
|
+
if os.path.isabs(src_path) or src_path.startswith(("/", "\\")):
|
|
159
|
+
return False
|
|
135
160
|
if ".." not in src_path:
|
|
136
161
|
return True
|
|
137
162
|
return not os.path.normpath(src_path).startswith("..")
|
|
@@ -1182,6 +1182,31 @@ Regular paragraph with [example link](example.md).
|
|
|
1182
1182
|
from mkdocs_easylinks.plugin import _sanitize_log
|
|
1183
1183
|
assert _sanitize_log("a\nb\rc\td") == "a\\nb\\rc\\td"
|
|
1184
1184
|
|
|
1185
|
+
def test_sanitize_log_escapes_ansi_escape(self):
|
|
1186
|
+
"""_sanitize_log must escape ESC so terminals tailing logs cannot be hijacked."""
|
|
1187
|
+
from mkdocs_easylinks.plugin import _sanitize_log
|
|
1188
|
+
assert _sanitize_log("file\x1b[31mRED.md") == "file\\x1b[31mRED.md"
|
|
1189
|
+
|
|
1190
|
+
def test_sanitize_log_escapes_null_byte(self):
|
|
1191
|
+
"""_sanitize_log must escape NUL bytes."""
|
|
1192
|
+
from mkdocs_easylinks.plugin import _sanitize_log
|
|
1193
|
+
assert _sanitize_log("file\x00.md") == "file\\x00.md"
|
|
1194
|
+
|
|
1195
|
+
def test_sanitize_log_escapes_vertical_tab_and_form_feed(self):
|
|
1196
|
+
"""_sanitize_log must escape \\v and \\f, which can move the cursor in some terminals."""
|
|
1197
|
+
from mkdocs_easylinks.plugin import _sanitize_log
|
|
1198
|
+
assert _sanitize_log("a\vb\fc") == "a\\x0bb\\x0cc"
|
|
1199
|
+
|
|
1200
|
+
def test_sanitize_log_escapes_unicode_line_separator(self):
|
|
1201
|
+
"""_sanitize_log must escape U+2028 and U+2029, which act as line breaks in some viewers."""
|
|
1202
|
+
from mkdocs_easylinks.plugin import _sanitize_log
|
|
1203
|
+
assert _sanitize_log("a
b
c") == "a\\u2028b\\u2029c"
|
|
1204
|
+
|
|
1205
|
+
def test_sanitize_log_preserves_printable_unicode(self):
|
|
1206
|
+
"""_sanitize_log must leave printable Unicode (accents, emoji, CJK) alone."""
|
|
1207
|
+
from mkdocs_easylinks.plugin import _sanitize_log
|
|
1208
|
+
assert _sanitize_log("café-日本語-🚀.md") == "café-日本語-🚀.md"
|
|
1209
|
+
|
|
1185
1210
|
# ------------------------------------------------------------------
|
|
1186
1211
|
# Security fix: path traversal (_is_safe_path / on_files)
|
|
1187
1212
|
# ------------------------------------------------------------------
|
|
@@ -1210,6 +1235,19 @@ Regular paragraph with [example link](example.md).
|
|
|
1210
1235
|
"""An embedded .. that stays within the root should be accepted."""
|
|
1211
1236
|
assert self.plugin._is_safe_path("subdir/../other/file.md") is True
|
|
1212
1237
|
|
|
1238
|
+
def test_is_safe_path_rejects_posix_absolute(self):
|
|
1239
|
+
"""An absolute POSIX path must be rejected even though it has no '..'."""
|
|
1240
|
+
assert self.plugin._is_safe_path("/etc/passwd") is False
|
|
1241
|
+
|
|
1242
|
+
def test_is_safe_path_rejects_windows_absolute(self):
|
|
1243
|
+
"""An absolute Windows path must be rejected when running on Windows."""
|
|
1244
|
+
import os
|
|
1245
|
+
import pytest
|
|
1246
|
+
if os.name != "nt":
|
|
1247
|
+
pytest.skip("Windows-specific path semantics")
|
|
1248
|
+
assert self.plugin._is_safe_path(r"C:\Windows\System32\config\SAM") is False
|
|
1249
|
+
assert self.plugin._is_safe_path(r"\\server\share\file.md") is False
|
|
1250
|
+
|
|
1213
1251
|
def test_traversal_path_ignored_in_on_files(self):
|
|
1214
1252
|
"""Files whose src_path escapes the docs root must be excluded from the index."""
|
|
1215
1253
|
mock_config = MagicMock()
|
{mkdocs_easylinks_plugin-0.2.2 → mkdocs_easylinks_plugin-0.2.3}/.github/workflows/publish.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|