v0.5.27
What's new in v0.5.27
v0.5.27 is a three-axis release. The CLI output paths and the daemon ack flow get a hardening pass that closes filesystem and terminal-injection gaps inherited from earlier releases. The interactive TUI (perf-sentinel query inspect) drops the 100-300 ms freeze that used to hold the screen still on every a / u Submit while the ack/revoke roundtrip ran. And a batch of allocation-light rewrites lands on the analysis hot paths, including the per-finding signature builder and the redundant-call detector. No public surface change, no behavior change for already-clean inputs.
CLI hardening
CLI write paths now open output files with O_NOFOLLOW on Unix for the HTML report (perf-sentinel report --output), the calibration TOML (perf-sentinel calibrate --output), and the diff --output file. Mirrors the daemon ack store discipline so a hostile pre-planted symlink in a shared CI runner cannot redirect a write outside the operator's tree. The Tempo and pg-stat endpoint validators (validate_http_endpoint, validate_prometheus_endpoint) now reject ASCII control characters before reaching hyper::Uri, matching the strict posture already in place on the daemon and ack URL validators. Every CLI error path that touches a daemon-supplied body, an environment-variable URL, or a stdin signature now goes through text_safety::sanitize_for_terminal consistently. A hostile env-var value or a malicious daemon response can no longer repaint the operator's terminal at error time. The daemon ack store tightens its parent directory to mode 0700 on Unix when the default storage path is created (closes the world-writable XDG_DATA_HOME edge in shared-tenancy environments) and the rewrite_compacted path re-checks for symlinks immediately before the rename to close the long compaction window. The CSP placeholder safety net in the HTML report renderer is promoted from debug_assert! to plain assert! so the guard survives release builds. The .gitleaks.toml finding-signature regex is tightened to the actual sanitize_endpoint output charset ([A-Za-z0-9_.-]), shrinking the false-positive bypass surface.
Operator-visible warnings
Three new WARN-level events surface configuration risks at the moment they happen, before the operator discovers them through a broken Acks panel or a captured key replayed from a hostile origin. The HTML report now emits a render-time warning when --daemon-url http://... points at a non-loopback host, catching the "report served over HTTPS but daemon URL is HTTP" mixed-content trap before the operator opens the file. The daemon emits a startup warning when [daemon.cors] allowed_origins = ["*"] is combined with [daemon.ack] api_key. Wildcard CORS plus an X-API-Key auth lets any browser origin replay a captured key. Whitelist explicit origins for production deployments. The tempo and jaeger-query subcommands now emit a ps-visibility warning when --auth-header is used directly, mirroring the existing nudge on pg-stat, pointing operators at --auth-header-env.
Input caps
The perf-sentinel ack create stdin signature read is now capped at 1 KiB, so a cat /dev/urandom | perf-sentinel ack create pipe cannot exhaust memory before the daemon-side validator rejects the input. The interactive rpassword API-key prompt is capped at the same 1 KiB for symmetry.
TUI ack non-blocking
perf-sentinel query inspect used to gate every a / u Submit on three sequential Handle::current().block_on(...) calls (POST/DELETE then GET refetch). The UI froze for 100 to 300 ms per submit, longer over a remote daemon. v0.5.27 keeps the run loop synchronous (no crossterm event-stream feature, no ratatui or tokio bump) and instead snapshots the modal state into an owned AckSubmitPayload then Handle::current().spawn(execute_ack_submit(...)) returns immediately. The sync run_loop drains a tokio::sync::mpsc::UnboundedReceiver<AckOutcome> before each redraw and switches to event::poll(50ms) only while a write is in flight, falling back to blocking event::read() at idle so the power profile matches the pre-refactor baseline. Three edge cases are now locked by tests: a held Enter or double tap on Submit is gated by the submitting: bool flag (no duplicate spawn, no spurious 409 on the daemon side), an AckOutcome::Failure arriving after Esc-while-submitting logs at WARN before being dropped (a misconfigured [daemon.ack] api_key cannot stay hidden in the operator's logs), and AckOutcome::Success carries Option<HashMap<String, AckSource>> so a refetch failure (None) keeps the previous snapshot while a legitimate empty refetch (Some(empty)) clears it. AckSubmitPayload ships with a hand-written Debug that redacts the API key.
Allocation reductions on hot paths
strip_bidi_and_invisible now returns Cow<'_, str> with a probe-before-allocate check, saving an allocation per finding signature and per SARIF acknowledgment field on clean inputs (the common case). compute_signature is rebuilt around String::with_capacity plus push_str, dropping the 12-arg format! macro and a redundant replace allocation in sanitize_endpoint (which itself returns Cow now). The redundant-call detector indexes N+1 templates in a HashSet once before the per-group loop, swapping O(G * F) for O(G + F) per trace. endpoint_stats_to_per_endpoint_io_ops sorts borrowed (&str, &str) pairs so the comparator no longer walks freshly allocated Strings. top_offenders and the bench-latency sort use f64::total_cmp for stable ordering without the partial_cmp().unwrap_or(Equal) dance. parse_daemon_environment and SanitizerAwareMode::from_config use eq_ignore_ascii_case instead of allocating a fresh to_ascii_lowercase per call.
Added
- Mixed-content WARN at HTML render time when
--daemon-url http://...points at a non-loopback host. Loopback URLs (localhost,127.0.0.1,[::1]) are exempt because dev setups intentionally run the daemon on cleartext HTTP. - CORS wildcard plus ack
api_keyWARN at daemon startup when[daemon.cors] allowed_origins = ["*"]is combined with[daemon.ack] api_key. Whitelist explicit origins for production deployments. --auth-headerps-visibility WARN ontempoandjaeger-query, mirroring the existing nudge onpg-stat. Operators are pointed at--auth-header-env.- 1 KiB cap on
ack createstdin signature read and on the interactive API-key prompt. AckOutcome,AckSubmitPayload,AckSubmitOptypes incrates/sentinel-cli/src/tui.rsenabling the spawn-and-drain pattern.AckSubmitPayloadships a hand-writtenDebugthat redacts the API key.- Refetch failure semantics carried through
AckOutcome::Success { refreshed_acks: Option<HashMap<...>> }.Nonemeans refetch failed, keep the previous snapshot.Some(empty)means a legitimate empty refetch, clear the map.
Changed
- CLI write paths use
O_NOFOLLOWon Unix for the HTML report, the calibration TOML, and the diff--outputfile. validate_http_endpoint(Tempo, Jaeger query) andvalidate_prometheus_endpoint(pg-stat) reject ASCII control characters before reachinghyper::Uri.- CLI errors sanitize signatures, daemon URLs, and daemon-supplied bodies through
text_safety::sanitize_for_terminalconsistently. - Daemon ack store parent directory tightened to
0700on Unix when the default storage path is created. rewrite_compactedre-checks the ack file for symlinks immediately before the rename, closing the long compaction window where a hostile local user could otherwise plant a symlink between startup and swap.- CSP placeholder safety net is now a plain
assert!instead ofdebug_assert!, so the guard survives release builds. .gitleaks.tomlfinding-signature regex tightened to the actualsanitize_endpointoutput charset.- TUI
submit_ack_modalis now non-blocking. ThreeHandle::current().block_on(...)calls migrated to a singleHandle::current().spawn(execute_ack_submit(...))plus atokio::sync::mpsc::UnboundedSender<AckOutcome>channel that the syncrun_loopdrains before each redraw.event::poll(50ms)is used only while a write is in flight, idle still blocks onevent::read(). - Held Enter on Submit no longer spawns duplicates thanks to a
submitting: boolgate at the top ofsubmit_ack_modal. AnAckOutcome::Failurearriving after Esc-while-submitting logs at WARN before being dropped.
Performance
- Probe-before-allocate
strip_bidi_and_invisiblereturnsCow<'_, str>, saving an allocation per finding signature and per SARIF acknowledgment field on clean inputs. compute_signaturebuilds viaString::with_capacitypluspush_strinstead of a 12-argformat!.sanitize_endpointreturnsCow<'_, str>so a clean endpoint pays zero copy.- Redundant detection indexes N+1 templates in a
HashSetonce before the group loop, swappingO(G * F)forO(G + F)per trace. endpoint_stats_to_per_endpoint_io_opssorts borrowed(&str, &str)pairs, so the comparator no longer walks freshly allocatedStrings.top_offendersand bench latency sort usef64::total_cmpfor stable ordering.parse_daemon_environmentandSanitizerAwareMode::from_configuseeq_ignore_ascii_case, dropping ato_ascii_lowercaseallocation per call.
Internal
parse_scraper_auth_headerreturnsScraperAuthOutcome::{None, Some, Invalid}instead ofResult<Option<_>, ()>, sidestepping theclippy::option-optionandclippy::result-unit-errlints.MAX_SIGNATURE_LENis nowpub const(#[doc(hidden)]) so the CLI can read the daemon's per-signature byte cap without forking the constant.PROMETHEUS_SCRAPE_FLOOR,resolve_auth_header_or_exit,AckSubmitError,post_ack_via_daemon,delete_ack_via_daemon,decode_body_messageandFINDINGS_FETCH_LIMITare correctly feature-gated, eliminating dead-code warnings on--no-default-features --features daemonand similar matrices.
Notes
- No public Rust surface change. No new
pubAPI, no removedpubAPI. Any project depending onperf-sentinel-core0.5.26 builds against 0.5.27 with no source change. - No daemon HTTP API change. The query API and ack endpoints expose the exact same surface as 0.5.26. Only daemon-side WARN logs and CLI
--auth-headerUX changes are visible to operators. - Behavior is preserved for already-clean inputs. The hardening pass closes gaps that only fire on hostile or malformed input. A correctly configured deployment sees no behavior change beyond the new WARN events documented above.
Install
Pre-built static binaries are attached to this release for linux-amd64, linux-arm64, macos-arm64, and windows-amd64. Verify the SHA256 from SHA256SUMS.txt before extracting. Crate consumers can cargo install perf-sentinel --version 0.5.27 once the workflow finishes propagating.
Full Changelog: v0.5.26...v0.5.27