From 889c649f01ec2e243ab4ce1be4318903123fca68 Mon Sep 17 00:00:00 2001 From: saagpatel <41898282+saagpatel@users.noreply.github.com> Date: Thu, 16 Jul 2026 20:19:56 -0700 Subject: [PATCH] fix(deps): patch Rust XML advisory paths --- .../scripts/dependency_watch_completion.py | 15 +++ .github/workflows/dependency-watch.yml | 4 +- ...rust-advisory-vector-dependency-refresh.md | 17 ++-- docs/security/DEPENDENCY_ADVISORY_BASELINE.md | 6 +- scripts/security/run-cargo-audit.sh | 13 ++- src-tauri/Cargo.lock | 99 ++++++++++++++----- src-tauri/Cargo.toml | 2 +- .../test_dependency_watch_completion.py | 22 ++++- .../test_rust_dependency_advisories.py | 62 ++++++++++++ 9 files changed, 194 insertions(+), 46 deletions(-) create mode 100644 tests/security/test_rust_dependency_advisories.py diff --git a/.github/scripts/dependency_watch_completion.py b/.github/scripts/dependency_watch_completion.py index 78e3d74..894489a 100644 --- a/.github/scripts/dependency_watch_completion.py +++ b/.github/scripts/dependency_watch_completion.py @@ -38,6 +38,18 @@ def build_payload(env: Mapping[str, str], observed_at: str) -> dict: state = "failed" succeeded = state == "succeeded" + if not issue_attempted: + write_result = "not_attempted" + readback_result = "not_required" + observed_result = {"kind": "state", "value": "no_issue_mutation"} + elif action: + write_result = "succeeded" + readback_result = "verified" if readback_verified else "failed" + observed_result = {"kind": "destination_id", "value": destination_id} + else: + write_result = "failed" + readback_result = "failed" + observed_result = {"kind": "state", "value": "issue_unverified"} return { "schema": "AutomationTerminalStateV1", "automation_id": AUTOMATION_ID, @@ -54,6 +66,9 @@ def build_payload(env: Mapping[str, str], observed_at: str) -> dict: "evidence": { "issue_action": action or ("unknown" if issue_attempted else "not_required"), "issue_step_outcome": issue_outcome or "unknown", + "write_result": write_result, + "readback_result": readback_result, + "observed_result": observed_result, }, }, "operator_action_required": not succeeded, diff --git a/.github/workflows/dependency-watch.yml b/.github/workflows/dependency-watch.yml index a6828f1..14ec5cf 100644 --- a/.github/workflows/dependency-watch.yml +++ b/.github/workflows/dependency-watch.yml @@ -58,9 +58,9 @@ jobs: working-directory: src-tauri run: | set +e - cargo audit --json > ../cargo-audit.json + bash ../scripts/security/run-cargo-audit.sh --json > ../cargo-audit.json status=$? - cargo audit > ../cargo-audit.txt || true + bash ../scripts/security/run-cargo-audit.sh > ../cargo-audit.txt || true echo "exit_code=$status" >> "$GITHUB_OUTPUT" exit 0 diff --git a/docs/adr/0014-rust-advisory-vector-dependency-refresh.md b/docs/adr/0014-rust-advisory-vector-dependency-refresh.md index 5638518..8d750f4 100644 --- a/docs/adr/0014-rust-advisory-vector-dependency-refresh.md +++ b/docs/adr/0014-rust-advisory-vector-dependency-refresh.md @@ -33,10 +33,13 @@ Patch related Rust advisory drift in the same lockfile refresh: - Tauri utility/plugin crates far enough to remove the stale `rand 0.7.3` build-time path -Temporarily waive `RUSTSEC-2026-0194` and `RUSTSEC-2026-0195` for existing -`quick-xml` paths constrained by `calamine` and Tauri/plist. Keep the waiver in -`scripts/security/run-cargo-audit.sh`, not in workflow configuration, so the -exception remains visible to maintainers. +Upgrade `calamine` to `0.36.0` and `plist` to `1.10.0` so operator-controlled +spreadsheet parsing and Tauri/plist runtime parsing use patched +`quick-xml 0.41.0`. Temporarily retain the `RUSTSEC-2026-0194` and +`RUSTSEC-2026-0195` waiver only for `wayland-scanner`'s trusted build-time +protocol XML generator. Keep the waiver in `scripts/security/run-cargo-audit.sh`, +not in workflow configuration, so the exception remains visible to maintainers. +Track the residual exception through the dependency-alert destination `#178`. ## Consequences @@ -45,9 +48,9 @@ Dependabot paths are removed from the lockfile. The vector store continues to use the same table schema and data flow, with only the LanceDB reader interface adapted. -The remaining `glib` and `quick-xml` advisories are upstream-constrained and -must stay on the dependency-advisory review list until their dependency chains -allow patched versions. +The remaining `glib` warning and build-only `quick-xml` advisory path are +upstream-constrained and must stay on the dependency-advisory review list until +their dependency chains allow patched versions. ## Alternatives Considered diff --git a/docs/security/DEPENDENCY_ADVISORY_BASELINE.md b/docs/security/DEPENDENCY_ADVISORY_BASELINE.md index f63fcac..0391fd8 100644 --- a/docs/security/DEPENDENCY_ADVISORY_BASELINE.md +++ b/docs/security/DEPENDENCY_ADVISORY_BASELINE.md @@ -1,6 +1,6 @@ # Dependency Advisory Baseline -Date: 2026-07-04 +Date: 2026-07-13 Default branch snapshot: pending merge of `codex/fix/assistsupport-lru-advisory` ## Purpose @@ -38,7 +38,9 @@ It records known advisory state, active remediation PRs, and temporary exception - `lancedb` upgraded to `0.30.0`, removing vulnerable `lru`. - `rand` resolved to patched `0.8.6`. - `quinn-proto`, `anyhow`, and `memmap2` patch updates applied for newly published RustSec advisories. - - `quick-xml` advisories `RUSTSEC-2026-0194` and `RUSTSEC-2026-0195` are temporarily waived for constrained `calamine` and Tauri/plist transitive paths. + - `calamine` is upgraded to `0.36.0` and `plist` to `1.10.0`; both runtime XML paths now use patched `quick-xml 0.41.0`. + - `quick-xml` advisories `RUSTSEC-2026-0194` and `RUSTSEC-2026-0195` remain temporarily waived only for `wayland-scanner`'s trusted build-time protocol XML generator. + - Waiver owner: Platform Engineering; tracking issue: `#178`; review deadline: 2026-08-10 or the first `wayland-scanner` release using `quick-xml >=0.41.0`. ## Exit Criteria to Update Baseline diff --git a/scripts/security/run-cargo-audit.sh b/scripts/security/run-cargo-audit.sh index 8436bc1..2ae21f8 100644 --- a/scripts/security/run-cargo-audit.sh +++ b/scripts/security/run-cargo-audit.sh @@ -12,7 +12,7 @@ cd "$repo_root/src-tauri" # Last remediation update: hickory-resolver upgraded to 0.26.1 to clear # RUSTSEC-2026-0119, and locked lz4_flex versions kept outside the # RUSTSEC-2026-0041 vulnerable ranges. -# Umbrella tracking issue: https://github.com/saagar210/AssistSupport/issues/11 +# Current dependency-alert destination: https://github.com/saagpatel/AssistSupport/issues/178 # # GTK3/Tauri Linux runtime chain (issue #12): # - RUSTSEC-2024-0411 gdkwayland-sys @@ -44,11 +44,13 @@ cd "$repo_root/src-tauri" # - RUSTSEC-2026-0105 core2 # - RUSTSEC-2026-0097 rand # -# XML parser transitives (issue #17): +# XML parser transitives (dependency alert #178): # - RUSTSEC-2026-0194 quick-xml duplicate attribute scan DoS # - RUSTSEC-2026-0195 quick-xml namespace declaration allocation DoS -# Current paths are constrained by calamine and Tauri/plist. Revisit when -# calamine allows quick-xml >=0.41.0 and Tauri/plist moves past quick-xml 0.38. +# Runtime paths through calamine and Tauri/plist use quick-xml >=0.41.0. +# The sole older copy is constrained to wayland-scanner's trusted build-time +# protocol XML generator; it never parses operator-controlled documents. +# Review deadline: 2026-08-10, or when wayland-scanner adopts quick-xml >=0.41. # Deny unsound/unmaintained advisories but do not hard-fail on yanked crate warnings, # which can fluctuate transitively outside this repo's direct control. cargo audit --deny unsound --deny unmaintained \ @@ -73,4 +75,5 @@ cargo audit --deny unsound --deny unmaintained \ --ignore RUSTSEC-2026-0097 \ --ignore RUSTSEC-2026-0105 \ --ignore RUSTSEC-2026-0194 \ - --ignore RUSTSEC-2026-0195 + --ignore RUSTSEC-2026-0195 \ + "$@" diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index f11a2ce..a6fd9ce 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -605,6 +605,16 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atoi_simd" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cdb3708a128e559a30fb830e8a77a5022ee6902806925c216658652b452a44" +dependencies = [ + "debug_unsafe", + "rustversion", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -722,7 +732,7 @@ dependencies = [ "bitflags 2.11.0", "cexpr", "clang-sys", - "itertools 0.10.5", + "itertools 0.13.0", "log", "prettyplease", "proc-macro2", @@ -969,17 +979,19 @@ dependencies = [ [[package]] name = "calamine" -version = "0.26.1" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "138646b9af2c5d7f1804ea4bf93afc597737d2bd4f7341d67c48b03316976eb1" +checksum = "6975084f43060e56343ffba7f9731fa52a7dcf2e1cd8e2459fd4c6bf4a1bff59" dependencies = [ + "atoi_simd", "byteorder", "codepage", "encoding_rs", + "fast-float2", "log", - "quick-xml 0.31.0", + "quick-xml 0.41.0", "serde", - "zip 2.4.2", + "zip 8.6.0", ] [[package]] @@ -2329,6 +2341,12 @@ dependencies = [ "sqlparser", ] +[[package]] +name = "debug_unsafe" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eed2c4702fa172d1ce21078faa7c5203e69f5394d48cc436d25928394a867a2" + [[package]] name = "deepsize" version = "0.2.0" @@ -2457,7 +2475,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2489,7 +2507,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" dependencies = [ - "libloading 0.7.4", + "libloading 0.8.9", ] [[package]] @@ -2717,7 +2735,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2873,6 +2891,7 @@ checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -3147,7 +3166,7 @@ dependencies = [ "libc", "log", "rustversion", - "windows-link 0.1.3", + "windows-link 0.2.1", "windows-result", ] @@ -4090,7 +4109,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -5442,7 +5461,7 @@ dependencies = [ "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5593,7 +5612,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6263,13 +6282,13 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plist" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +checksum = "7da1d65da6dd5d1e44199ac0f58712d241c0f439f80adea8924d832384087f85" dependencies = [ "base64 0.22.1", "indexmap 2.13.0", - "quick-xml 0.38.4", + "quick-xml 0.41.0", "serde", "time", ] @@ -6509,7 +6528,7 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "itertools 0.14.0", "log", "multimap", @@ -6609,20 +6628,20 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" [[package]] name = "quick-xml" -version = "0.31.0" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ - "encoding_rs", "memchr", ] [[package]] name = "quick-xml" -version = "0.38.4" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" dependencies = [ + "encoding_rs", "memchr", ] @@ -7228,7 +7247,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.11.0", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -7763,7 +7782,7 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1c97747dbf44bb1ca44a561ece23508e99cb592e862f22222dcf42f51d1e451" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.117", @@ -7775,7 +7794,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f103c50866b8743da9429b8a581d81a27c2d3a9c4ac7df8f8571c1dd7896eda" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.117", @@ -8401,7 +8420,7 @@ dependencies = [ "getrandom 0.4.1", "once_cell", "rustix 1.1.3", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -8909,7 +8928,7 @@ dependencies = [ "png 0.18.1", "serde", "thiserror 2.0.18", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -8927,6 +8946,12 @@ dependencies = [ "rand 0.9.3", ] +[[package]] +name = "typed-path" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" + [[package]] name = "typeid" version = "1.0.3" @@ -9572,7 +9597,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] @@ -10444,6 +10469,26 @@ dependencies = [ "zstd", ] +[[package]] +name = "zip" +version = "8.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b" +dependencies = [ + "crc32fast", + "flate2", + "indexmap 2.13.0", + "memchr", + "typed-path", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5" + [[package]] name = "zmij" version = "1.0.21" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 3063d9d..69de65a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -57,7 +57,7 @@ pdfium-render = "0.8" tesseract = { version = "0.13", optional = true } encoding_rs = "0.8" docx-rs = "0.4" -calamine = "0.26" +calamine = "0.36" # File Watching notify = "8" diff --git a/tests/automation/test_dependency_watch_completion.py b/tests/automation/test_dependency_watch_completion.py index dbb7eb2..a790d1d 100644 --- a/tests/automation/test_dependency_watch_completion.py +++ b/tests/automation/test_dependency_watch_completion.py @@ -44,7 +44,12 @@ def assert_contract(self, payload): self.assertEqual(payload["automation_id"], "github:AssistSupport/dependency-watch") self.assertEqual((payload["state"] == "partial"), payload["partial"]) self.assertEqual((payload["state"] == "skipped"), payload["skipped"]) - self.assertTrue(payload["destination_readback"]["evidence"]) + evidence = payload["destination_readback"]["evidence"] + self.assertEqual( + set(("write_result", "readback_result", "observed_result")) - set(evidence), + set(), + ) + self.assertEqual(set(evidence["observed_result"]), {"kind", "value"}) if payload["destination_readback"]["required"]: self.assertTrue(payload["destination_readback"]["destination_id"]) if payload["can_auto_archive"]: @@ -66,7 +71,13 @@ def test_clean_run_is_success_not_skipped(self): "verified": False, "destination_id": None, "observed_at": None, - "evidence": {"issue_action": "not_required", "issue_step_outcome": "skipped"}, + "evidence": { + "issue_action": "not_required", + "issue_step_outcome": "skipped", + "write_result": "not_attempted", + "readback_result": "not_required", + "observed_result": {"kind": "state", "value": "no_issue_mutation"}, + }, }, ) @@ -82,6 +93,10 @@ def test_issue_mutation_with_readback_is_success(self): self.assertEqual(payload["mutation_count"], 1) self.assertEqual(payload["destination_readback"]["destination_id"], "issue:42") self.assertEqual(payload["destination_readback"]["observed_at"], self.observed_at) + evidence = payload["destination_readback"]["evidence"] + self.assertEqual(evidence["write_result"], "succeeded") + self.assertEqual(evidence["readback_result"], "verified") + self.assertEqual(evidence["observed_result"], {"kind": "destination_id", "value": "issue:42"}) def test_actionable_failure_with_verified_issue_is_failed_not_partial(self): payload = self.payload( @@ -106,6 +121,9 @@ def test_unverified_attempt_is_partial(self): self.assertTrue(payload["partial"]) self.assertTrue(payload["destination_readback"]["required"]) self.assertFalse(payload["destination_readback"]["verified"]) + evidence = payload["destination_readback"]["evidence"] + self.assertEqual(evidence["write_result"], "failed") + self.assertEqual(evidence["readback_result"], "failed") self.assertEqual( payload["destination_readback"]["destination_id"], "repo:saagpatel/AssistSupport/issues#Dependency Watch Alerts", diff --git a/tests/security/test_rust_dependency_advisories.py b/tests/security/test_rust_dependency_advisories.py new file mode 100644 index 0000000..b2f06cd --- /dev/null +++ b/tests/security/test_rust_dependency_advisories.py @@ -0,0 +1,62 @@ +import tomllib +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +CARGO_TOML = ROOT / "src-tauri" / "Cargo.toml" +CARGO_LOCK = ROOT / "src-tauri" / "Cargo.lock" +WORKFLOW = ROOT / ".github" / "workflows" / "dependency-watch.yml" +AUDIT_WRAPPER = ROOT / "scripts" / "security" / "run-cargo-audit.sh" + + +def version_tuple(value: str) -> tuple[int, ...]: + parts = [int(part) for part in value.split(".")[:3]] + return tuple(parts + [0] * (3 - len(parts))) + + +class RustDependencyAdvisoryTests(unittest.TestCase): + def test_vulnerable_quick_xml_is_confined_to_trusted_build_time_generator(self): + lock = tomllib.loads(CARGO_LOCK.read_text(encoding="utf-8")) + packages = lock["package"] + vulnerable = { + package["version"] + for package in packages + if package["name"] == "quick-xml" and version_tuple(package["version"]) < (0, 41, 0) + } + vulnerable_refs = {f"quick-xml {version}" for version in vulnerable} + parents = { + package["name"] + for package in packages + if vulnerable_refs.intersection(package.get("dependencies", [])) + } + self.assertEqual(parents, {"wayland-scanner"}) + + for runtime_parent in ("calamine", "plist"): + package = next(package for package in packages if package["name"] == runtime_parent) + quick_xml_refs = [dep for dep in package.get("dependencies", []) if dep.startswith("quick-xml")] + self.assertTrue(quick_xml_refs, f"{runtime_parent} must retain its XML parser dependency") + self.assertTrue( + all(version_tuple(dep.split()[-1]) >= (0, 41, 0) for dep in quick_xml_refs), + f"{runtime_parent} runtime XML path must use quick-xml >=0.41.0: {quick_xml_refs}", + ) + + def test_calamine_direct_requirement_stays_on_patched_dependency_line(self): + manifest = tomllib.loads(CARGO_TOML.read_text(encoding="utf-8")) + requirement = manifest["dependencies"]["calamine"] + self.assertGreaterEqual(version_tuple(requirement), (0, 36, 0)) + + def test_workflow_uses_canonical_issue_backed_audit_policy(self): + workflow = WORKFLOW.read_text(encoding="utf-8") + wrapper = AUDIT_WRAPPER.read_text(encoding="utf-8") + self.assertGreaterEqual(workflow.count("../scripts/security/run-cargo-audit.sh"), 2) + self.assertIn('"$@"', wrapper) + self.assertIn("https://github.com/saagpatel/AssistSupport/issues/178", wrapper) + for advisory in ("RUSTSEC-2026-0194", "RUSTSEC-2026-0195"): + self.assertEqual(wrapper.count(f"--ignore {advisory}"), 1) + self.assertIn("wayland-scanner", wrapper) + self.assertNotIn("constrained by calamine and Tauri/plist", wrapper) + + +if __name__ == "__main__": + unittest.main()