mail-mcp/Cargo.toml
Kayos 5e1c63eeaa final-approval audit fixes: HIGH-1/2/3
Three findings from the post-cleanup approval audit, all blockers
before the rename to a real codename:

HIGH-1: ReadOutput.headers map kept LAST occurrence of duplicate
headers, not FIRST. Comment said 'keep the first occurrence' but the
code used Message::header_raw(name) which internally does
.iter().rev().find(...) — returns the last one. For load-bearing
headers like References this is usually singular so the bug was
latent, but an attacker who could inject a second References: line
would have gotten to override the first one used by mail_reply for
threading. Switched to parsed.headers_raw() which iterates in arrival
order — first-occurrence guaranteed.

HIGH-2: tokio-rustls default features pulled aws-lc-rs + aws-lc-sys
into the dep tree even though we explicitly went ring-only on rustls.
The default feature chain on tokio-rustls v0.26 enables 'aws_lc_rs'
via rustls. Pinned tokio-rustls to default-features=false and the
matching small feature set: logging, tls12, ring. Verified via
`cargo tree` — no aws-lc-* in the build, single ring v0.17.14
shared between rustls + tokio-rustls. ~9s shorter cmake step in cold
builds, smaller binary, no C-FFI crypto surface area.

HIGH-3: IntoMcpError trait was introduced in the cleanup pass but
applied at only 2 of 10 tools — the other 8 still used the manual
.map_err(|e| format!('{e:#}'))? + serde_json::to_string chain.
Maintenance trap. Applied to_mcp() at all 8 sites
(mail_inbox_list, mail_folder_list, mail_search, mail_thread,
mail_attachment_get, mail_inbox_read; mail_move and mail_mark stay
with literal {"ok":true} returns — no value to serialize). Tool
methods are now uniformly:
    imap_mod::xxx(...).await.to_mcp()
or for the few that need pre-arg work, three lines instead of seven.

Wire smoke verified — read on uid 34 returns the same 13 headers
shape, no empties, all canonical fields populated. cargo test 31/31.

Repo chain: 2240bf7 -> 4251f51 -> f4b3199 -> 6432a1f -> 54a1a6b ->
6fb63b0 -> f7e698b -> b681953 -> 7c8e246 -> this.
2026-05-21 09:22:39 -07:00

86 lines
3.1 KiB
TOML

# Cargo workspace root for mail-mcp.
#
# One crate today (mail-mcp), workspace shape so we can grow without
# rework. Same pattern as aldabra.
#
# Workspace deps pinned here; each crate references with `foo = { workspace = true }`.
[workspace]
resolver = "2"
members = ["crates/mail-mcp"]
[workspace.package]
version = "0.1.0"
edition = "2021"
license = "MIT"
repository = "http://192.168.0.5:3001/Sulkta-Coop/mail-mcp"
authors = ["Cobb <cobb@sulkta.com>", "Kayos <kayos@sulkta.com>"]
[workspace.dependencies]
tokio = { version = "1", features = ["full"] }
# MCP — same crate aldabra uses. Pinned to 0.1 series; bump together
# across repos when we move.
rmcp = { version = "0.1", features = ["server", "transport-io"] }
schemars = "0.8"
# SMTP — lettre handles RFC-5322 headers (Date, Message-ID), STARTTLS,
# multipart/alternative + multipart/mixed natively. rustls-tls so we
# don't pull openssl. No `hostname` feature — we override Message-ID
# with our own UUID@<from_domain>, so lettre never needs the system
# hostname.
lettre = { version = "0.11", default-features = false, features = [
"tokio1-rustls-tls",
"smtp-transport",
"builder",
] }
# IMAP — async-imap is tokio-native and supports UID-based addressing
# (which we use throughout the API surface).
async-imap = { version = "0.10", default-features = false, features = ["runtime-tokio"] }
# tokio-rustls default-features pulls in aws-lc-rs via rustls's default
# feature chain. We use `ring` exclusively (installed once in main.rs);
# turn off defaults and add back only the small pieces we want.
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12", "ring"] }
rustls = { version = "0.23", default-features = false, features = ["std", "tls12", "ring"] }
rustls-pki-types = "1"
webpki-roots = "0.26"
# Email parsing on the read side. mail-parser is fast, no_std-friendly,
# and handles the RFC-5322 + MIME zoo without surprises.
mail-parser = "0.9"
# Config + serde
toml = "0.8"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# UUID for Message-ID generation when lettre's auto isn't appropriate
# (we want our own domain in the Message-ID, not lettre's local-hostname
# default).
uuid = { version = "1", features = ["v4"] }
# Base64 for attachments
base64 = "0.22"
# Errors — anyhow at module boundaries; rmcp tool methods return
# `Result<String, String>` and convert via the IntoMcpError trait.
anyhow = "1"
# Stream adapter (.next() on async-imap fetch streams)
futures = "0.3"
# RFC-3339 timestamps for SendOutput.sent_at and parsed header dates.
# default-features=false keeps us off the system-locale crate; we only
# need the UTC clock + serialization helpers.
chrono = { version = "0.4", default-features = false, features = ["clock"] }
# Logging — stderr only, never stdout (stdio is the MCP transport).
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Dirs lookup for `~/.config/mail-mcp/config.toml` default path
dirs = "5"
# Shell-style env-var expansion for the `password_file` setting
# (`~/.config/...` paths). shellexpand is small + maintained.
shellexpand = "3"