release: v1.17.0 — security audit 2026-05-09 (30+ FIND remediated)#48
Merged
Conversation
12 vulns triaged by severity and grouped into 4 PRs: - P0 input validation + audit redaction + path traversal (Tasks 1-7) - P1 HTTP transport hardening (Tasks 8-9) - P1 multi-session isolation (Tasks 10-11) - P2 validator shell-aware normalization (Task 12) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vuln 7 (audit 2026-05-09). protocol arg was interpolated raw into
iptables/ufw/firewall-cmd shell commands. Added validate_protocol()
restricting to {tcp, udp, icmp, icmpv6}; called from both build_allow_command
and build_deny_command.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vuln 6 (audit 2026-05-09). unit_type was interpolated raw into
'systemctl list-units --type={utype}'. Converted build_list_command
to Result, added validate_unit_type() with the documented set of
unit types. Updated ssh_service_list handler to propagate the error.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vuln 5 (audit 2026-05-09). Variable KEYS in HashMap<String,String>
were interpolated raw into 'export {k}={v}' shell commands while
values were correctly escaped. Added validate_env_var_name() requiring
POSIX [A-Za-z_][A-Za-z0-9_]*; converted build_template_command to Result.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vuln 12 (audit 2026-05-09). build_user_info_command and build_group_members_command concatenated raw username/group strings into LDAP filter syntax. Added ldap_filter_escape() encoding (, ), *, \, NUL per RFC 4515 §3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vuln 4 (audit 2026-05-09). Hardcoded 'TEMPLATE_EOF' allowed an attacker
to close the heredoc by including a sole-line 'TEMPLATE_EOF' in the
content body, then run arbitrary shell after it. Now generates
'MCP_EOF_{uuid}' per call and verifies it does not appear as a sole
line in the body.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vuln 3 (audit 2026-05-09). Audit log was emitting MYSQL_PWD, PGPASSWORD, Bearer tokens, and webhook URL secrets in plaintext to both the JSONL file and tracing sinks. Added new_with_sanitizer constructor on AuditLogger; runs Sanitizer::sanitize over event.command before tracing emission and file write. Audit log file now opens with mode 0o600 on Unix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vuln 11 (audit 2026-05-09). validate_root_scope did a string prefix match without resolving '..', so '/declared-root/../../etc/shadow' passed. Added normalize_path_lexical() that collapses '.', '..', and empty components before the prefix check. ssh_file_read already routes through scoped_paths -> validate_root_scope in the StandardToolHandler pipeline (standard_tool.rs:281), so fixing the validator alone closes the bypass for that handler too. Sibling ToolHandler-based handlers (ssh_file_write, ssh_ls, ...) call validate_root_scope directly and inherit the same fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…public bind Vuln 1 (audit 2026-05-09). HttpTransportConfig::default() bound 0.0.0.0:3000 with OAuth disabled and an Origin guard that explicitly forwarded requests without an Origin header — full unauthenticated RCE for any non-browser network attacker. Changes: - Default bind: 127.0.0.1:3000 (transport + YAML config) - New allow_unsafe_bind field on HttpTransportConfig (defaults false) - serve() refuses non-loopback bind unless OAuth is enabled or allow_unsafe_bind is explicitly true - New --insecure-bind CLI flag for serve-http - origin_guard rejects requests with no Origin header (was forwarded before, defeating anti-DNS-rebinding intent) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vuln 2 (audit 2026-05-09). Previous validator decoded the JWT payload and read claims without verifying the signature, exp, nbf, or alg. Replaced with jsonwebtoken-backed verification: RSA + ECDSA + RSA-PSS algorithms only (HMAC family rejected to prevent alg-confusion), exp/nbf checked with 30s leeway, iss/aud enforced, required_scopes enforced. Static-keys mode only in this commit; load_jwks() accepts a parsed JWKS document but the HTTP fetch and Arc<OAuthValidator> wiring through Axum extensions are deferred to a follow-up. OAuth-enabled deployments must populate keys via OAuthValidator::set_static_keys until that wiring lands; in the meantime the per-request middleware validator has an empty key map and rejects every token with "Unknown JWT signing key". Stub-tests that asserted Ok on tokens with alg=none and forged signatures have been removed — they encoded the previous insecure behaviour. Added six new tests covering signature, exp, iss, scope, alg=none rejection, and a happy-path accept. RSA test fixtures live under tests/fixtures/oauth/ (synthetic, test-only). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t 1)
Vuln 8 (audit 2026-05-09) part 1. Switched server-initiated request
ids from monotonic 'srv-{N}' to 'srv-{uuid_v4_simple}'. Even if a
multi-session deployment leaks the map, ids cannot be brute-forced.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vuln 8 (audit 2026-05-09) part 2. Moved PendingRequests from a single server-wide Arc to a per-session allocation in serve_session. Threaded through ToolContext so handlers reach the session-local map. Added multisession_isolation integration test verifying client B cannot resolve client A's pending request. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Vuln 9 (audit 2026-05-09). client_supports_elicitation / client_supports_sampling / client_supports_roots were single per-server AtomicBools that flipped to true on the first client's initialize and were never reset. In daemon multi-session mode, a client that did NOT advertise elicitation could still trigger destructive tools and auto-confirm via global state from a prior client. Moved all three flags into a per-session SessionCapabilities struct (src/mcp/session_capabilities.rs). serve_session allocates a fresh Arc<SessionCapabilities> per connection. handle_initialize writes its client's capabilities to the session-local struct. ToolContext snapshots the booleans for handlers; check_destructive_elicitation reads the session_caps directly so the gate consults THIS session only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ist match
Vuln 10 (audit 2026-05-09). Default blacklist regexes use \s+ between
command words (rm\s+-rf\s+/), so an MCP client sending
'rm${IFS}-rf${IFS}/' bypassed the regex and ran 'rm -rf /' on the
remote host once shell expansion happened. validate() and
validate_builtin() now collapse ${IFS}, $IFS, $'\t', $'\n', $' ',
and '\<NL>' to single spaces before running the blacklist regexes;
the whitelist still matches the raw command for byte-for-byte
strictness.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Accumulated formatting drift from per-task commits. No semantic changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Cached per-crate upstream docs for 10 audit-critical deps and produced a drift summary against in-tree code. Drift findings flagged P0–P2 feed the Task 8/9/10/11 scans: - P0: serde-saphyr loaders use bare from_str (no Budget) — billion-laughs vector on src/config/loader.rs and src/domain/runbook.rs - P0/P1: axum HTTP transport missing TimeoutLayer, DefaultBodyLimit override, SetSensitiveHeaders, RequestIdLayer - P1: jsonwebtoken Validation missing set_required_spec_claims (sub/iss/aud only validated when present) - P1: russh client::Config::Preferred not explicitly set — relies on upstream Default which may include weaker legacy algos No-drift findings (russh check_server_key, rustls dangerous(), serde_yaml removal, secrecy substitution by Zeroizing) documented for audit completeness.
Phase B+C of full security audit campaign per docs/superpowers/plans/2026-05-09-full-security-audit.md (Task 5). Skill: audit-context-building:audit-context-building (trailofbits, v1.1.0). Six function-analyzer subagents dispatched in parallel on the highest-risk surfaces. Each completed the per-function microstructure checklist (Purpose, Inputs+Assumptions, Outputs+Effects, Block-by-Block analysis with First Principles + 5 Whys + 5 Hows, Cross-Function Dependencies) under the skill's quality thresholds (>=3 invariants/fn, >=5 assumptions, >=3 risk considerations, >=1 First Principles, >=3 combined 5-Whys/5-Hows). Surfaces analysed: - src/security/validator.rs (raw + builtin gate, normalize_for_blacklist_match) - src/mcp/pending_requests.rs (Vuln 8 patched surface, per-session isolation) - src/mcp/session_capabilities.rs (Vuln 9 patched surface, AtomicBool flags) - src/mcp/transport/oauth.rs (JWT validator, alg-confusion mitigation) - src/ssh/client.rs (Handler::check_server_key, auth paths, jump-host RAII) - src/domain/runbook.rs (YAML loader, apply_template, command construction) Pure context only — no findings, no severities, no PoCs per skill non-goals. Open questions per surface feed Tasks 8, 9, 11, 13.
Phase B+C of full security audit per
docs/superpowers/plans/2026-05-09-full-security-audit.md (Task 6).
The trailofbits entry-point-analyzer skill explicitly excludes
non-smart-contract codebases ("Do NOT use this skill for non-smart-
contract codebases"), so this surface mapping is produced via direct
programmatic classification: file-name pattern matching + handler-body
grep for credential keywords + body grep for destructive_hint markers
+ raw-exec vs builtin-exec discrimination by checking for use_case
delegation. Method documented in audit/2026-05-09/surface/entry-points.md
header.
Risk score = writes_files*4 + executes_shell_raw*4 + handles_creds*3
+ destructive*3 + executes_shell_builtin*2 + reads_files*1.
Bucket distribution (358 non-mod.rs handlers — actual count is 358,
not 357 as documented in CLAUDE.md):
- P0 (>=10): 97 (raw exec, file writes, cred-bearing destructive ops)
- P1 (7-9): 21
- P2 (4-6): 222 (majority — read-only credential queries, builtin exec)
- P3 (1-3): 18
- P4 (0): 0 (every handler scored at least 1)
Output cross-checked: ls of src/mcp/tool_handlers/ excluding mod.rs
= 358 = CSV row count.
Feeds Tasks 8, 9, 11 for targeted scans on the P0 + P1 surfaces.
Phase D of full security audit per docs/superpowers/plans/2026-05-09-full-security-audit.md (Task 8). Skill: static-analysis:semgrep (trailofbits, v1.2.1). Engine: Semgrep OSS 1.162.0 (Pro not available — no cross-file taint). Mode: important-only (severity MEDIUM+, post-filter category=security + MEDIUM/HIGH confidence+impact). Scope: src/security/, src/ssh/, src/mcp/, src/domain/, src/config/, src/winrm/, src/psrp/ (502 .rs files). User-approved scan plan via AskUserQuestion hard gate per skill protocol. Four parallel scanner subagents dispatched: - r/rust (4 rules) -> 0 findings - p/security-audit (2 multilang) -> 0 findings (pack does not cover Rust) - r/generic.secrets (48 rules) -> 5 findings (all FP: sanitizer self-test fixtures) - trailofbits/semgrep-rules/rust -> 4 findings (panic-in-Result class) Merged SARIF: 9 findings. True positives (4): - src/ssh/retry.rs L204 + L282 — panic in Result-returning fn (hot path) - src/ssh/pool.rs L395 — panic in Result-returning fn (pool drain) - src/mcp/tool_handlers/ssh_file_write.rs L237 — panic in handler False positives (5): - src/security/sanitizer.rs L38, L1021, L1190, L1239, L1289 — deliberate test vectors so the sanitizer can verify it redacts those patterns. The four Task-4 context7 drift findings (serde-saphyr no Budget, axum missing security middleware, jsonwebtoken missing set_required_spec_claims, russh Default Preferred algos) and the five Task-5 context-summary focus areas (validator bypass, path traversal, plain-String creds, cross-session shared state, audit log raw command) are NOT pattern-matchable by the available OSS Rust rulesets. They are restated in static-analysis.md as custom-rule candidates for Task 12 (semgrep-rule-creator).
Single-source-of-truth backlog for every concrete problem the 2026-05-09 audit surfaces. Each finding has a stable FIND-NNN id (append-only), source attribution, fix recommendation, and status. Initial seed (21 open findings + 5 confirmed FP + 12 OQ): - P0 (6): serde-saphyr Budget gaps x4, axum middleware stack, OAuth validator key-population production wiring - P1 (3): jwt set_required_spec_claims, russh Preferred algos + Limits, EdDSA allowlist gap - P2 (8): 4 panic-in-Result (semgrep TOB), SOCKS password not Zeroizing, originator_address hardcoded, sanitize_ssh_error coverage gap, deny_unknown_fields missing - P3 (4): cargo outdated broken (winrm-rs/reqwest), 2 pre-existing clippy errors blocking ci-full, cargo-geiger nkeys extract issue - FP (5): all in src/security/sanitizer.rs (sanitizer self-test fixtures legitimately containing example secrets) - OQ (12): consolidated from per-surface Open Questions in audit/2026-05-09/surface/context-summary.md Append protocol for Tasks 9-17 documented at end of file. This tracker is the actionable backlog; audit/2026-05-09/FINDINGS.md will be the Task-16-generated derivative report.
6 sibling vulnerabilities identified by enumerating every McpServer field for the Vuln 8/9 pattern (process-singleton mutable collection keyed without session scope, mutated by per-session handlers). CRITICAL — FIND-038 (P0): src/mcp/server.rs:91 active_requests HashMap<String, CancellationToken> keyed on caller-chosen JSON-RPC request id. Concurrent client B can cancel client A's in-flight request by sending notifications/cancelled with A's request id. Direct cross-session DoS — same defect class as Vuln 8. P1 quality-of-service / cross-session contamination: - FIND-033 runtime_max_output_chars (last-writer-wins) - FIND-034 notification_tx slot (notifications route to wrong session) - FIND-036 resource_subscriptions HashMap (URI-keyed, not session-keyed) - FIND-037 roots Vec (last-writer-wins per fetch_roots) P3: - FIND-035 log_level cross-session mute Verified secure (no finding): client_info, all *::ConnectionPool / WinRmPool / PsrpPool (intentional cross-session by design), sanitizer/registry static collections. Recommended fix shape for all 6: same as Vuln 8/9 — move field out of McpServer, allocate per-session Arc in serve_session, clone into spawned tasks, audit read/write sites.
…file (FIND-024)
BREAKING CHANGE: Unlisted tool groups now default to DISABLED. Operators
who relied on "unlisted = enabled" must explicitly enable each group in
config.yaml under `tool_groups.groups`. The minimal default profile is
[core, file_ops, directory, process, monitoring, network, systemd, sessions].
Pre-FIND-024, `tool_groups: { groups: {} }` registered every group (75
groups / 357 handlers), exposing AD/LDAP/Vault/K8s/AWS/ESXi/HyperV /
Windows-only tools to operators who only needed `docker` + `service`.
The `is_group_enabled` resolver now falls through unlisted groups to
membership in the new `MINIMAL_DEFAULT_GROUPS` const (eight groups
covering raw exec, file ops, ls/find, process mgmt, system metrics,
network diagnostics, systemd services, and persistent tmux sessions).
Explicitly listed groups (`true` or `false`) still win over the default
profile.
Test migration: introduced `create_all_enabled_registry()` and
`all_enabled_tool_groups_config_for_test()` test helpers; ~85 tests in
mcp::registry, mcp::meta_tools, and mcp::server that relied on the old
"all groups enabled" semantic now use these helpers. Production paths
(`server.rs:221`, `cli/runner.rs`) are unaffected — they already drive
the registry from operator-supplied `config.tool_groups`.
Added FIND-024 regression test
(`test_default_registry_only_contains_minimal_profile`) that asserts
every tool in the default registry belongs to a `MINIMAL_DEFAULT_GROUPS`
group, every minimal group registers at least one handler, and the
default registry is strictly smaller than the full inventory.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
shellexpand crate was archived upstream on 2026-02-25 — no further security patches. Replace it with a thin dirs::home_dir-based helper in a new src/path_utils.rs module exposing home_expand, home_expand_string, and home_expand_or_input (the latter preserves shellexpand's "never fail" contract by passing through unchanged when home cannot be resolved). Migrated 10 call sites (CLI runner ×3, config loader ×2, ssh_config ×1, ssh client ×1, file-transfer handlers ×3). dirs is already a direct dep so no new transitive surface. Verification: - cargo build --lib clean - cargo test --lib: 7017/7017 pass (incl. 6 new path_utils tests) - cargo clippy --lib -- -D warnings clean - cargo machete clean (no new unused deps) - shellexpand fully removed from Cargo.lock Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SshVaultWriteArgs.data carried vault key=value secret pairs as plain Vec<String>; the heap allocation persisted until Args drop, leaving secret bytes resident. - SshVaultWriteArgs.data: Vec<Zeroizing<String>> - VaultCommandBuilder::build_write_command takes &[Zeroizing<String>] - shell_escape call site unchanged (Deref coercion: &Zeroizing<String> -> &String -> &str) - 7 test fixtures across vault.rs and ssh_vault_write.rs updated to wrap their literals in Zeroizing::new - Test assertions on Args.data converted via .iter().map(|s| s.as_str()) Note: this only addresses LOCAL heap residency. The secret values still transit shell argv on the REMOTE host (visible in `ps eww` / `/proc/PID/environ`) — that is FIND-031, addressed in Task 21 by piping data via stdin.
Sprint 2 Task 21. Secrets no longer transit shell argv or environ on the remote host. Vault (`build_write_command`): - `key=value` data switched from argv to a stdin heredoc with a randomized `VAULT_DATA_EOF_<uuid>` terminator (mirrors the `template_apply` pattern from 2da5d55). Single-quoted terminator disables shell expansion; the body lives in the kernel pipe buffer, never `ps eww`. Database (`build_query_command`, `build_dump_command`, `build_restore_command`): - `MYSQL_PWD=...` env var replaced by `mysql --defaults-extra-file=$TMPF` reading a 0600 tempfile that holds `[client]\npassword=...`. - `PGPASSWORD=...` env var replaced by `PGPASSFILE=$TMPF` pointing at a 0600 `~/.pgpass`-format file (`host:port:db:user:password`, with libpq `:`/`\\` escaping). - Tempfile creation: `mktemp` (race-free), `chmod 600` before write (no TOCTOU), cleanup `trap 'shred -u "$TMPF" 2>/dev/null || rm -f "$TMPF"' EXIT` (handles BusyBox/Alpine where `shred` is missing). Tests: - Six FIND-031 regression tests in `database.rs` covering query, dump, and restore for both MySQL and PostgreSQL — assert no password appears post-redirect (the `ps eww`-visible portion) and that `MYSQL_PWD=` / `PGPASSWORD=` env vars are gone. - Three FIND-031 regression tests in `vault.rs` — argv-leak split on `<<`, stdin-heredoc shape, randomized-terminator (call twice and assert different strings). - Handler assertions in `ssh_db_query.rs` and `ssh_db_dump.rs` flipped from `PGPASSWORD=` to `PGPASSFILE=$TMPF`. 7027 lib tests green; `cargo clippy --lib -- -D warnings` clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…s (FIND-019/020) - tests/security_audit_redaction.rs:84: inline format args (clippy::uninlined_format_args, FIND-019) - src/mcp/transport/oauth.rs:531 sign_token(&Value): pass by reference (clippy::needless_pass_by_value, FIND-020); 9 call sites updated - tests/ssh_preferred_algos.rs:117: struct-update syntax instead of field reassignment after Default::default() (clippy::field_reassign_with_default; same class, introduced by FIND-008 test additions, batched here) cargo clippy --workspace --all-targets --all-features -- -D warnings: clean. cargo test --lib: 7027 passed.
`winrm-rs 1.0.0` on crates.io declares `reqwest ^0.13` with feature `webpki-roots`, which reqwest 0.13.3 marked obsolete. cargo outdated fails to resolve the dep graph as a result, blocking version-audit tooling. Upstream fix: ~/winrm-rs commit 573dadf drops the obsolete feature (rustls + socks remain; webpki-roots ships transitively via rustls 0.23+). Pin via [patch.crates-io] until winrm-rs > 1.0 is published. `cargo outdated` now resolves; `cargo test --lib` 7027 pass.
cargo geiger --all-features fails to extract nkeys-0.4.5 (aws-sdk transitive) on cold cargo cache. Makefile target now: - pre-fetches the crate graph - falls back to --forbid-only on extraction failure (acceptable since #![forbid(unsafe_code)] is enforced workspace-wide) - corrects --output-format from `ascii` to `Ascii` (cargo-geiger 0.13 parses the value case-sensitively) audit/2026-05-09/baseline/cargo-geiger.txt re-captured at 2730 lines, all entries `?` (no unsafe forbidden status known) — expected for the forbid-only path.
`McpServer.log_level` was a server-singleton `Arc<AtomicU8>`. Any session's `notifications/setLevel` overwrote it, so client B could silently mute client A's `notifications/message` stream (cross-session denial-of-observability). - SessionContext gains `log_level: Arc<AtomicU8>` (mirrors the per-session storage pattern from FIND-033/034/036/037). - serve_session() builds the per-session McpLogger against the session's slot, so notifications are gated by THIS client's threshold. - handle_logging_set_level writes the session's slot; falls back to the server-wide field for legacy non-session call paths (tests). - Server-wide field retained as fallback for the same backward-compat reason. - Regression tests in tests/per_session_log_level.rs cover (a) default starts at Warning, (b) two sessions hold independent storage with distinct Arc allocations. cargo test --lib: 7027 pass. cargo test --test per_session_log_level: 2/2 pass. cargo clippy --workspace --all-targets --all-features -- -D warnings: clean.
Three integration suites still asserted the pre-audit defaults: - tests/config_validation.rs::test_valid_config_preserves_defaults: expected ssh_config.enabled = true; FIND-023 flipped it to false. - tests/mcp_conformance.rs (2 tests): used ToolGroupsConfig::default() to drive a 357-tool registry; FIND-024 flipped default to the 8-group minimal profile. Switched to create_all_enabled_registry() (test helper). - tests/tool_filtering.rs (~13 tests): same root cause. Switched fixtures to all_enabled_tool_groups_config_for_test() so the disable-X tests still exercise the full registry as their starting point. cargo test --tests: 0 FAILED (was 4 FAILED). All 7027 lib + integration tests green.
Audit complete, all FIND-### remediation merged to security branch. Drop audit/, docs/audit-2026-05-09-findings.md, security-fixes plan. Findings tracker history preserved in git log + CHANGELOG.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bumps Cargo + dxt manifest + server-card to 1.17.0. CHANGELOG promotes [Unreleased] to [1.17.0] with full FIND-### inventory (BREAKING flips on FIND-022 + FIND-024, per-session isolation, secret zeroization, saphyr Budget, JWT/OAuth, russh hardening). Includes rustfmt sweep across security fix sites. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI runners don't have a sibling /work/mcp-ssh-bridge/winrm-rs checkout. Switch to git ref pinning the FIND-018 fix on muchiny/winrm-rs at 573dadf5 (drops obsolete reqwest webpki-roots feature). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Full security audit branch landing — 30+ FIND-### findings remediated, 86 commits, 2 BREAKING default flips. Tag
v1.17.0already pushed (Docker + Release workflows running).BREAKING
tool_groupsdefault-disabled, ships 8-group minimal profile (FIND-024).security.require_elicitation_on_destructivedefaults totrue(FIND-022).SshConfigDiscoverydefault off (FIND-023).Highlights
PendingRequests,SessionCapabilities,active_requests,runtime/notification/resource_subs/roots,log_level(Vuln 8/9 + FIND-033..038).sudo_password,db_password, vaultArgs.data, SOCKS proxy password, vault/db secrets via stdin/tempfile (FIND-014/028/029/030/031).Budgeton every parse site +deny_unknown_fieldson every config struct (FIND-001/002/004/032 + FIND-017).sub/iss/audspec claims (FIND-007); OAuth keys boot-loaded, sharedArc<OAuthValidator>(FIND-006).expect/unwrapwith?/Erron Result fns (FIND-010..013); clippy clean--all-targets --all-features(FIND-019/020); cargo-geiger CI fallback (FIND-021).shellexpand(FIND-025); patchwinrm-rsreqwest feature (FIND-018); monitoring entries forsaphyr+tokio-socks(FIND-026/027).Full inventory in
CHANGELOG.md+ commit log.Test plan
cargo fmt --checkcleancargo checkcleancargo clippy -D warningsclean🤖 Generated with Claude Code