fix(security): resolve all Dependabot advisories and audit gaps - #1
Conversation
Supersedes the outstanding Dependabot PR (`bytes` 1.11.0 -> 1.11.1) and
remediates every RustSec advisory affecting a crate this project compiles.
Before: 9 vulnerabilities + 10 unmaintained/unsound warnings in the workspace,
and a `fuzz` lockfile nothing had ever audited.
After: `cargo audit` and `cargo deny check` both exit 0, workspace and fuzz.
## Fixed by dependency updates alone
`bytes` >=1.11.1 (RUSTSEC-2026-0007, integer overflow in `BytesMut::reserve`),
`crossbeam-epoch` >=0.9.20 (RUSTSEC-2026-0204), `rkyv` >=0.8.17
(RUSTSEC-2026-0233/0234/0235: use-after-free and out-of-bounds reads
deserialising crafted archives), `time` >=0.3.47 (RUSTSEC-2026-0009, DoS via
stack exhaustion), plus unsound-flagged `memmap2` >=0.9.11, `anyhow` >=1.0.103,
`rand` >=0.8.6 and `event-listener`. All semver-compatible; no manifest edits.
## Two dependencies were declared but never imported
The two highest-severity advisories in this project came from a crate that
contributed nothing to it.
- **`quick-xml` removed** -- RUSTSEC-2026-0194 and RUSTSEC-2026-0195, both 7.5
high (quadratic parse time on duplicate attribute names; unbounded
namespace-declaration allocation). It appeared only in
`crates/prtip-cli/Cargo.toml`; no source file imports it. ProRT-IP writes its
Nmap-compatible XML by hand.
- **`tui-input` removed** -- likewise never imported, and the sole reason a
second `ratatui` (0.28.1) was being built alongside 0.29.0.
## Upgrades that clear the remaining warnings
- **`ratatui` 0.29 -> 0.30** drops `paste` (RUSTSEC-2024-0436, unmaintained)
and moves to `lru` >=0.18.2, fixing RUSTSEC-2026-0002 and RUSTSEC-2026-0253.
Checked the 0.30 breaking-change surface against this codebase first --
`Alignment` rename, `block::Title`/`Position`, `highlight_symbol`,
`WidgetRef`, custom `Backend` impls, `Styled`, braille symbols -- and
ProRT-IP uses none of it. Zero code edits required.
- **`indicatif` 0.17 -> 0.18** replaces `number_prefix` (RUSTSEC-2025-0119,
unmaintained) with `unit-prefix`. `crates/prtip-scanner` had pinned its own
`indicatif = "0.17"` outside the workspace table, so it was building a second
copy; it now uses the workspace version.
- **`console` 0.15 -> 0.16, `crossterm` 0.28 -> 0.29** to deduplicate two
crates left doubled by the upgrades above.
## The fuzz crate was never audited
`fuzz` is excluded from the workspace and keeps its own `Cargo.lock`, so
neither `cargo deny` nor a workspace `cargo audit` ever read it. It carried
three unfixed `rustls-webpki` advisories that do not appear in the root at all
-- RUSTSEC-2026-0098 and -0099 (name constraints wrongly accepted for URI names
and for wildcard-asserting certificates) and RUSTSEC-2026-0104 (reachable panic
parsing certificate revocation lists) -- plus `bytes`, `crossbeam-epoch` and
`time`. Its lockfile is updated and CI now audits it.
## rsa: exempted, not fixed
RUSTSEC-2023-0071 (Marvin Attack) has no fixed version. `rsa` is never
compiled: it reaches the lockfile only through `sqlx-mysql`, an *optional*
dependency of `sqlx`, and this workspace enables only
`["runtime-tokio", "sqlite", "chrono"]`. Cargo records optional dependencies in
the lockfile whether or not their feature is on.
Verified rather than argued:
cargo tree -i rsa --workspace --target all -> nothing to print
cargo tree -i sqlx-mysql --workspace --target all -> nothing to print
The exemption is in `.cargo/audit.toml` (mirrored in `fuzz/.cargo/`), with the
condition under which it must be withdrawn. It is deliberately *not* in
`deny.toml`: cargo-deny resolves the feature-aware graph and never encounters
`rsa`, so an entry there would only emit "advisory-not-detected" -- and would
mask the advisory if a future change made `rsa` genuinely reachable.
## A real bug found while removing quick-xml
Removing an XML library from a project that hand-writes XML meant auditing what
it hand-writes. `crates/prtip-cli/src/output.rs` escaped the banner but
interpolated `result.service` straight into `name="..."`. Both are chosen by the
host being scanned, so a hostile target could close the attribute and inject
elements into a scan report -- a forgery against whatever consumes it. The
parallel implementation in `export.rs` escaped both correctly, which is the
inconsistency two hand-rolled copies invite.
Both now use one implementation, `crates/prtip-cli/src/xml.rs`, hardened beyond
the five metacharacters: XML 1.0 forbids most control characters *even as
numeric references*, so a banner containing a NUL -- unremarkable from a binary
protocol -- previously produced a document conforming parsers reject. Those are
rendered as visible `\xNN`, matching how Nmap presents non-printable banner
bytes, so evidence survives and the document stays valid. Nine tests, including
a negative control walking every byte value asserting no illegal code point
survives, and an attribute-breakout attempt.
## Keeping it fixed
- **`.github/dependabot.yml` added.** There was no Dependabot config at all, so
the repository got only the security PRs GitHub opens unprompted; routine
updates were never proposed. That is how a `quick-xml` five minor versions
behind and a duplicate `ratatui` major went unnoticed. Updates are grouped --
minor/patch batched, majors separate, security together -- because ungrouped
updates on a workspace this size produce enough PRs to be ignored, which is
indistinguishable from having none. Covers cargo (root and `fuzz`), GitHub
Actions, and `docker/`.
- **CI runs all four cargo-deny checks**, not just `advisories`. The licence
check had never run: `deny.toml` still allowed the deprecated `GPL-3.0`
identifier while the workspace now declares `GPL-3.0-or-later`, so
`cargo deny check licenses` failed locally and CI could not have caught it.
- **`.gitignore` fixed** so `audit.toml` is tracked. `.cargo/` was excluded
wholesale, which silently dropped both exemption files from the first staging
attempt and would have failed CI's fuzz audit. The pattern must be
`.cargo/*` with a `!.cargo/audit.toml` negation -- git does not descend into
an excluded directory, so a negation under `.cargo/` is never consulted.
- Stale `deny.toml` ignores for `paste` and `number_prefix` removed; both
crates are gone from the tree entirely.
## Verification
cargo audit exit 0 (workspace)
cargo audit (in fuzz/) exit 0
cargo deny check exit 0 advisories, licenses, bans, sources
cargo fmt --all -- --check exit 0
cargo clippy --workspace --all-targets -- -D warnings exit 0
cargo test --workspace 2,581 passed, 0 failed, 121 ignored
Test count is 2,563 -> 2,581; the +18 is the 9 new `xml.rs` tests, which
`prtip-cli` compiles into both its lib and its bin.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBDCJvLbq7nuor7RtT57rD
|
🤖 Hi @doublegate, I've received your request, and I'm working on it now! You can track my progress in the logs for more details. |
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
🤖 I'm sorry @doublegate, but I was unable to process your request. Please see the logs for more details. |
There was a problem hiding this comment.
🟡 Changes recommended
The new XML escaping currently loses evidence for U+FFFE/U+FFFF by emitting a fixed \u{fffd} literal (and tests codify it), which contradicts the module’s stated evidence-preserving goal.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR remediates Rust supply-chain/security findings across the workspace (and the out-of-workspace fuzz crate) by upgrading vulnerable dependencies, removing unused advisory-carrying crates, and hardening hand-written Nmap-compatible XML output escaping.
Changes:
- Update Rust dependencies to eliminate active RustSec advisories/unsoundness warnings; remove unused
quick-xmlandtui-input, and deduplicate progress/TUI stacks via workspace dependency alignment. - Centralize and strengthen XML escaping for
-oXoutput intocrates/prtip-cli/src/xml.rs, and route both XML emitters through it. - Add/enable ongoing hygiene: Dependabot configuration, full cargo-deny checks in CI, and
cargo auditcoverage for the separatefuzzlockfile (with mirrored audit exemptions).
File summaries
| File | Description |
|---|---|
| fuzz/Cargo.lock | Updates fuzz-target dependency graph to clear advisories in its separate lockfile. |
| fuzz/.cargo/audit.toml | Adds fuzz-local cargo audit ignore config mirroring the root rationale for rsa. |
| deny.toml | Removes stale ignores and updates license allowlist to match declared project licensing. |
| crates/prtip-tui/Cargo.toml | Drops unused tui-input dependency (and associated duplicate ratatui build). |
| crates/prtip-scanner/Cargo.toml | Switches indicatif to the workspace version to avoid duplicate builds and pick up fixes. |
| crates/prtip-cli/src/xml.rs | Introduces shared XML escaping implementation + tests for safe -oX output. |
| crates/prtip-cli/src/output.rs | Uses shared escape_xml for service name + banner in XML formatter. |
| crates/prtip-cli/src/main.rs | Registers the new xml module in the CLI binary crate. |
| crates/prtip-cli/src/lib.rs | Exposes the new xml module from the CLI library crate. |
| crates/prtip-cli/src/export.rs | Removes local XML escaping helper and uses shared escape_xml. |
| crates/prtip-cli/Cargo.toml | Removes unused quick-xml and bumps console to align with dependency updates. |
| CHANGELOG.md | Documents advisory remediation, XML hardening, CI/Dependabot additions, and supply-chain changes. |
| Cargo.toml | Bumps workspace dependency versions (e.g., indicatif, ratatui, crossterm). |
| .gitignore | Adjusts .cargo ignore rules to ensure audit config files remain tracked. |
| .github/workflows/ci.yml | Runs full cargo-deny checks and adds cargo audit for the fuzz lockfile. |
| .github/dependabot.yml | Adds Dependabot config with grouping for Cargo (workspace + fuzz), actions, and docker. |
| .cargo/audit.toml | Adds cargo audit ignore config documenting and constraining the rsa exemption. |
Review details
Suppressed comments (1)
crates/prtip-cli/src/xml.rs:172
- This test encodes the current non-character behavior; if
escape_xmlpreserves U+FFFE/U+FFFF visibly (rather than mapping both to\\u{fffd}), the assertions should be updated accordingly so the test continues to verify evidence-preserving escaping.
#[test]
fn renders_non_characters() {
assert_eq!(escape_xml("a\u{FFFE}b"), "a\\u{fffd}b");
assert_eq!(escape_xml("a\u{FFFF}b"), "a\\u{fffd}b");
}
- Files reviewed: 15/18 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| // Non-characters, likewise illegal. | ||
| '\u{FFFE}' | '\u{FFFF}' => out.push_str("\\u{fffd}"), | ||
|
|
| - name: Install cargo-audit | ||
| run: cargo install cargo-audit --locked | ||
|
|
||
| - name: Audit fuzz lockfile | ||
| working-directory: fuzz | ||
| run: cargo audit |
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
The MSRV check failed on the previous commit, and pinning around it turned out
to be impossible rather than merely awkward.
RUSTSEC-2026-0009 is a denial of service via stack exhaustion in `time`.
ProRT-IP reaches `time` through `x509-parser` while parsing certificates from
scanned hosts, so the input is attacker-influenced and the advisory is
genuinely reachable -- not a lockfile artifact.
Every `time` release that fixes it declares `rust-version = "1.88.0"`:
time 0.3.47 1.88.0 time 0.3.52 1.88.0
time 0.3.49 1.88.0 time 0.3.53 1.88.0
time 0.3.51 1.88.0 time 0.3.54 1.88.0
time 0.3.55 1.88.0
There is no version that is both patched and buildable on 1.85, so holding the
old floor meant shipping the advisory. Checked before concluding this: a
*targeted* update of only the advisory-affected crates, leaving `time` at
0.3.44, holds the graph at exactly `main`'s MSRV baseline -- so the floor is
being raised for this one advisory, not by dependency drift.
Raising it also unblocks `ratatui` 0.30, which needs 1.88 and clears three more
advisories (`paste` RUSTSEC-2024-0436 unmaintained; `lru` RUSTSEC-2026-0002 and
RUSTSEC-2026-0253 unsound, via `lru` >=0.18.2). ProRT-IP uses none of ratatui
0.30's breaking-change surface, so no code edits were needed. `crossterm` moves
0.28 -> 0.29 to match its backend and stay deduplicated.
Result: `deny.toml` now has **no advisory exemptions at all**. The previously
documented ignores for `paste` and `number_prefix` are gone because both crates
have left the tree.
Verified that nothing in the resolved graph needs more than the new floor:
499 packages resolved, 0 require > 1.88
That also fixes a pre-existing violation -- `home` 0.5.12 already required 1.88
and was quietly breaking the 1.85 promise on `main`.
MSRV updated everywhere it is stated, not just the manifest: `Cargo.toml`,
the README badge and requirements list, `WARP.md`, `CONTRIBUTING.md`, the
pull-request template checklist, and the CI job name and toolchain pin.
One code change fell out of it: `clippy::manual_is_multiple_of` is MSRV-gated
(the method stabilised in 1.87), so raising the floor turned it on.
`mmap_writer.rs`'s compile-time alignment assertion now reads
`ENTRY_SIZE.is_multiple_of(16)`.
Verification:
cargo audit exit 0 (workspace)
cargo audit (in fuzz/) exit 0
cargo deny check exit 0 all four checks
cargo fmt --all -- --check exit 0
cargo clippy --workspace --all-targets --locked -- -D warnings exit 0
cargo test --workspace 2,581 passed, 0 failed, 121 ignored
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBDCJvLbq7nuor7RtT57rD
The markdown link check failed on `https://docs.ansible.com/ansible/latest/` with "Status: 429", reported as a dead link. A 429 is the server answering. It proves the host exists, resolves, and is serving -- it is declining this particular request, which is the opposite of link rot. The config already sets `retryOn429` with three retries, but docs.ansible.com throttles CI runners harder than that, so the retries expire and the build fails on a link that is demonstrably fine. 429 now sits in `aliveStatusCodes` alongside 403 and 999, which are already there for the same reason: the server responded, just not with content. Unrelated to this branch's dependency work; it was failing on `main` too, and blocks the PR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBDCJvLbq7nuor7RtT57rD
The link checker reported `#hotfix-procedures` as a 404 even though `## Hotfix Procedures` exists in the same file at line 585, outside any code fence, in clean UTF-8. It is the only bare same-file anchor in the document. Qualifying it as `release-process.md#hotfix-procedures` resolves identically on GitHub and in mdBook, where each page renders separately, and gives the checker a file to resolve against. Pre-existing; surfaced only once the 429 misclassification stopped failing the same job first. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBDCJvLbq7nuor7RtT57rD
The link check failed three consecutive runs on three different external hosts
-- a 429 from docs.ansible.com, then a connection failure to bgp.tools -- while
seven genuinely broken internal links sat undetected behind them. Fixing the
external symptoms one at a time was never going to converge, because the
failures were not defects in the repository.
## Seven broken internal links, previously invisible
`to-dos/PHASE-5/SPRINT-5.5.5-TODO.md` and `SPRINT-5.5.6-TODO.md` link to
`../docs/00-ARCHITECTURE.md`, `../docs/31-BENCHMARKING-GUIDE.md`,
`../docs/34-PERFORMANCE-CHARACTERISTICS.md` and
`../benchmarks/profiling/PROFILING-ANALYSIS.md`. From `to-dos/PHASE-5/` that
resolves to `to-dos/docs/...`, one directory short. All four targets exist; the
links now use `../../`.
They were never reported because the `to-dos/` step ended with `|| true`, which
discards every failure in that directory.
## Merges no longer depend on other people's uptime
The job validated external links on every pull request. Whether a branch could
merge therefore depended on whether unrelated websites chose to answer a GitHub
runner -- non-deterministic, unactionable by the author, and the fastest way to
train everyone to ignore a red check.
Two modes now:
pull_request / push -> internal links only (relative paths, anchors).
Deterministic, always the author's to fix.
schedule / dispatch -> internal + external. Link rot still gets caught
weekly, without blocking anyone.
Verified across all 232 markdown files in scope: zero broken internal links.
## Three other ways this check was lying
- **`mlc_config.json` was inert.** The workflow generated its own config over
the top of the committed file, so editing the committed one changed nothing.
It is now the single source of truth; the workflow layers only the PR-mode
`^https?://` ignore onto it.
- **The summary always claimed success.** The report step ran with
`if: always()` and unconditionally printed "✅ All markdown links validated
successfully" -- including on a failed run. It now prints the file counts and
the real outcome.
- **The first broken link hid the rest.** The loop exited at the first failure.
It now checks every file, groups each failure in the log, and reports how many
files failed before exiting non-zero.
`aliveStatusCodes` keeps 429 alongside 403 and 999: all three mean the server
answered and declined this particular request, which proves the host resolves.
`mlc_effective.json` and `files.txt` are generated at run time and gitignored.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBDCJvLbq7nuor7RtT57rD
…sl binary Fixes two Dockerfile defects found while merging the Alpine bump (#2). - Builder pinned Rust 1.85, below the 1.88 MSRV raised in #1 -- the image could not compile the project. - The alpine stage copied a Debian glibc binary into a musl image, producing a container in which prtip could not exec at all. A real musl-builder stage now compiles natively on rust:1.88-alpine. Verified: both images build and run prtip 1.0.0; alpine links ld-musl-x86_64.so.1 at 26.9 MB. New CI job builds both targets, runs the binary in each, and asserts musl linkage.
Important
Breaking: MSRV rises 1.85 → 1.88.
This is forced, not incidental. RUSTSEC-2026-0009 is a DoS via stack exhaustion in
time, which ProRT-IP reaches throughx509-parserwhen parsing certificates from scanned hosts — attacker-influenced input, so genuinely reachable. Everytimerelease that fixes it (0.3.47 through 0.3.55) declaresrust-version = "1.88.0"; there is no version both patched and buildable on 1.85. Holding the old floor meant shipping the advisory.I confirmed this wasn't just dependency drift: a targeted update of only the advisory-affected crates, leaving
timealone, holds the graph at exactlymain's MSRV baseline. The floor moves for this one advisory.It also fixes a pre-existing violation —
home0.5.12 already required 1.88 and was quietly breaking the 1.85 promise onmain. Verified afterwards: 499 packages resolved, 0 require > 1.88.Updated in
Cargo.toml, the README badge and requirements,WARP.md,CONTRIBUTING.md, the PR-template checklist, and the CI job.Encapsulates the outstanding Dependabot PR (
bytes1.11.0 → 1.11.1) and remediates every RustSec advisory affecting a crate this project compiles.cargo audit(workspace)cargo audit(fuzz/)cargo deny checkcargo test --workspaceFixed by dependency updates alone
All semver-compatible; no manifest edits needed.
bytes→ ≥1.11.1BytesMut::reservecrossbeam-epoch→ ≥0.9.20rkyv→ ≥0.8.17time→ ≥0.3.47memmap2≥0.9.11,anyhow≥1.0.103,rand≥0.8.6,event-listenerTwo dependencies were declared but never imported
The two highest-severity advisories in this project came from a crate that contributed nothing to it.
quick-xmlremoved — RUSTSEC-2026-0194 and RUSTSEC-2026-0195, both 7.5 high (quadratic parse time on duplicate attribute names; unbounded namespace-declaration allocation). It appeared only incrates/prtip-cli/Cargo.toml; no source file imports it. ProRT-IP writes its Nmap-compatible XML by hand.tui-inputremoved — likewise never imported, and the sole reason a secondratatui(0.28.1) was being built alongside 0.29.0.Upgrades that clear the remaining warnings
ratatui0.29 → 0.30 dropspaste(RUSTSEC-2024-0436) and moves tolru≥0.18.2, fixing RUSTSEC-2026-0002 and RUSTSEC-2026-0253. Needs Rust 1.88, which thetimefix already required. I checked the 0.30 breaking-change surface against this codebase before committing to it —Alignmentrename,block::Title/Position,highlight_symbol,WidgetRef, customBackendimpls,Styled, braille symbols — and ProRT-IP uses none of it. Zero code edits.indicatif0.17 → 0.18 replacesnumber_prefix(RUSTSEC-2025-0119) withunit-prefix.crates/prtip-scannerhad pinned its ownindicatif = "0.17"outside the workspace table and was building a second copy.console0.15 → 0.16,crossterm0.28 → 0.29 to deduplicate crates left doubled by the above.The
fuzzcrate was never auditedIt is excluded from the workspace and keeps its own
Cargo.lock, so neithercargo denynor a workspacecargo auditever read it. It carried threerustls-webpkiadvisories that do not appear in the root at all — RUSTSEC-2026-0098/0099 (name constraints wrongly accepted for URI names and wildcard-asserting certificates) and RUSTSEC-2026-0104 (reachable panic parsing CRLs) — plusbytes,crossbeam-epochandtime. Fixed, and CI now audits that lockfile.rsa: exempted, not fixedRUSTSEC-2023-0071 (Marvin Attack) has no fixed version.
rsais never compiled — it reaches the lockfile only viasqlx-mysql, an optional dependency ofsqlx, and this workspace enables only["runtime-tokio", "sqlite", "chrono"]. Cargo records optional dependencies whether or not their feature is on.Verified rather than argued:
deny.tomlends with no advisory exemptions at all. Thersaexemption lives in.cargo/audit.toml(mirrored infuzz/.cargo/) with the condition under which it must be withdrawn. It is deliberately not indeny.toml: cargo-deny resolves the feature-aware graph and never encountersrsa, so an entry there would only emitadvisory-not-detected— and would mask the advisory if a future change madersagenuinely reachable.A real bug found while removing
quick-xmlRemoving an XML library from a project that hand-writes XML meant auditing what it hand-writes.
crates/prtip-cli/src/output.rsescaped the banner but interpolatedresult.servicestraight intoname="...". Both are chosen by the host being scanned, so a hostile target could close the attribute and inject elements into a scan report — a forgery against whatever consumes it.export.rsescaped both correctly, which is precisely the inconsistency two hand-rolled copies invite.Both now share one implementation in
crates/prtip-cli/src/xml.rs, hardened beyond the five metacharacters: XML 1.0 forbids most control characters even as numeric references, so a banner containing a NUL — unremarkable from a binary protocol — previously produced a document conforming parsers reject. Those are now rendered as visible\xNN, matching how Nmap presents non-printable banner bytes, so evidence survives and the document stays valid.Nine tests, including a negative control that walks every byte value asserting no illegal code point survives, and an attribute-breakout attempt.
Keeping it fixed
.github/dependabot.ymladded. There was no Dependabot config at all, so the repository received only the security PRs GitHub opens unprompted; routine version updates were never proposed. That is how aquick-xmlfive minor versions behind and a duplicateratatuimajor went unnoticed. Updates are grouped — minor/patch batched, majors separate, security together — because ungrouped updates on a workspace this size produce enough PRs to be ignored, which is indistinguishable from having none. Covers cargo (root andfuzz), GitHub Actions, anddocker/.advisories. The licence check had never run:deny.tomlstill allowed the deprecatedGPL-3.0identifier while the workspace now declaresGPL-3.0-or-later, socargo deny check licenseswas failing and CI could not have caught it..gitignorefixed soaudit.tomlis tracked..cargo/was excluded wholesale, which silently dropped both exemption files from the first staging attempt and would have failed CI's fuzz audit. The pattern has to be.cargo/*plus a!.cargo/audit.tomlnegation — git does not descend into an excluded directory, so a negation under.cargo/is never consulted.deny.tomlignores forpasteandnumber_prefixremoved; both crates are gone from the tree.Documentation link checking, fixed properly
The link check failed three consecutive runs on three different external hosts — a 429 from
docs.ansible.com, then a connection failure tobgp.tools— while seven genuinely broken internal links sat undetected behind them. Fixing external symptoms one at a time was never going to converge, because they weren't defects in the repository.The seven broken links are in
to-dos/PHASE-5/SPRINT-5.5.{5,6}-TODO.md, pointing at../docs/...and../benchmarks/...fromto-dos/PHASE-5/— one directory short. All four targets exist; they now use../../. They were never reported because theto-dos/step ended with|| true, discarding every failure in that directory.Merges no longer depend on third-party uptime. Pull requests and pushes check internal links only (relative paths, heading anchors) — deterministic, always the author's to fix. The weekly schedule and manual dispatches check external links too, so link rot is still caught without blocking anyone. Verified across all 232 markdown files: zero broken internal links. PR runs also dropped from 4m38s to 1m20s.
Three further ways this check was lying:
mlc_config.jsonwas inert — the workflow generated its own config over the top, so editing the committed file did nothing. It's now the single source of truth, with only the PR-mode ignore layered on.if: always()and unconditionally printed "✅ All markdown links validated successfully", including on failed runs.Note on the superseded PR
The one open Dependabot PR (
bytes1.11.0 → 1.11.1) lives ondoublegate/ProRT-IP_NPSL, which is now private and archived, so it cannot be merged or closed there. Its content is included here.Verification
Test count 2,563 → 2,581: the +18 is the 9 new
xml.rstests, whichprtip-clicompiles into both its lib and its bin.🤖 Generated with Claude Code
https://claude.ai/code/session_01NBDCJvLbq7nuor7RtT57rD