{"vulnerability": "GHSA-4v52-7q2x-v4xj", "sightings": [{"uuid": "ca338bcf-2297-4fa0-8c38-42aff317f134", "vulnerability_lookup_origin": "1a89b78e-f703-45f3-bb86-59eb712668bd", "author": "9f56dd64-161d-43a6-b9c3-555944290a09", "vulnerability": "GHSA-4v52-7q2x-v4xj", "type": "seen", "source": "https://gist.github.com/Reilley64/5281c3e3797df1ef31d3e4100483a1a1", "content": "# Decision outcome\n\nAfter reviewing this comparison, the project owner selected Rootcause 0.13 as the sole error-handling dependency. The recommendation below records the researcher's independent evaluation, not the final decision. The accompanying Rust coding standard contains the approved policy.\n\n# Rust error libraries for issue #17\n\nResearch date: 2026-09-14. Sources are versioned upstream documentation, source, changelogs, releases, crates.io metadata, and the RustSec advisory maintained for the affected crate.\n\n## Recommendation\n\nUse **`thiserror 2` plus `error-stack 0.8`** for the core error model.\n\n- Define concrete domain and infrastructure errors with `thiserror`. It derives the standard `Error`, `Display`, `source`, and `From` implementations for structs as well as enums, without putting `thiserror` itself in the public API ([2.0.20 docs](https://docs.rs/thiserror/2.0.20/thiserror/)). Keep small enums where the domain really has alternatives. Do not build a repository-wide catch-all enum.\n- Give each application use case one outer error struct, for example `InstallModsError`, and return `Result&gt;`. `Report` keeps the current context statically typed. `change_context` changes that type while retaining the older frame tree, and stable `contains::()` and `downcast_ref::()` find prior contexts or attachments ([`Report`](https://docs.rs/error-stack/0.8.0/error_stack/struct.Report.html), [`ResultExt`](https://docs.rs/error-stack/0.8.0/error_stack/trait.ResultExt.html)).\n- Convert a failure into a report at the earliest owned boundary, then use `change_context` at later ownership boundaries. This is important. When `Report::new` receives an error that already has a standard `source()` chain, version 0.8 copies those source messages into internal `SourceContext(String)` frames. Those copied frames no longer carry the source's concrete type, although the original outer error still owns its normal source chain ([source](https://github.com/hashintel/hash/blob/error-stack%400.8.0/libs/error-stack/src/report.rs), [`SourceContext`](https://github.com/hashintel/hash/blob/error-stack%400.8.0/libs/error-stack/src/context.rs)). Building the report early makes each owned error a typed context frame and keeps direct report downcasts useful.\n- Use `attach_opaque` for diagnostic data by default. It retains a `Send + Sync + 'static` typed value without rendering it. Use printable `attach` only for values reviewed as safe for operator output ([attachment traits](https://docs.rs/error-stack/0.8.0/error_stack/trait.OpaqueAttachment.html)).\n- Use stable multi-error APIs only. `Report::expand`, `Report&lt;[C]&gt;::push`, and `ReportSink` collect peer failures under one homogeneous outer context type ([multi-error docs](https://docs.rs/error-stack/0.8.0/error_stack/struct.Report.html#method.expand), [`ReportSink`](https://docs.rs/error-stack/0.8.0/error_stack/struct.ReportSink.html)).\n- Do not enable error-stack's `unstable` feature and do not make repository code depend on nightly-only `request_ref`, `request_value`, or `Try` integration. The pinned nightly makes some of these APIs available through `cfg(nightly)`, but the stable `frames`, `contains`, and `downcast_ref` APIs cover the decided needs. The crate says its `unstable` feature is outside SemVer guarantees ([feature table](https://docs.rs/error-stack/0.8.0/error_stack/#feature-flags)). Avoid thiserror's nightly-only automatic `Backtrace` provider support too ([thiserror details](https://github.com/dtolnay/thiserror/blob/2.0.20/README.md#details)).\n\nA separate presentation layer is warranted. Do not send `format!(\"{report:?}\")` through MCP. CLI and MCP need different contracts:\n\n- The CLI adapter may render an allowlisted cause tree and safe attachments. Add `miette` only in the CLI crate if source spans, labels, help, or its graphical and narratable handlers are useful ([handler options](https://docs.rs/miette/7.6.0/miette/struct.MietteHandlerOpts.html), [`Diagnostic`](https://docs.rs/miette/7.6.0/miette/trait.Diagnostic.html)). It is not needed merely to print an error-stack report.\n- The MCP adapter should inspect typed contexts and opaque attachments, then produce a stable DTO such as `{ code, message, retryable, details }`. This must be an allowlist. The MCP specification distinguishes protocol errors from tool-execution errors and requires servers to sanitize tool outputs ([error handling](https://modelcontextprotocol.io/specification/2026-07-28/server/tools#error-handling), [security](https://modelcontextprotocol.io/specification/2026-07-28/server/tools#security-considerations)). Miette's JSON handler is machine-readable, but it serializes diagnostic messages, help, labels, source snippets, causes, and related diagnostics, so it is not automatically a safe MCP contract ([JSON handler source](https://github.com/zkat/miette/blob/v7.6.0/src/handlers/json.rs)).\n\n## Current release baseline\n\n| Crate | Current release | Published | Declared toolchain | License | Maintenance signal |\n|---|---:|---:|---:|---|---|\n| [`error-stack`](https://crates.io/api/v1/crates/error-stack/0.8.0) | 0.8.0 | 2026-07-03 | Rust 1.83 | MIT OR Apache-2.0 | HASH-maintained; four releases from 0.6 through 0.8 since August 2025 ([changelog](https://github.com/hashintel/hash/blob/error-stack%400.8.0/libs/error-stack/CHANGELOG.md)) |\n| [`thiserror`](https://crates.io/api/v1/crates/thiserror/2.0.20) | 2.0.20 | 2026-08-08 | Rust 1.71 | MIT OR Apache-2.0 | Current release and active upstream ([release](https://github.com/dtolnay/thiserror/releases/tag/2.0.20)) |\n| [`snafu`](https://crates.io/api/v1/crates/snafu/0.9.2) | 0.9.2 | 2026-07-21 | Rust 1.65 package MSRV; default `rust_1_81` feature assumes 1.81 | MIT OR Apache-2.0 | 0.9 series released in 2026 ([changelog](https://github.com/shepmaster/snafu/blob/0.9.2/CHANGELOG.md), [compatibility](https://github.com/shepmaster/snafu/blob/0.9.2/src/guide/compatibility.md)) |\n| [`miette`](https://crates.io/api/v1/crates/miette/7.6.0) | 7.6.0 | 2025-04-27 | Rust 1.70 | Apache-2.0 | Latest release is older, but upstream commits continued in 2026 ([commits](https://github.com/zkat/miette/commits/main/), [changelog](https://github.com/zkat/miette/blob/v7.6.0/CHANGELOG.md)) |\n| [`anyhow`](https://crates.io/api/v1/crates/anyhow/1.0.104) | 1.0.104 | 2026-07-18 | Rust 1.68 | MIT OR Apache-2.0 | Current release and active upstream ([repository](https://github.com/dtolnay/anyhow)) |\n| [`eyre`](https://crates.io/api/v1/crates/eyre/0.6.14) | 0.6.14 | 2026-08-11 | Rust 1.65 | MIT OR Apache-2.0 | Current release resumed active maintenance in 2026 ([release](https://github.com/eyre-rs/eyre/releases/tag/v0.6.14)) |\n| [`rootcause`](https://crates.io/api/v1/crates/rootcause/0.13.0) | 0.13.0 | 2026-06-14 | Rust 1.89 | MIT/Apache-2.0 | Active, but young and explicitly pre-1.0 ([README and stability policy](https://github.com/rootcause-rs/rootcause/blob/v0.13.0/README.md#stability-and-roadmap)) |\n\nThe pinned nightly satisfies every listed compiler floor. Compiler availability is not a reason to depend on unstable APIs.\n\n## Fit comparison\n\n### `error-stack`\n\nThis is the best match for the report requirements. It has a typed current context, ownership-boundary context changes, printable and opaque typed attachments, stable frame inspection, and a native tree for several errors. Contexts and attachments require `Send + Sync + 'static`, which makes the normal report suitable for spawned async work ([frame storage](https://github.com/hashintel/hash/blob/error-stack%400.8.0/libs/error-stack/src/frame/frame_impl.rs)). `Report&lt;[C]&gt;` keeps multi-error roots homogeneous, and `current_contexts()` does not promise a stable order ([docs](https://docs.rs/error-stack/0.8.0/error_stack/struct.Report.html#method.current_contexts)).\n\nIts main interoperability limit is that `Report` does not itself implement `Error`. `as_error` and `into_error` return wrappers, but that wrapper does not expose the internal frame tree through standard `Error::source()` on stable. Consumers that need the full report must use error-stack's frame APIs or formatting ([conversion docs](https://docs.rs/error-stack/0.8.0/error_stack/struct.Report.html#method.into_error), [wrapper source](https://github.com/hashintel/hash/blob/error-stack%400.8.0/libs/error-stack/src/error.rs)).\n\nRedaction is a policy, not a guarantee. Opaque attachments are skipped by default formatting and current serde output, but remain in memory and a globally installed type hook can render them. Context `Display` and printable attachments always need review ([format hooks](https://docs.rs/error-stack/0.8.0/error_stack/fmt/index.html), [serde source](https://github.com/hashintel/hash/blob/error-stack%400.8.0/libs/error-stack/src/serde.rs)).\n\n### `thiserror`\n\nThis is the best definition tool for the typed context types. It is small, stable, and supports a single outer struct. Standard `source()` chains work, but each struct or enum variant can designate only one source. Context fields need explicit construction when a `#[from]` variant would need more than its source. It has no attachment history, aggregate error type, renderer, or redaction marker ([official details](https://github.com/dtolnay/thiserror/blob/2.0.20/README.md#details), [duplicate-source compile test](https://github.com/dtolnay/thiserror/blob/2.0.20/tests/ui/duplicate-struct-source.stderr)). `Send` and `Sync` depend on the fields selected for each error.\n\n### `SNAFU`\n\nSNAFU is the strongest single-crate typed alternative. Generated context selectors make `.context(...)`, lazy context, implicit locations, futures and streams, and backtraces pleasant to use. It supports opaque public error structs as well as enums ([guide](https://docs.rs/snafu/0.9.2/snafu/guide/), [opaque errors](https://github.com/shepmaster/snafu/blob/0.9.2/src/guide/opaque.md)). `ErrorCompat::iter_chain` improves linear source traversal.\n\nIt still lacks a stable arbitrary attachment stack and native simultaneous-error aggregate. Extra typed values through `#[snafu(provide)]` require the opt-in `unstable-provider-api`; upstream recommends that feature only for applications ([provider docs](https://github.com/shepmaster/snafu/blob/0.9.2/src/Snafu.md#providing-data-beyond-the-error-trait), [feature warning](https://github.com/shepmaster/snafu/blob/0.9.2/src/guide/feature_flags.md#unstable-provider-api)). Custom error auto-traits follow their fields. `Whatever` is `Send + Sync`; `WhateverLocal` is the non-thread-safe alternative ([source](https://github.com/shepmaster/snafu/blob/0.9.2/src/whatever.rs#L39-L91)). No built-in redaction stops a context or source `Display` from leaking data.\n\n### `miette`\n\nMiette is a diagnostic protocol and renderer first. Its own guidance says libraries should return concrete errors rather than `miette::Report`; concrete types can derive `Diagnostic` while retaining normal typed errors ([library guidance](https://docs.rs/miette/7.6.0/miette/#-in-libraries)). It supports codes, help, severity, URLs, source code, labels, diagnostic causes, and multiple `related()` diagnostics. Its graphical, narratable, debug, and JSON handlers are useful presentation choices ([protocol](https://docs.rs/miette/7.6.0/miette/trait.Diagnostic.html), [related errors](https://github.com/zkat/miette/blob/v7.6.0/README.md#-multiple-related-errors)).\n\nIts `Report` is an application-style erased container. It requires `Diagnostic + Send + Sync + 'static`, preserves a linear chain, and can downcast to attached context and underlying error types ([`Report`](https://docs.rs/miette/7.6.0/miette/struct.Report.html), [`WrapErr` downcasting contract](https://docs.rs/miette/7.6.0/miette/trait.WrapErr.html#effect-on-downcasting)). It does not provide error-stack's arbitrary opaque attachment history. Related diagnostics require an explicit aggregate field. Putting `Diagnostic` metadata on domain types also couples the domain to presentation concerns, so keep miette at the CLI boundary if selected.\n\n### `anyhow`\n\n`anyhow::Error` is an excellent conventional application error, but it erases the outer type and therefore conflicts with the typed application boundary. It requires errors and context values to be `Send + Sync + 'static`. Its linear chain and `Context` API preserve downcasting to both the newly attached context type and the underlying error type ([`Error`](https://docs.rs/anyhow/1.0.104/anyhow/struct.Error.html), [context downcasting](https://docs.rs/anyhow/1.0.104/anyhow/trait.Context.html#effect-on-downcasting)).\n\nThere is no opaque attachment category or native multi-error tree. Every context value is displayable. Debug output can include the full cause chain and a backtrace, so it is unsafe as a protocol response unless the application has already sanitized every message ([format docs](https://docs.rs/anyhow/1.0.104/anyhow/struct.Error.html#display-representations)). Use it for scripts or binaries that explicitly choose type erasure, not for these core boundaries.\n\n### `eyre`\n\nEyre has the same erased, linear model as anyhow and the same `Send + Sync + 'static` and downcasting strengths. Its reason to choose it is `EyreHandler`, which can capture extra data and customize report formatting; `color-eyre` adds backtraces, span traces, suggestions, and printable sections ([eyre README](https://github.com/eyre-rs/eyre/blob/v0.6.14/eyre/README.md), [`EyreHandler`](https://docs.rs/eyre/0.6.14/eyre/trait.EyreHandler.html)).\n\nThat customization does not repair the typed-boundary mismatch, and the global report hook is awkward when one process has CLI and MCP presentations. Rich output raises disclosure risk: `color-eyre` can print source lines, environment-controlled backtraces and span traces, plus attached stdout and stderr sections ([color-eyre README](https://github.com/eyre-rs/eyre/blob/color-eyre-v0.6.5/color-eyre/README.md)). There is no native peer-error tree.\n\n### `rootcause`, maintained alternative\n\nRootcause 0.13 is the only discovered maintained alternative that clearly competes with error-stack on the full shape of the problem. It offers optional `Report` typing, ownership-changing `context_to` and `context_transform`, downcastable typed attachments, full tree traversal, `ReportCollection`, cloneable reports, and explicit `SendSync` versus `Local` markers ([0.13 README](https://github.com/rootcause-rs/rootcause/blob/v0.13.0/README.md), [`ReportCollection`](https://docs.rs/rootcause/0.13.0/rootcause/report_collection/struct.ReportCollection.html)). It also exposes the current context's standard error source and lets formatting hooks follow source chains ([source-following example](https://github.com/rootcause-rs/rootcause/blob/v0.13.0/examples/following_error_sources.rs)).\n\nIt is credible, but not the default recommendation yet. Upstream calls it pre-1.0 and tells adopters to expect breaking minor releases. Its first release was October 2025, 0.13 raises the floor to Rust 1.89, and default attachments are formatted unless hooks change the policy ([changelog](https://github.com/rootcause-rs/rootcause/blob/v0.13.0/CHANGELOG.md), [attachment hooks](https://docs.rs/rootcause/0.13.0/rootcause/hooks/attachment_formatter/index.html)). Reassess it after 1.0 or if cloneable reports and unrestricted tree navigation become hard requirements.\n\n## Safety and upgrade constraints\n\nUse **error-stack 0.8.0 or newer only**. Versions before 0.8 had a soundness hole in the safe `Report::frames_mut` iterator that could create aliased mutable references and trigger undefined behavior, including demonstrated segfaults. Version 0.8 replaced it with a scoped visitor. RustSec marks `&gt;=0.8.0` patched ([RUSTSEC-2026-0198](https://rustsec.org/advisories/RUSTSEC-2026-0198.html), [fix](https://github.com/hashintel/hash/pull/8946)).\n\nEyre also fixed a high-severity memory-corruption bug in wrapped-report downcasting in 0.6.12. The advisory affects `&gt;=0.6.9, &lt;0.6.12`; current 0.6.14 is patched. Eyre 0.6.9 had also fixed strict-provenance Miri failures ([GHSA-4v52-7q2x-v4xj](https://github.com/advisories/GHSA-4v52-7q2x-v4xj), [changelog](https://github.com/eyre-rs/eyre/blob/v0.6.14/eyre/CHANGELOG.md)). No current-release soundness issue was identified in the official docs, repositories, and release material reviewed for thiserror, SNAFU, miette, anyhow, or rootcause. That is not a security-audit result.\n\nError-stack is still 0.x and has made breaking changes in 0.6, 0.7, and 0.8. Pin the accepted minor line in the workspace, run tests before updates, and read its changelog. In particular, write code against the stable APIs named above rather than nightly provider or `Try` behavior.\n", "creation_timestamp": "2026-09-14T15:45:28.779732Z"}]}