You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
CHANGELOG [Unreleased]: the 4 features, the breaking changes
absorbed pre-publish, a Security entry for the SSRF surface (incl.
the IPv6 host_str bypass found+fixed in review), and the 0007
deferral note flipped to superseded-by-0012. docs/adr/README index
gains rows 0012-0014.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+15-1Lines changed: 15 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -78,6 +78,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
78
78
-`protocol::transport::ws::WsTransport` — WebSocket transport via tokio-tungstenite (rustls + webpki-roots); 16 MB per-frame OOM cap mirroring stdio. Activates the `"ws"` scheme that was previously parser-accepted but rejected at runtime.
79
79
- SECURITY.md — security disclosure policy at repo root (in-scope vs out-of-scope surfaces, reporting flow, recent hardening notes).
80
80
- 12 new tests bring the suite to ~255 passing (was 243): spike happy path + 5 HTML reporter (escaping, chart, violation styling) + 2 WS (echo roundtrip + scheme rejection) + 1 config parse-spike-kind + scenario name/schema asserts.
- SSRF host-allowlist (ADR 0012): `[server].allowed_hosts` exact-match (ASCII case-insensitive, no wildcard) allowlist for `http`/`sse`/`ws`; empty/unset = allow any public host. Always-on block of private/loopback/link-local/ULA/unspecified/reserved **IP literals** (the SSE server-provided endpoint URL is checked too), with an operator escape hatch (list the literal, e.g. `"127.0.0.1"`, to permit local testing). `protocol::transport::HostGuard` is now public (needed to construct `Http`/`Sse`/`Ws` transports directly via their `connect(url, &guard)` constructors).
83
+
-`--capture-stderr` / `--tee-stderr` on `run` (ADR 0013): redirect the spawned stdio server's stderr to `runs/<id>/server-stderr.log` (capture) or additionally mirror it live to the parent's stderr (tee). New public API `SpawnOptions` / `StderrMode` / `StderrCapture`, `Session::spawn_with`, `StdioTransport::spawn_with`, `Run::with_stderr_capture`; the stable 2-arg `Session::spawn` is unchanged (delegates). Tee is a cancellation-aware, JoinHandle-tracked task that flushes before every exit.
84
+
-`mcp-loadtest doctor` (DESIGN §21.6, ADR 0014): 4 best-effort checks — Python on PATH, optional `--server` initialize smoke (captures the server's stderr on failure), stale `runs/` accumulation, Windows MSVC/GNU toolchain mismatch. ✅/❌ checklist + one-line fix per ❌; non-zero exit if any ❌.
85
+
-`--explain` global flag (DESIGN §21.4, ADR 0014): static per-subcommand algorithm text, exit 0. Serviced by a pre-clap `std::env::args()` scan so it works without a subcommand's required args (e.g. `run --explain` needs no `--config`).
86
+
- Actionable error hints (DESIGN §21.3, ADR 0014): `hints::ErrorHint` (CLI crate) maps `RunError`/`SessionError`/`TransportError`/`ConfigError`/`ReportError` to a one-line next step, printed after the error chain at the CLI boundary so the library error enums stay clean.
87
+
- Python fixtures `mock-leak.py` (10 KB/call RSS growth), `mock-error.py` (cycles `-32601`/`-32602`/`-32603`), `mock-slow-init.py` (5 s `initialize` delay), `mock-malformed.py` (every 10th response is newline-terminated broken JSON) — DESIGN §16.7-16.10, no longer "planned for v0.2".
81
88
82
89
### Refactor / cleanup
83
90
@@ -98,6 +105,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
98
105
- Fuzzer: skipped raw-transport iterations no longer bump `total_calls` or pollute `CallOutcome::Cancelled` (they never hit the wire).
99
106
- Fuzzer: server-accepted malformed payloads now record `CallOutcome::Malformed` and bump `error_count` so threshold evaluators surface them.
100
107
- Run: `memory_growth_mb` now compares `peak − final` instead of bare peak, so a steady-state high-RSS process no longer false-positives.
108
+
- SSRF guard IPv6 hole found + fixed in review: `url::Url::host_str()` returns bracketed IPv6 (`[::1]`), so the initial `parse::<IpAddr>()` host extraction silently let _every_ IPv6 literal bypass the private-IP block. The guard now classifies the host via the typed `url::Host` enum (the parser already split host kind at parse time), closing the bypass; covered by IPv6/IPv4-mapped unit tests.
109
+
110
+
### Security
111
+
112
+
- SSRF hardening (ADR 0012, supersedes the ADR 0007 deferral): every outbound `http`/`sse`/`ws` connection — including the SSE server-provided endpoint URL — is validated against an optional exact-match host allowlist plus an always-on private/loopback/link-local/ULA/unspecified/reserved **IP-literal** block, before any network I/O. Complements the existing `Policy::none()` redirect policy. Residual documented gap: a hostname that _resolves_ to a private IP (DNS rebinding) is not blocked in v0.1 — the allowlist is the strong control; a resolver-pinning connector is the recorded v0.2 follow-up (ADR 0012 Open).
101
113
102
114
### Performance
103
115
@@ -130,6 +142,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
130
142
- Regression thresholds `P99_REGRESSION_PCT` + `ERROR_RATE_REGRESSION_PP` lifted into `analysis::regression` and shared between `cmd_compare` and `serve/tools::compare_runs`.
131
143
- Unified scenario builder: a single `build_scenario` factory now drives every config — `sustained` accepts weighted `patterns` / legacy `tool_call` arrays (via `PatternScenario`) alongside the single-`tool` path, and all M5–M7 kinds (`ramp` / `soak` / `spike` / `race_check` / `fuzzer` / `pattern`) dispatch through it.
132
144
-`cmd_run` split into private `builder` / `params` / `patterns` submodules (each under the 300 prod-LoC convention); public surface unchanged (`run_from_config` + the re-exported `parse_dur_str``main.rs` shares with `deadlock-probe` / `cross`).
145
+
-**Breaking (absorbed into v0.1.0, pre-publish):**`HttpTransport`/`SseTransport`/`WsTransport`::`connect` take an added `&HostGuard` argument; `StdioTransport::spawn` is now `async`; CLI `cmd_run::run_from_config` takes added `capture_stderr` / `tee_stderr` parameters. The documented `Session::spawn(command, args)` signature is unchanged (delegates to `spawn_with`).
146
+
- DESIGN.md §16.7-16.10: dropped the _(planned for v0.2)_ markers — the four fixtures now ship.
133
147
134
148
### Deprecated
135
149
@@ -139,7 +153,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
139
153
140
154
- ✅ The M8 file-split pass completed in the pre-publish review. All source files have production code (excluding `#[cfg(test)] mod tests`) under the 300-line convention. See `POST_PUBLISH_ISSUES.md` for the per-wave summary of what split where.
141
155
-`serve` and `tui` modules will move behind cargo feature flags in a future release to keep the default build slim.
142
-
- HTTP / SSE transport host-allowlist for SSRF defense is deferred. Currently mitigated by `Policy::none()`on redirects; the allowlist is operator-facing config and will land alongside the broader transport-hardening pass.
156
+
-✅ HTTP / SSE / WS transport host-allowlist for SSRF defense landed (was deferred): exact-match `[server].allowed_hosts` + always-on private/loopback/link-local/ULA/reserved IP-literal block, on top of the existing `Policy::none()`redirect policy. Supersedes the ADR 0007 deferral — see ADR 0012. Residual documented gap: hostname→private-IP (DNS rebinding) is not yet blocked in v0.1; resolver-pinning is the recorded v0.2 follow-up.
143
157
- ✅ Pre-publish security pass on the B1/B2 surface: bounded `protocol::schema` recursion (`MAX_SCHEMA_DEPTH`, defends strict mode against a maliciously deep server `inputSchema`); non-positive regression thresholds rejected at the CLI/MCP boundary (would otherwise invert the gate); `compare_runs` no longer echoes the raw caller path; `enum`-violation messages are length-capped.
144
158
- ✅ Pre-publish stabilization: `crates/mcp-loadtest` now `exclude`s the four `CLAUDE.md` scaffolding files from the published package (108 → 104 files); added `run_strict` CLI integration test covering the real `run --config` entrypoint with `[validation] strict = true` end-to-end (TOML → `Run::execute` → report-on-disk → non-zero gate); full pipeline (fmt, clippy `-D warnings`, `--locked` build/test, doc) verified on the **x86_64-pc-windows-msvc** target — the toolchain crates.io ships to Windows users.
145
159
- ✅ Supply-chain gate (`cargo deny check` / `cargo audit`): allowlisted `CDLA-Permissive-2.0` (the `webpki-roots` CA-bundle license, via `tokio-tungstenite`) and added three individually-triaged, documented advisory `ignore`s — RUSTSEC-2025-0052 (`async-std`, dev-only via `httpmock`), RUSTSEC-2024-0436 (`paste`, transitive), RUSTSEC-2026-0002 (`lru` unsoundness, transitive via `ratatui`, optional `tui` feature only, no semver fix). Rationale + revisit conditions in ADR 0011 + `POST_PUBLISH_ISSUES.md`; no actual vulnerabilities, gate still hard-fails on anything new.
0 commit comments