Skip to content

chore(ci): doc-link integrity + secret/host-leak guards in the test gate (#130)#131

Merged
mvalancy merged 3 commits into
devfrom
chore/ci-doc-secret-guards
Jul 23, 2026
Merged

chore(ci): doc-link integrity + secret/host-leak guards in the test gate (#130)#131
mvalancy merged 3 commits into
devfrom
chore/ci-doc-secret-guards

Conversation

@mvalancy

Copy link
Copy Markdown
Owner

Closes #130.

Why

The #129 doc-hierarchy cleanup exposed two defect classes current CI never caught:

  1. A real host name (nas-5sgf) sat committed in tests/README.md and passed every run — a Prime-Directive-1 miss on a public repo.
  2. Nothing verified the .md hierarchy's internal links/anchors actually resolve — the docs: map + interconnect the .md hierarchy, AGENTS.md per folder, readability #129 review had to hand-build a resolver.

Both are cheap to catch mechanically and expensive to catch by eye. This adds them to the gate and hardens the CI process.

What

Two offline test-*.sh validators, auto-discovered by run-all.sh's test-*.sh glob (so they gate locally and in CI), plus a fast-fail CI step:

  • tests/lib/md_link_check.py + test-doc-links.sh — resolves every internal Markdown link and #anchor across all tracked .md (GitHub-slug compatible; strips fenced/inline code; honors <a name>/id=). Fails on any broken path or dead anchor. External URL liveness stays in test-urls.sh.
  • tests/lib/secret_scan.py + test-secrets.sh + secrets-allowlist.txt — fails on: a tracked .env/private-key file; a concrete RFC1918 private IP; a private-TLD host (.local/.lan/.internal/…); or a bare single-label URL host (the nas-5sgf class). Public dotted domains, loopback, and $/{}/<> placeholders pass automatically. Reviewed exceptions live in the allowlist — a ratchet: any new leak still fails.
  • .github/workflows/ci.yml — runs both guards as a fail-fast step (python3-only, preinstalled) before the multi-minute Playwright browser install, so a leak/broken-link fails in seconds.

Proof it works (not a checker that never fails)

  • Link checker negative tests: catches a broken relative path, a dead #anchor, and a broken image; passes valid links; slug matches GitHub (Community & Commscommunity--comms, emphasis stripped, U+2011 dropped). It also caught my own wrong anchor in the new decisions.md entry.
  • Secret scanner negative tests: catches http://nas-5sgf:3000, 192.168.4.73 (both paths), ssh://buildbox.internal, a tracked .env, and a .pem; passes github.com/localhost/redis/your-ark-host.
  • Both exit 0 on the current tree (0 false positives across 63 .md files); every allowlist entry is a reviewed doc example, protocol constant, or test fixture.

Docs

tests/AGENTS.md (+ an allowlist gotcha: fix the leak, not the test), tests/README.md, and a docs/knowledge/decisions.md entry (prepend; body unchanged).

🤖 Generated with Claude Code

mvalancy and others added 2 commits July 23, 2026 00:19
… gate (#130)

The #129 doc cleanup surfaced two defect classes CI never caught: a real host
name (nas-5sgf) committed in tests/README.md, and no verification that the .md
hierarchy's internal links/anchors resolve. Both are cheap to catch mechanically.

- tests/lib/md_link_check.py + test-doc-links.sh: offline resolver for every
  internal Markdown link + #anchor across all tracked .md (GitHub-slug compatible;
  honors <a name>/id=). Fails on any broken path or dead anchor.
- tests/lib/secret_scan.py + test-secrets.sh + secrets-allowlist.txt: offline
  scan that fails on a tracked .env/private-key, a concrete RFC1918 private IP, a
  private-TLD host (.local/.lan/...), or a bare single-label URL host (the
  nas-5sgf class). Public dotted domains, loopback, and $/{}/<> placeholders pass;
  reviewed exceptions live in the allowlist (a ratchet — new leaks still fail).
- Both auto-discovered by run-all.sh's test-*.sh glob (gate locally + in CI) and
  run as a fail-fast CI step BEFORE the Playwright browser install.
- Proven by negative tests: catches nas-5sgf, a private IP, an .internal FQDN, a
  tracked .env, and a .pem; passes github.com/localhost/redis/placeholders.
- Docs: tests/AGENTS.md (+ allowlist gotcha), tests/README.md, decisions.md entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review of #131 caught that the new files re-committed the very host name #129
scrubbed (in secret_scan.py's docstring incl. the full URL form, test-secrets.sh,
and the decisions.md entry) — and the scanner couldn't self-catch it (own files
SKIP_EXACT'd; the prose mention had no scheme). Removed all four; git grep clean.

Hardening from the same review's non-blocking findings (all zero-FP on the tree,
negative-tested):
- secret_scan: URL_HOST_RE now covers DB/queue/socket schemes
  (redis/postgres/mongodb/amqp/mysql/mqtt/ws(s)) — a real host in a connection
  string is the same leak class; bracketed IPv6 literals survive capture and a
  private ULA (fc00::/fd00::) in a URL host is flagged (public IPv6 passes).
- secret_scan: prefixed *.env files (production.env) now caught, matching the
  docstring; example/sample/template/dist env files stay exempt.
- secret_scan: high-signal inline content secrets — private-key bodies and AWS
  access-key ids — in any tracked text file.
- md_link_check: reference-style links `[text][label]` (defined labels only, so
  incidental `[x][y]` prose is ignored), internal `<a href>`, and `?query`
  stripping so `real.md?v=1#frag` resolves.

Both guards remain 0-FP across 63 .md files; decisions.md stays a pure prepend.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…bidOnly (#132)

The escattr-sweep catalog test intermittently failed CI (run 29987925904:
.catalog-card not found in 10s) though it passes on dev and #131 touches no
UI/spec code — a genuine flake from the SPA's async render timing (root cause
tracked in #132: loadCatalog fires once per route with no retry on a transient
no-op). Add `retries: process.env.CI ? 2 : 0` so a real regression still fails
every attempt while a transient flake self-heals, and `forbidOnly` so a stray
`.only` can't silently green-light a partial CI run. This is resilience, not a
mask — #132 tracks the product-side fix. Config validated (387 tests list, exit 0).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mvalancy mvalancy closed this Jul 23, 2026
@mvalancy mvalancy reopened this Jul 23, 2026
@mvalancy
mvalancy merged commit dfe8761 into dev Jul 23, 2026
1 of 2 checks passed
@mvalancy
mvalancy deleted the chore/ci-doc-secret-guards branch July 23, 2026 08:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant