From c23da602e532dc1ed712b6fc9678e1f65a3decf8 Mon Sep 17 00:00:00 2001 From: Sonali21 Date: Mon, 7 Sep 2026 00:38:17 +0530 Subject: [PATCH] docs: guard generated mdBook links --- .github/workflows/docs.yml | 3 ++ docs/SUMMARY.md | 3 ++ docs/cli.md | 4 +-- docs/contributing/CONTRIBUTING.md | 2 +- docs/contributing/testing.md | 4 +-- docs/the-audit-chain.md | 2 +- scripts/check-mdbook-links.sh | 41 +++++++++++++++++++++++++ tests/release/mdbook-links.test.sh | 49 ++++++++++++++++++++++++++++++ 8 files changed, 102 insertions(+), 6 deletions(-) create mode 100755 scripts/check-mdbook-links.sh create mode 100755 tests/release/mdbook-links.test.sh diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 540d7c17..81477629 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -63,6 +63,9 @@ jobs: mdbook-admonish install . mdbook build + - name: Check generated HTML links + run: tests/release/mdbook-links.test.sh + - name: Upload Pages artifact uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5 with: diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 6518314d..278904a4 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -10,6 +10,7 @@ - [MCP Server](mcp.md) - [Registry and Directory Listings](mcp-registry.md) - [CLI Reference](cli.md) +- [User Stories](user-stories.md) - [Configure Your LLM](configuration.md) - [Distro Support](distro-support.md) @@ -43,7 +44,9 @@ - [Developer Guide](developer-guide.md) - [Testing Guide](contributing/testing.md) +- [Ubuntu VM Testing](contributing/ubuntu-vm-testing.md) - [Contributing](contributing/CONTRIBUTING.md) - [Ubuntu Story Audit](contributing/ubuntu-story-audit.md) - [Release Process](release.md) - [Release Readiness](release-readiness.md) +- [Testing User Stories](testing/user-stories.md) diff --git a/docs/cli.md b/docs/cli.md index e07ccc83..13a83d66 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -5,7 +5,7 @@ natural-language intent into a risk-labelled plan, asks for approval where needed, and streams execution output in real time. If you want SysKnife inside Claude Code / Cursor / Codex CLI instead, see -the [main README](../README.md) and run `npx sysknife-setup`. Both paths +the [main README](https://github.com/lacs-project/sysknife/blob/main/README.md) and run `npx sysknife-setup`. Both paths share the daemon, the audit chain, and the typed-action set. startup and `sysknife audit verify` repeats it beside every verdict, because > `OK: N rows verified` would otherwise read as "nothing was removed". Setup > instructions are in -> [SECURITY.md](../SECURITY.md#audit-anchoring-in-the-default-deployment). +> [SECURITY.md](https://github.com/lacs-project/sysknife/blob/main/SECURITY.md#audit-anchoring-in-the-default-deployment). A hash chain alone cannot detect one specific attack: **tail truncation**. If an attacker with write access to the audit database deletes the most diff --git a/scripts/check-mdbook-links.sh b/scripts/check-mdbook-links.sh new file mode 100755 index 00000000..40779d87 --- /dev/null +++ b/scripts/check-mdbook-links.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +book_dir="${1:-book}" + +python3 - "$book_dir" <<'PY' +import pathlib +import re +import sys +import urllib.parse + +book = pathlib.Path(sys.argv[1]) +if not book.is_dir(): + raise SystemExit(f"mdbook-links: book directory does not exist: {book}") + +href_pattern = re.compile(r'href="([^"]+)"') +checked = 0 +broken = [] + +for page in sorted(book.rglob("*.html")): + if page.name == "print.html": + continue + for href in href_pattern.findall(page.read_text(errors="replace")): + if href.startswith(("http://", "https://", "#", "mailto:", "//")): + continue + target = urllib.parse.unquote(href.split("#", 1)[0].split("?", 1)[0]) + if not target.endswith(".html"): + continue + checked += 1 + if not (page.parent / target).resolve().exists(): + broken.append(f"{page}\t{href}") + +if checked == 0: + raise SystemExit("mdbook-links: no internal .html links were checked") +if broken: + print("mdbook-links: broken generated links:", file=sys.stderr) + print("\n".join(broken), file=sys.stderr) + raise SystemExit(1) + +print(f"mdbook-links: checked {checked} internal .html links") +PY \ No newline at end of file diff --git a/tests/release/mdbook-links.test.sh b/tests/release/mdbook-links.test.sh new file mode 100755 index 00000000..0a8d52f7 --- /dev/null +++ b/tests/release/mdbook-links.test.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +checker="$repo_root/scripts/check-mdbook-links.sh" +fixture="$(mktemp -d)" +trap 'rm -rf "$fixture"' EXIT + +mkdir -p "$fixture/book" +cat > "$fixture/book/index.html" <<'EOF' +guide +EOF +cat > "$fixture/book/guide.html" <<'EOF' +external +EOF + +"$checker" "$fixture/book" + +rm "$fixture/book/guide.html" +if output="$($checker "$fixture/book" 2>&1)"; then + printf 'mdbook-links: missing generated page unexpectedly passed\n' >&2 + exit 1 +fi +grep -Fq 'index.html' <<< "$output" || { + printf 'mdbook-links: missing-page error omitted its source: %s\n' "$output" >&2 + exit 1 +} + +cat > "$fixture/book/index.html" <<'EOF' +external only +EOF +if output="$($checker "$fixture/book" 2>&1)"; then + printf 'mdbook-links: zero-link fixture unexpectedly passed\n' >&2 + exit 1 +fi +grep -Fq 'no internal .html links were checked' <<< "$output" || { + printf 'mdbook-links: zero-link error was not explicit: %s\n' "$output" >&2 + exit 1 +} + +if ! command -v mdbook >/dev/null 2>&1 || ! command -v mdbook-admonish >/dev/null 2>&1; then + printf 'mdbook-links: SKIP real mdBook build (mdbook and mdbook-admonish are required)\n' + exit 0 +fi + +build_dir="$fixture/real-book" +mdbook-admonish install "$repo_root" +mdbook build --dest-dir "$build_dir" "$repo_root" +"$checker" "$build_dir" \ No newline at end of file