Skip to content

Commit edb219b

Browse files
docs(changelog): record v0.1.0 feature wave
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.
1 parent 3bf42c6 commit edb219b

2 files changed

Lines changed: 18 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7878
- `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.
7979
- SECURITY.md — security disclosure policy at repo root (in-scope vs out-of-scope surfaces, reporting flow, recent hardening notes).
8080
- 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.
81+
- v0.1.0 pre-publish feature wave (4-agent parallel sprint, disjoint file ownership):
82+
- 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".
8188

8289
### Refactor / cleanup
8390

@@ -98,6 +105,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
98105
- Fuzzer: skipped raw-transport iterations no longer bump `total_calls` or pollute `CallOutcome::Cancelled` (they never hit the wire).
99106
- Fuzzer: server-accepted malformed payloads now record `CallOutcome::Malformed` and bump `error_count` so threshold evaluators surface them.
100107
- 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).
101113

102114
### Performance
103115

@@ -130,6 +142,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
130142
- Regression thresholds `P99_REGRESSION_PCT` + `ERROR_RATE_REGRESSION_PP` lifted into `analysis::regression` and shared between `cmd_compare` and `serve/tools::compare_runs`.
131143
- 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.
132144
- `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.
133147

134148
### Deprecated
135149

@@ -139,7 +153,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
139153

140154
- ✅ 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.
141155
- `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.
143157
- ✅ 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.
144158
- ✅ 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.
145159
- ✅ 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.

docs/adr/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ Append-only log of significant decisions. New ADRs are added with the next numbe
1717
| [0009](0009-regression-threshold-defaults.md) | Regression threshold defaults: 10% p99 / 0.5pp error rate | Accepted |
1818
| [0010](0010-strict-schema-validation.md) | Opt-in strict MCP schema validation | Accepted |
1919
| [0011](0011-supply-chain-advisory-policy.md) | Supply-chain policy: CDLA-Permissive-2.0 + triaged advisory ignores | Accepted |
20+
| [0012](0012-ssrf-host-allowlist.md) | SSRF host-allowlist + always-on private-IP-literal block | Accepted |
21+
| [0013](0013-spawn-options-api.md) | SpawnOptions API for stdio stderr capture | Accepted |
22+
| [0014](0014-error-hints-explain-doctor.md) | Error hints, `--explain`, and `doctor` (AI-friendliness §21) | Accepted |
2023

2124
## When to write a new ADR
2225

0 commit comments

Comments
 (0)