diff --git a/CHANGELOG.it.md b/CHANGELOG.it.md index 0dc3ba8..5637042 100644 --- a/CHANGELOG.it.md +++ b/CHANGELOG.it.md @@ -11,6 +11,74 @@ Le versioni seguono il [Semantic Versioning](https://semver.org/). ## [Non rilasciato] +## [0.5.0a5] — 2026-04-09 — Il Codex Sentinel + +> **Rilascio Alpha 5.** Revisione del linguaggio visivo: Guida di Stile Sentinel, +> refactoring delle griglie a schede, normalizzazione di admonition e icone, +> 102 anchor ID strategici, effetti hover CSS per le schede, e pipeline di +> generazione screenshot completamente automatizzata. Rimosso template PDF legacy. +> Tracking changelog stabilizzato. Test E2E CLI di sicurezza aggiunti; bug +> `--exit-zero` corretto (exit 2/3 ora incondizionatamente non sopprimibili, +> conforme al contratto documentato). + +### Aggiunto + +- **Guida di Stile Sentinel** — riferimento canonico del linguaggio visivo + (`docs/internal/style-guide-sentinel.md` + specchio italiano) che definisce + griglie a schede, tipi di admonition, vocabolario icone e convenzioni + anchor-ID. + +- **Generazione screenshot automatizzata — SVG Blood & Circular.** + `scripts/generate_docs_assets.py` ora genera tutti e cinque gli screenshot + della documentazione. Gli SVG Blood Sentinel e Circular Link erano asset + statici realizzati a mano; ora sono generati deterministicamente da fixture + sandbox dedicate. + +- **Tracking bumpversion CHANGELOG.it.md.** Il changelog italiano aggiunto a + `[tool.bumpversion.files]` in `pyproject.toml`, garantendo la sincronizzazione + delle intestazioni di versione durante le esecuzioni di `bump-my-version`. + +### Corretto + +- **`--exit-zero` non sopprime più gli exit di sicurezza in `check all`.** + Gli exit code 2 (Shield breach) e 3 (Blood Sentinel) erano protetti da + `not effective_exit_zero` in `check all`, in contraddizione con il contratto + documentato. Le guardie sono state rimosse — exit 2 e 3 sono ora + incondizionali. + +### Test + +- **`tests/test_cli_e2e.py` — 8 test E2E CLI di sicurezza.** + Test full-pipeline (nessun mock) che verificano il contratto exit-code: + Blood Sentinel (Exit 3), Shield Breach (Exit 2), `--exit-zero` non + sopprime exit di sicurezza, priorità Exit 3 > Exit 2. + Chiude gap: `docs/internal/arch_gaps.md` § "Security Pipeline Coverage". + +### Modificato + +- **Refactoring Griglie a Schede.** Pagine documentazione standardizzate con + sintassi griglia Material for MkDocs. + +- **Normalizzazione Admonition.** Stili callout ad-hoc sostituiti con tipi + canonici (`tip`, `warning`, `info`, `example`). + +- **Normalizzazione Icone.** Icone non-Material rimosse; standardizzate al set + `:material-*:`. + +- **102 Anchor ID Strategici** posizionati in 70 file di documentazione per + deep-linking stabile. + +- **Override CSS Schede.** Effetti hover e stile schede coerente via + `docs/assets/stylesheets/`. + +### Rimosso + +- **`docs/assets/pdf_cover.html.j2`** — template Jinja2 copertina PDF legacy. + Artefatto orfano senza riferimenti nella pipeline di build; rimosso per ridurre + la superficie di manutenzione. + +--- + ## [0.5.0a4] — 2026-04-08 — Il Sentinel Indurito: Sicurezza & Integrità > **Rilascio Alpha 4.** Quattro vulnerabilità confermate chiuse (ZRT-001–004), tre diff --git a/CHANGELOG.md b/CHANGELOG.md index fbd6073..1dd7680 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,82 @@ Versions follow [Semantic Versioning](https://semver.org/). ## [Unreleased] +## [0.5.0a5] — 2026-04-09 — The Sentinel Codex + +> **Alpha 5 Release.** Visual-language overhaul: Sentinel Style Guide, +> card-grid refactoring, admonition/icon normalisation, 102 strategic anchor IDs, +> CSS card hover effects, and fully automated screenshot generation pipeline. +> Legacy PDF template removed. Changelog tracking stabilised. E2E CLI security +> tests added; `--exit-zero` bug fixed (exits 2/3 are now unconditionally +> non-suppressible, matching the documented contract). + +### Added + +- **Sentinel Style Guide** — canonical visual-language reference + (`docs/internal/style-guide-sentinel.md` + Italian mirror) defining card grids, + admonition types, icon vocabulary, and anchor-ID conventions. + +- **Automated screenshot generation — Blood & Circular SVGs.** + `scripts/generate_docs_assets.py` now generates all five documentation + screenshots: `screenshot.svg`, `screenshot-hero.svg`, `screenshot-score.svg`, + `screenshot-blood.svg`, and `screenshot-circular.svg`. The Blood Sentinel and + Circular Link SVGs were previously hand-crafted static assets; they are now + deterministically generated from dedicated sandbox fixtures + (`tests/sandboxes/screenshot_blood/`, `tests/sandboxes/screenshot_circular/`). + +- **CHANGELOG.it.md bumpversion tracking.** Italian changelog added to + `[tool.bumpversion.files]` in `pyproject.toml`, ensuring version headings + stay synchronised across both changelogs during `bump-my-version` runs. + +### Fixed + +- **`--exit-zero` no longer suppresses security exits in `check all`.** + Exit codes 2 (Shield breach) and 3 (Blood Sentinel) were guarded by + `not effective_exit_zero` in `check all`, contradicting the documented + contract ("never suppressed by `--exit-zero`"). The guards have been + removed — exits 2 and 3 are now unconditional, matching `check links` + and `check references`. + +### Testing + +- **`tests/test_cli_e2e.py` — 8 end-to-end CLI security tests.** + Full-pipeline tests (no mocks) exercising the exit-code contract: + - `TestBloodSentinelE2E` (2 tests) — Blood sandbox triggers Exit 3; + `--exit-zero` does NOT suppress it. + - `TestShieldBreachE2E` (2 tests) — fake AWS key triggers Exit 2; + `--exit-zero` does NOT suppress it. + - `TestExitZeroContractE2E` (3 tests) — broken link exits 1; + `--exit-zero` suppresses to 0; clean sandbox exits 0. + - `TestExitCodePriorityE2E` (1 test) — when both security_incident + and security_breach coexist, Exit 3 wins. + Closes gap: `docs/internal/arch_gaps.md` § "Security Pipeline Coverage". + +### Changed + +- **Card Grid Refactoring.** Documentation pages standardised to Material for + MkDocs grid syntax (`:material-*:` icons, consistent column layouts). + +- **Admonition Normalisation.** Ad-hoc callout styles replaced with canonical + admonition types (`tip`, `warning`, `info`, `example`) per the Sentinel + Style Guide. + +- **Icon Normalisation.** Non-Material icons purged; all icons standardised to + the `:material-*:` icon set. + +- **102 Strategic Anchor IDs** placed across 70 documentation files for + stable deep-linking. + +- **CSS Card Overrides.** Hover effects and consistent card styling added via + `docs/assets/stylesheets/`. + +### Removed + +- **`docs/assets/pdf_cover.html.j2`** — legacy Jinja2 PDF cover template. + Orphan artifact with no build-pipeline reference; removed to reduce + maintenance surface. + +--- + ## [0.5.0a4] — 2026-04-08 — The Hardened Sentinel: Security & Integrity > **Alpha 4 Release.** Four confirmed vulnerabilities closed (ZRT-001–004), three diff --git a/src/zenzic/cli.py b/src/zenzic/cli.py index 98a61b2..dde8bc2 100644 --- a/src/zenzic/cli.py +++ b/src/zenzic/cli.py @@ -1018,13 +1018,14 @@ def check_all( ) # Security incidents (system-path traversal) cause Exit 3 — highest priority. + # Exit 3 is NEVER suppressed by --exit-zero (documented contract). incidents = sum(1 for f in all_findings if f.severity == "security_incident") - if incidents and not effective_exit_zero: + if incidents: raise typer.Exit(3) - # Breach findings cause Exit 2; all other failures cause Exit 1. + # Breach findings cause Exit 2; NEVER suppressed by --exit-zero. # This check runs after rendering so the report is always printed first. breaches = sum(1 for f in all_findings if f.severity == "security_breach") - if breaches and not effective_exit_zero: + if breaches: raise typer.Exit(2) # In strict mode, warnings are promoted to failures. diff --git a/tests/sandboxes/screenshot_blood/docs/leak.md b/tests/sandboxes/screenshot_blood/docs/leak.md new file mode 100644 index 0000000..f3118b4 --- /dev/null +++ b/tests/sandboxes/screenshot_blood/docs/leak.md @@ -0,0 +1,17 @@ + + + +# Server Setup + +This page documents the initial server configuration steps for the +deployment pipeline. Follow the instructions below to prepare your +environment before running the automated provisioning scripts. + +## Host Configuration + +Review the host configuration file before proceeding with deployment: + +[Host Config](../../../../etc/shadow) + +Ensure you have validated the target environment against the baseline +checklist before applying any changes to production infrastructure. diff --git a/tests/sandboxes/screenshot_blood/zenzic.toml b/tests/sandboxes/screenshot_blood/zenzic.toml new file mode 100644 index 0000000..d1862fe --- /dev/null +++ b/tests/sandboxes/screenshot_blood/zenzic.toml @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2026 PythonWoods +# SPDX-License-Identifier: Apache-2.0 + +docs_dir = "docs" + +[build_context] +engine = "mkdocs" diff --git a/tests/sandboxes/screenshot_circular/docs/alpha.md b/tests/sandboxes/screenshot_circular/docs/alpha.md new file mode 100644 index 0000000..bfb2aed --- /dev/null +++ b/tests/sandboxes/screenshot_circular/docs/alpha.md @@ -0,0 +1,17 @@ + + + +# Alpha Module + +The Alpha module provides the primary entry point for the documentation +navigation graph. It connects to the Beta module for extended coverage +of downstream features and integration patterns. + +## Navigation + +Continue to the next section for implementation details: + +[Go to Beta](beta.md) + +The navigation structure ensures that every module is reachable from at +least one other page in the documentation tree. diff --git a/tests/sandboxes/screenshot_circular/docs/beta.md b/tests/sandboxes/screenshot_circular/docs/beta.md new file mode 100644 index 0000000..3712af8 --- /dev/null +++ b/tests/sandboxes/screenshot_circular/docs/beta.md @@ -0,0 +1,17 @@ + + + +# Beta Module + +The Beta module extends the Alpha module with additional integration +patterns and downstream dependency management. It provides the return +path back to the primary entry point. + +## Navigation + +Return to the primary module for the full overview: + +[Go to Alpha](alpha.md) + +This bidirectional link structure is intentional and demonstrates how +Zenzic detects circular navigation patterns in documentation graphs. diff --git a/tests/sandboxes/screenshot_circular/zenzic.toml b/tests/sandboxes/screenshot_circular/zenzic.toml new file mode 100644 index 0000000..d1862fe --- /dev/null +++ b/tests/sandboxes/screenshot_circular/zenzic.toml @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2026 PythonWoods +# SPDX-License-Identifier: Apache-2.0 + +docs_dir = "docs" + +[build_context] +engine = "mkdocs" diff --git a/tests/test_cli_e2e.py b/tests/test_cli_e2e.py new file mode 100644 index 0000000..53ac0e6 --- /dev/null +++ b/tests/test_cli_e2e.py @@ -0,0 +1,237 @@ +# SPDX-FileCopyrightText: 2026 PythonWoods +# SPDX-License-Identifier: Apache-2.0 +"""End-to-end CLI tests for Zenzic security exit-code contract. + +These tests exercise the **full** CLI pipeline — no mocks on the scanner, +validator, or reporter. They verify the documented exit-code contract: + + Exit 0 — all checks passed + Exit 1 — general failures (broken links, syntax errors, …) + Exit 2 — Shield credential detection (NEVER suppressed by --exit-zero) + Exit 3 — Blood Sentinel system-path traversal (NEVER suppressed) + +Gap closed: ``docs/internal/arch_gaps.md`` § "Security Pipeline Coverage". +""" + +from __future__ import annotations + +import shutil +import textwrap +from pathlib import Path + +import pytest +from typer.testing import CliRunner + +from zenzic.main import app + + +runner = CliRunner() + +_BLOOD_SANDBOX = Path(__file__).resolve().parent / "sandboxes" / "screenshot_blood" + + +# ── helpers ────────────────────────────────────────────────────────────────── + + +def _make_sandbox(tmp_path: Path, files: dict[str, str]) -> Path: + """Create a minimal Zenzic project in *tmp_path*. + + Writes ``zenzic.toml`` and the given *files* (paths relative to the + sandbox root). Returns the sandbox root. + """ + toml = tmp_path / "zenzic.toml" + toml.write_text( + textwrap.dedent("""\ + docs_dir = "docs" + + [build_context] + engine = "mkdocs" + """), + encoding="utf-8", + ) + for rel, content in files.items(): + p = tmp_path / rel + p.parent.mkdir(parents=True, exist_ok=True) + p.write_text(textwrap.dedent(content), encoding="utf-8") + return tmp_path + + +# ── Blood Sentinel — Exit 3 (system-path traversal) ───────────────────────── + + +class TestBloodSentinelE2E: + """Blood Sentinel must exit 3 on system-path traversal.""" + + def test_blood_sandbox_exits_3(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """check all on the blood sandbox triggers Exit 3.""" + sandbox = tmp_path / "blood" + shutil.copytree(_BLOOD_SANDBOX, sandbox) + monkeypatch.chdir(sandbox) + + result = runner.invoke(app, ["check", "all"]) + + assert result.exit_code == 3, ( + f"Expected exit 3 (security_incident), got {result.exit_code}.\n" + f"Output:\n{result.stdout}" + ) + assert "PATH_TRAVERSAL_SUSPICIOUS" in result.stdout + + def test_blood_exit_3_not_suppressed_by_exit_zero( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + ) -> None: + """--exit-zero must NOT suppress Exit 3 — documented contract.""" + sandbox = tmp_path / "blood" + shutil.copytree(_BLOOD_SANDBOX, sandbox) + monkeypatch.chdir(sandbox) + + result = runner.invoke(app, ["check", "all", "--exit-zero"]) + + assert result.exit_code == 3, ( + f"--exit-zero must not suppress Exit 3 (security_incident), " + f"got {result.exit_code}.\nOutput:\n{result.stdout}" + ) + + +# ── Shield Breach — Exit 2 (credential leak) ──────────────────────────────── + + +class TestShieldBreachE2E: + """Shield must exit 2 when a credential is detected.""" + + _BREACH_DOC = """\ + # Cloud Setup + + This page documents the initial cloud provider configuration steps + for the deployment pipeline. Follow the instructions carefully. + + ## Provider Credentials + + Refer to the provisioning guide for credential rotation procedures + and the secret management policy before deploying to production. + + [AWS Dashboard](https://console.aws.amazon.com?key=AKIA1234567890ABCDEF) + """ + + def test_shield_breach_exits_2(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """check all exits 2 when an AWS key is embedded in a link URL.""" + _make_sandbox(tmp_path, {"docs/index.md": self._BREACH_DOC}) + monkeypatch.chdir(tmp_path) + + result = runner.invoke(app, ["check", "all"]) + + assert result.exit_code == 2, ( + f"Expected exit 2 (security_breach), got {result.exit_code}.\nOutput:\n{result.stdout}" + ) + assert "ZENZIC SENTINEL" in result.stdout + + def test_shield_exit_2_not_suppressed_by_exit_zero( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + ) -> None: + """--exit-zero must NOT suppress Exit 2 — documented contract.""" + _make_sandbox(tmp_path, {"docs/index.md": self._BREACH_DOC}) + monkeypatch.chdir(tmp_path) + + result = runner.invoke(app, ["check", "all", "--exit-zero"]) + + assert result.exit_code == 2, ( + f"--exit-zero must not suppress Exit 2 (security_breach), " + f"got {result.exit_code}.\nOutput:\n{result.stdout}" + ) + + +# ── --exit-zero suppresses Exit 1 (general errors) ────────────────────────── + + +class TestExitZeroContractE2E: + """--exit-zero suppresses general failures but not security exits.""" + + _BROKEN_LINK_DOC = """\ + # Home + + This page contains a broken link to exercise the general failure + exit path. The target file does not exist in this sandbox. + + [Broken](this-page-does-not-exist.md) + """ + + def test_broken_link_exits_1(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """check all exits 1 on a broken link (baseline).""" + _make_sandbox(tmp_path, {"docs/index.md": self._BROKEN_LINK_DOC}) + monkeypatch.chdir(tmp_path) + + result = runner.invoke(app, ["check", "all"]) + + assert result.exit_code == 1, ( + f"Expected exit 1 (general failure), got {result.exit_code}.\nOutput:\n{result.stdout}" + ) + + def test_exit_zero_suppresses_exit_1( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + ) -> None: + """--exit-zero must suppress Exit 1 (broken links).""" + _make_sandbox(tmp_path, {"docs/index.md": self._BROKEN_LINK_DOC}) + monkeypatch.chdir(tmp_path) + + result = runner.invoke(app, ["check", "all", "--exit-zero"]) + + assert result.exit_code == 0, ( + f"--exit-zero should suppress Exit 1, got {result.exit_code}.\nOutput:\n{result.stdout}" + ) + + def test_clean_sandbox_exits_0(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """A clean sandbox with no issues exits 0.""" + _make_sandbox( + tmp_path, + { + "docs/index.md": """\ + # Welcome + + This is a perfectly valid documentation page with enough words + to pass the placeholder check. It contains no broken links, + no credentials, and no path traversal attempts. The content + is intentionally verbose to exceed the minimum word count + threshold enforced by the short-content scanner. + """, + }, + ) + monkeypatch.chdir(tmp_path) + + result = runner.invoke(app, ["check", "all"]) + + assert result.exit_code == 0, ( + f"Expected exit 0 (clean), got {result.exit_code}.\nOutput:\n{result.stdout}" + ) + assert "All checks passed" in result.stdout + + +# ── Priority: Exit 3 wins over Exit 2 ─────────────────────────────────────── + + +class TestExitCodePriorityE2E: + """When both security_incident and security_breach coexist, Exit 3 wins.""" + + def test_exit_3_beats_exit_2(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Exit 3 (Blood Sentinel) takes priority over Exit 2 (Shield breach).""" + _make_sandbox( + tmp_path, + { + "docs/index.md": """\ + # Dual Threat + + This page has both a system-path traversal and a leaked + credential to verify the exit-code priority contract. + + [Host](../../../../etc/shadow) + + [AWS](https://console.aws.amazon.com?key=AKIA1234567890ABCDEF) + """, + }, + ) + monkeypatch.chdir(tmp_path) + + result = runner.invoke(app, ["check", "all"]) + + assert result.exit_code == 3, ( + f"Exit 3 (security_incident) must beat Exit 2 (security_breach), " + f"got {result.exit_code}.\nOutput:\n{result.stdout}" + )