From d831cfcd4ab32ca180dc11842e6f7a829aed0d40 Mon Sep 17 00:00:00 2001 From: PythonWoods Date: Sun, 26 Jul 2026 12:14:58 +0200 Subject: [PATCH 1/2] fix(lsp): eradicate Z202 path traversal bypass with docs_root boundary resolution (LSP-FIX-012) Signed-off-by: PythonWoods --- CHANGELOG.md | 5 ++++ src/zenzic/core/incremental.py | 45 ++++++++++++++++++++++++---------- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0560bae..5a58c203 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ Versions follow [Semantic Versioning](https://semver.org/). ## [Unreleased] +### Fixed + +- **Path Traversal Security Parity (`LSP-FIX-012`)**: Hardened `Z202`/`Z203` path traversal evaluation in `IncrementalAnalysisEngine._run_urp_checks()` by replacing absolute OS filesystem depth checks (`len(path.parents)`) with `docs_root`-boundary escape detection (`os.path.normpath` + `is_relative_to(resolved_docs_root)`). This guarantees 100% security parity between CLI and LSP mode. + + ## [0.25.1] - 2026-07-26 ### Added diff --git a/src/zenzic/core/incremental.py b/src/zenzic/core/incremental.py index e5d36429..0bea14a1 100644 --- a/src/zenzic/core/incremental.py +++ b/src/zenzic/core/incremental.py @@ -672,20 +672,39 @@ def _source_line(lineno: int) -> str: parsed = urlsplit(url) - # Z202 / Z203 - if "../" in url and url.count("../") > len(path.parents): - _intent = _classify_traversal_intent(url) - findings.append( - RuleFinding( - path, - lineno, - "Z203" if _intent == "suspicious" else "Z202", - f"Path traversal escape detected: '{url}'", - severity="error", - matched_line=raw_line, + # Z202 / Z203 — Path Traversal Detection + if "../" in url: + try: + resolved_docs_root = self.docs_root.resolve() + source_dir = path.parent.resolve() + target_str = os.path.normpath(str(source_dir / parsed.path)) + target_path = Path(target_str) + if not target_path.is_relative_to(resolved_docs_root): + _intent = _classify_traversal_intent(url) + findings.append( + RuleFinding( + path, + lineno, + "Z203" if _intent == "suspicious" else "Z202", + f"Path traversal escape detected: '{url}'", + severity="error", + matched_line=raw_line, + ) + ) + continue + except Exception: + _intent = _classify_traversal_intent(url) + findings.append( + RuleFinding( + path, + lineno, + "Z203" if _intent == "suspicious" else "Z202", + f"Path traversal escape detected: '{url}'", + severity="error", + matched_line=raw_line, + ) ) - ) - continue + continue # Z105 / Z203 elif parsed.path.startswith("/"): From 7dcf753d3e3078b010bc7fc15f32da535dff7d4b Mon Sep 17 00:00:00 2001 From: PythonWoods Date: Sun, 26 Jul 2026 12:15:01 +0200 Subject: [PATCH 2/2] release: bump version to 0.25.2 Signed-off-by: PythonWoods --- .bumpversion.toml | 2 +- .github/ISSUE_TEMPLATE/security_vulnerability.yml | 2 +- .pre-commit-hooks.yaml | 2 +- CHANGELOG.md | 2 ++ CITATION.cff | 2 +- README.md | 4 ++-- RELEASE.md | 8 ++++---- mkdocs.yml | 2 +- pyproject.toml | 2 +- src/zenzic/__init__.py | 2 +- src/zenzic/cli/_standalone.py | 2 +- uv.lock | 2 +- 12 files changed, 17 insertions(+), 15 deletions(-) diff --git a/.bumpversion.toml b/.bumpversion.toml index bac184c9..9d99e87d 100644 --- a/.bumpversion.toml +++ b/.bumpversion.toml @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 [tool.bumpversion] -current_version = "0.25.1" +current_version = "0.25.2" parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)((?Pa|b|rc)(?P\\d+))?" serialize = [ "{major}.{minor}.{patch}{pre_l}{pre_n}", diff --git a/.github/ISSUE_TEMPLATE/security_vulnerability.yml b/.github/ISSUE_TEMPLATE/security_vulnerability.yml index 1b16bc96..bed90689 100644 --- a/.github/ISSUE_TEMPLATE/security_vulnerability.yml +++ b/.github/ISSUE_TEMPLATE/security_vulnerability.yml @@ -29,7 +29,7 @@ body: attributes: label: Zenzic version description: Output of `zenzic --version` - placeholder: "0.25.1" + placeholder: "0.25.2" validations: required: true diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 7cd5d4fa..82e83427 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -7,7 +7,7 @@ # # repos: # - repo: https://github.com/PythonWoods/zenzic -# rev: v0.25.1 +# rev: v0.25.2 # hooks: # - id: zenzic-verify # quality gate — corrisponde a `just verify` lato zenzic # - id: zenzic-guard # fast staged-file credential scan diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a58c203..998fafc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ Versions follow [Semantic Versioning](https://semver.org/). ## [Unreleased] +## [0.25.2] - 2026-07-26 + ### Fixed - **Path Traversal Security Parity (`LSP-FIX-012`)**: Hardened `Z202`/`Z203` path traversal evaluation in `IncrementalAnalysisEngine._run_urp_checks()` by replacing absolute OS filesystem depth checks (`len(path.parents)`) with `docs_root`-boundary escape detection (`os.path.normpath` + `is_relative_to(resolved_docs_root)`). This guarantees 100% security parity between CLI and LSP mode. diff --git a/CITATION.cff b/CITATION.cff index aef8da19..4ed450f3 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -15,7 +15,7 @@ abstract: >- performs deterministic static analysis using a two-pass reference pipeline and a RE2-backed credential scanner, with zero subprocess calls and full SARIF 2.1.0 support for CI/CD integration. -version: 0.25.1 +version: 0.25.2 date-released: 2026-07-26 url: "https://zenzic.dev" repository-code: "https://github.com/PythonWoods/zenzic" diff --git a/README.md b/README.md index 2958964f..214c27c3 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ Zenzic Core is headless and emits standardized **SARIF** JSON, ensuring seamless "tool": { "driver": { "name": "zenzic", - "version": "0.25.1", + "version": "0.25.2", "rules": [ { "id": "Z101", @@ -215,7 +215,7 @@ uv tool upgrade zenzic To run a specific version ephemerally without altering your global environment: ```bash -uvx zenzic@0.25.1 check all +uvx zenzic@0.25.2 check all ``` --- diff --git a/RELEASE.md b/RELEASE.md index bf099e59..b3245dc4 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -8,7 +8,7 @@ | Field | Value | | :------- | :--------- | -| Version | v0.25.1 | +| Version | v0.25.2 | | Codename | Magnetite | | Date | 2026-07-26 | | Status | Stable | @@ -21,7 +21,7 @@ Before tagging, every item must be green: - [ ] `zenzic lab all` — all 20 scenarios exit with expected code - [ ] `zenzic score --stamp` committed — badge in README.md reflects current score - [ ] `zenzic check all .` — zero findings in the repo root -- [ ] `pyproject.toml` version matches the tag (`0.25.1`) +- [ ] `pyproject.toml` version matches the tag (`0.25.2`) - [ ] `CITATION.cff` version and date updated - [ ] `CHANGELOG.md` — `[Unreleased]` section moved to the new version heading - [ ] Update SECURITY.md support table (Add new release, demote previous to Critical/EOL). @@ -53,12 +53,12 @@ git checkout main git pull origin main # 3. Tag the main branch and push -git tag -s -S -m "Release v0.25.1" v0.25.1 +git tag -s -S -m "Release v0.25.2" v0.25.2 git push origin main --tags ``` -- [ ] Create GitHub Release from the tag, using the `## [0.25.1]` CHANGELOG section as the release body. +- [ ] Create GitHub Release from the tag, using the `## [0.25.2]` CHANGELOG section as the release body. ## Changelog Reference diff --git a/mkdocs.yml b/mkdocs.yml index 54d01c42..c85fe8cc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -224,7 +224,7 @@ extra: # ADR-037: No hardcoded SemVer in any .html or .md source. # CI pipeline passes the current version at build time, e.g.: # uv run mkdocs build --extra zenzic_version=0.14.1 - zenzic_version: "0.25.1" # release sync + zenzic_version: "0.25.2" # release sync social: - icon: fontawesome/brands/github link: https://github.com/PythonWoods/zenzic diff --git a/pyproject.toml b/pyproject.toml index 9a94e56e..f75ba28f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ build-backend = "hatchling.build" [project] name = "zenzic" -version = "0.25.1" +version = "0.25.2" description = "Deterministic Document Integrity Engine and SAST for Markdown/MDX graphs." readme = "README.md" requires-python = ">=3.10" diff --git a/src/zenzic/__init__.py b/src/zenzic/__init__.py index d167fe9e..5522a668 100644 --- a/src/zenzic/__init__.py +++ b/src/zenzic/__init__.py @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 """Zenzic — engine-agnostic static analyzer and credential scanner for Markdown documentation.""" -__version__ = "0.25.1" +__version__ = "0.25.2" __version_name__ = "Basalt" # Release codename stored separately from the package version. diff --git a/src/zenzic/cli/_standalone.py b/src/zenzic/cli/_standalone.py index 69101b55..0470d554 100644 --- a/src/zenzic/cli/_standalone.py +++ b/src/zenzic/cli/_standalone.py @@ -1588,7 +1588,7 @@ def _scaffold_plugin(repo_root: Path, plugin_name: str, force: bool) -> None: description = "Custom Zenzic plugin rule package" readme = "README.md" requires-python = ">=3.11" -dependencies = ["zenzic>=0.25.1"] +dependencies = ["zenzic>=0.25.2"] [project.entry-points."zenzic.rules"] {project_slug} = "{module_name}.rules:{class_name}" diff --git a/uv.lock b/uv.lock index 7b50b7ef..d1c27682 100644 --- a/uv.lock +++ b/uv.lock @@ -2465,7 +2465,7 @@ wheels = [ [[package]] name = "zenzic" -version = "0.25.1" +version = "0.25.2" source = { editable = "." } dependencies = [ { name = "google-re2" },