Skip to content

Commit accc161

Browse files
fix(security): clear cargo-audit vulnerabilities + unmaintained-fxhash denial (resolves #113) (#126)
## What Resolves #113 — `cargo audit` is red on `main` (the scheduled workflow auto-opened the issue on 2026-08-31). Reproduced locally, fixed at source, and every affected gate verified green with CI's exact invocations. ## Audit findings → fixes | Finding | Fix | |---|---| | `wasmtime` 36.0.11 — **RUSTSEC-2026-0269** (fs sandbox escape via trailing slashes, **high**) & RUSTSEC-2026-0222 | bump to **36.0.15** (≥ 36.0.14 patched, 36.x line) | | `crossbeam-epoch` 0.9.18 — RUSTSEC-2026-0204 | bump to **0.9.21** (≥ 0.9.20 patched) | | `anyhow` 1.0.102 — RUSTSEC-2026-0190 (unsound `downcast_mut`) | bump to **1.0.104** (≥ 1.0.103 patched) | | `fxhash` — RUSTSEC-2025-0057 (unmaintained, **`patched = []`** — no bump can fix) | **removed at source**: it only entered via wasmtime's default `profiling` feature → `fxprof-processed-profile`. The plugin sandbox uses core runtime + fuel metering only, so the manifest sets `default-features = false` with `cranelift`/`runtime`/`std`/`wat`. Graph shrinks 335 → 301 crates. | Both `cargo audit --deny warnings` and `cargo audit --deny unmaintained` exit 0 against the updated, committed `Cargo.lock`. ## Pre-existing gate breakage also repaired (main was already red) Reproducing CI locally showed these failures independent of the audit: - **`cargo check --all-targets` failed** — `benches/rpa_benchmarks.rs` was added without the root `[dev-dependencies]` it imports (`criterion`, `rpa-core`, `rpa-plugin`, `serde_json`); plus a stale `record_error("…")` arg vs `WorkflowState::record_error()`. - **`cargo fmt --all -- --check` failed on 9 files** → canonicalised. - **`rpa-scheduler` tests didn't compile** — chrono removed `LocalResult::expect`; migrated to `.single().expect(...)` (5 sites). ## Verification (toolchain rustc/cargo 1.98.1 stable, cargo-audit 0.22.2, fresh advisory DB) ``` cargo audit --deny warnings PASS cargo audit --deny unmaintained PASS cargo check --locked --all-targets PASS cargo fmt --all -- --check PASS cargo clippy --locked --all-targets -- -D warnings PASS cargo test --locked --all-targets PASS cargo test --locked --workspace --all-targets PASS (128 tests, 0 failures) ``` Resolves #113.
1 parent 8faaa23 commit accc161

11 files changed

Lines changed: 293 additions & 548 deletions

File tree

Cargo.lock

Lines changed: 65 additions & 435 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ description = "RPA Elysium - Comprehensive test suite for filesystem automation"
2626
name = "rpa_benchmarks"
2727
harness = false
2828

29+
# Dev-dependencies for the root bench target (benches/rpa_benchmarks.rs).
30+
# Without these the CI `cargo check --all-targets` gate fails to compile
31+
# the bench (unresolved criterion / rpa_core / rpa_plugin / serde_json).
32+
[dev-dependencies]
33+
criterion = { workspace = true }
34+
rpa-core = { path = "crates/rpa-core" }
35+
rpa-plugin = { path = "crates/rpa-plugin" }
36+
serde_json = { workspace = true }
37+
2938
[workspace.package]
3039
version = "0.1.0"
3140
edition = "2021"
@@ -66,7 +75,17 @@ zip = "0.6"
6675
chrono = { version = "0.4", features = ["serde"] }
6776

6877
# Plugin system (WASM sandbox)
69-
wasmtime = "36.0.8"
78+
# `profiling` (a wasmtime default feature) is deliberately disabled: it is the
79+
# only path pulling `fxprof-processed-profile` -> `fxhash`, which is
80+
# unmaintained (RUSTSEC-2025-0057) and fails `cargo audit --deny unmaintained`.
81+
# The plugin sandbox uses core-Wasm runtime + fuel metering only, so the
82+
# guest-profiler machinery is dead weight here.
83+
wasmtime = { version = "36.0.8", default-features = false, features = [
84+
"cranelift",
85+
"runtime",
86+
"std",
87+
"wat",
88+
] }
7089
wit-bindgen = "0.36"
7190
uuid = { version = "1.6", features = ["v4", "serde"] }
7291

benches/rpa_benchmarks.rs

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ fn bench_workflow_state(c: &mut Criterion) {
8181
let mut state = WorkflowState::new("test");
8282
state.start();
8383
b.iter(|| {
84-
state.record_error(black_box("error"));
84+
state.record_error();
8585
});
8686
});
8787

@@ -93,21 +93,15 @@ fn bench_permission_checking(c: &mut Criterion) {
9393
let mut group = c.benchmark_group("permissions");
9494

9595
group.bench_function("permission_check_read_path", |b| {
96-
let set = PermissionSet::empty()
97-
.with(Permission::read_path("/tmp/data"));
96+
let set = PermissionSet::empty().with(Permission::read_path("/tmp/data"));
9897

99-
b.iter(|| {
100-
set.check(black_box(&Permission::read_path("/tmp/data/file.txt")))
101-
});
98+
b.iter(|| set.check(black_box(&Permission::read_path("/tmp/data/file.txt"))));
10299
});
103100

104101
group.bench_function("permission_check_denied", |b| {
105-
let set = PermissionSet::empty()
106-
.with(Permission::read_path("/tmp/data"));
102+
let set = PermissionSet::empty().with(Permission::read_path("/tmp/data"));
107103

108-
b.iter(|| {
109-
set.check(black_box(&Permission::write_path("/tmp/data")))
110-
});
104+
b.iter(|| set.check(black_box(&Permission::write_path("/tmp/data"))));
111105
});
112106

113107
group.bench_function("permission_set_creation_small", |b| {
@@ -137,9 +131,7 @@ fn bench_permission_checking(c: &mut Criterion) {
137131
Permission::Random,
138132
]);
139133

140-
b.iter(|| {
141-
set.check(black_box(&Permission::read_path("/tmp/file.txt")))
142-
});
134+
b.iter(|| set.check(black_box(&Permission::read_path("/tmp/file.txt"))));
143135
});
144136

145137
group.finish();

crates/rpa-core/tests/concurrency_tests.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,7 @@ fn test_concurrent_workflow_state_error_recording() {
143143

144144
// Verify final count
145145
let state = state.lock().unwrap();
146-
assert_eq!(
147-
state.error_count, 1000,
148-
"should have recorded 1000 errors"
149-
);
146+
assert_eq!(state.error_count, 1000, "should have recorded 1000 errors");
150147
}
151148

152149
/// Test concurrent mixed operations on WorkflowState
@@ -210,7 +207,10 @@ fn test_high_concurrency_event_creation() {
210207
);
211208

212209
// Verify event invariants immediately
213-
assert!(event.id.starts_with("evt_"), "event ID should start with evt_");
210+
assert!(
211+
event.id.starts_with("evt_"),
212+
"event ID should start with evt_"
213+
);
214214
assert_eq!(event.source, format!("/test/{}/{}", thread_id, i));
215215

216216
events.lock().unwrap().push(event);

crates/rpa-core/tests/property_tests.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,7 @@ fn arb_event_kind() -> impl Strategy<Value = EventKind> {
2828
.prop_map(|s| PathBuf::from(format!("/tmp/{}", s)))
2929
.prop_map(|p| EventKind::FileDeleted { path: p }),
3030
// FileRenamed events
31-
(
32-
"[a-z]+\\.rs",
33-
"[a-z]+\\.rs",
34-
)
31+
("[a-z]+\\.rs", "[a-z]+\\.rs",)
3532
.prop_map(|(f1, f2)| {
3633
(
3734
PathBuf::from(format!("/tmp/{}", f1)),
@@ -40,8 +37,7 @@ fn arb_event_kind() -> impl Strategy<Value = EventKind> {
4037
})
4138
.prop_map(|(from, to)| EventKind::FileRenamed { from, to }),
4239
// Scheduled events - just simple time strings
43-
Just("10:30".to_string())
44-
.prop_map(|s| EventKind::Scheduled { schedule: s }),
40+
Just("10:30".to_string()).prop_map(|s| EventKind::Scheduled { schedule: s }),
4541
]
4642
}
4743

crates/rpa-fs-workflow/tests/e2e_workflow_tests.rs

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,18 @@ fn test_e2e_file_creation_and_rename_workflow() {
2424
fs::rename(&test_file, &renamed_file).expect("rename file");
2525

2626
// Verify old file is gone
27-
assert!(!test_file.exists(), "old file should not exist after rename");
27+
assert!(
28+
!test_file.exists(),
29+
"old file should not exist after rename"
30+
);
2831
assert!(renamed_file.exists(), "renamed file should exist");
2932

3033
// Verify content is preserved
3134
let content = fs::read_to_string(&renamed_file).expect("read renamed file");
32-
assert_eq!(content, "test content", "content should be preserved after rename");
35+
assert_eq!(
36+
content, "test content",
37+
"content should be preserved after rename"
38+
);
3339
}
3440

3541
/// Test complete file copy workflow
@@ -47,7 +53,10 @@ fn test_e2e_file_copy_workflow() {
4753

4854
// Verify both files exist
4955
assert!(source_file.exists(), "source file should still exist");
50-
assert!(dest_file.exists(), "destination file should exist after copy");
56+
assert!(
57+
dest_file.exists(),
58+
"destination file should exist after copy"
59+
);
5160

5261
// Verify content matches
5362
let source_content = fs::read_to_string(&source_file).expect("read source");
@@ -79,7 +88,10 @@ fn test_e2e_file_move_workflow() {
7988
// Verify file is in archive with content intact
8089
assert!(archived_file.exists(), "archived file should exist");
8190
let archived_content = fs::read_to_string(&archived_file).expect("read archived file");
82-
assert_eq!(archived_content, content, "content should be preserved in archive");
91+
assert_eq!(
92+
archived_content, content,
93+
"content should be preserved in archive"
94+
);
8395
}
8496

8597
/// Test complete file deletion workflow
@@ -149,7 +161,10 @@ fn test_e2e_workflow_with_file_operations_on_large_file() {
149161

150162
// Verify copy integrity
151163
let copy_content = fs::read(&copy).expect("read copy");
152-
assert_eq!(copy_content, large_content, "large file copy should be identical");
164+
assert_eq!(
165+
copy_content, large_content,
166+
"large file copy should be identical"
167+
);
153168

154169
// Clean up original
155170
fs::remove_file(&large_file).expect("delete original");
@@ -301,7 +316,11 @@ fn test_e2e_workflow_with_empty_files() {
301316
assert!(!empty_file.exists());
302317
assert!(renamed.exists());
303318
let renamed_metadata = fs::metadata(&renamed).expect("get renamed metadata");
304-
assert_eq!(renamed_metadata.len(), 0, "renamed file should still be empty");
319+
assert_eq!(
320+
renamed_metadata.len(),
321+
0,
322+
"renamed file should still be empty"
323+
);
305324
}
306325

307326
/// Test workflow cleanup

crates/rpa-fs-workflow/tests/error_handling_tests.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,10 @@ fn test_concurrent_file_access_handling() {
270270

271271
// File should still be readable at the end
272272
let result = fs::read_to_string(&file);
273-
assert!(result.is_ok(), "should be able to read file after concurrent access");
273+
assert!(
274+
result.is_ok(),
275+
"should be able to read file after concurrent access"
276+
);
274277
}
275278

276279
/// Test handling of metadata access on missing file
@@ -280,7 +283,10 @@ fn test_metadata_missing_file_handling() {
280283
let missing = temp.path().join("missing.txt");
281284

282285
let result = fs::metadata(&missing);
283-
assert!(result.is_err(), "should fail to get metadata of missing file");
286+
assert!(
287+
result.is_err(),
288+
"should fail to get metadata of missing file"
289+
);
284290
}
285291

286292
/// Test handling of operations on empty directory

0 commit comments

Comments
 (0)