github robintra/perf-sentinel v0.5.27

latest releases: chart-v0.2.63, v0.8.14, chart-v0.2.62...
one month ago

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.

Helm chart 0.2.30

Helm chart 0.2.30 ships in lockstep, bumping appVersion to 0.5.27 and the default daemon image tag to ghcr.io/robintra/perf-sentinel:0.5.27. The artifacthub.io/images annotation is updated in lockstep. No chart-level template change.

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_key WARN at daemon startup when [daemon.cors] allowed_origins = ["*"] is combined with [daemon.ack] api_key. Whitelist explicit origins for production deployments.
  • --auth-header ps-visibility WARN on tempo and jaeger-query, mirroring the existing nudge on pg-stat. Operators are pointed at --auth-header-env.
  • 1 KiB cap on ack create stdin signature read and on the interactive API-key prompt.
  • AckOutcome, AckSubmitPayload, AckSubmitOp types in crates/sentinel-cli/src/tui.rs enabling the spawn-and-drain pattern. AckSubmitPayload ships a hand-written Debug that redacts the API key.
  • Refetch failure semantics carried through AckOutcome::Success { refreshed_acks: Option<HashMap<...>> }. None means refetch failed, keep the previous snapshot. Some(empty) means a legitimate empty refetch, clear the map.

Changed

  • CLI write paths use O_NOFOLLOW on Unix for the HTML report, the calibration TOML, and the diff --output file.
  • validate_http_endpoint (Tempo, Jaeger query) and validate_prometheus_endpoint (pg-stat) reject ASCII control characters before reaching hyper::Uri.
  • CLI errors sanitize signatures, daemon URLs, and daemon-supplied bodies through text_safety::sanitize_for_terminal consistently.
  • Daemon ack store parent directory tightened to 0700 on Unix when the default storage path is created.
  • rewrite_compacted re-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 of debug_assert!, so the guard survives release builds.
  • .gitleaks.toml finding-signature regex tightened to the actual sanitize_endpoint output charset.
  • TUI submit_ack_modal is now non-blocking. Three Handle::current().block_on(...) calls migrated to a single Handle::current().spawn(execute_ack_submit(...)) plus a tokio::sync::mpsc::UnboundedSender<AckOutcome> channel that the sync run_loop drains before each redraw. event::poll(50ms) is used only while a write is in flight, idle still blocks on event::read().
  • Held Enter on Submit no longer spawns duplicates thanks to a submitting: bool gate at the top of submit_ack_modal. An AckOutcome::Failure arriving after Esc-while-submitting logs at WARN before being dropped.
  • Helm chart 0.2.29 to 0.2.30, appVersion 0.5.26 to 0.5.27. The artifacthub.io/images annotation is updated in lockstep. No chart template change.

Performance

  • Probe-before-allocate strip_bidi_and_invisible returns Cow<'_, str>, saving an allocation per finding signature and per SARIF acknowledgment field on clean inputs.
  • compute_signature builds via String::with_capacity plus push_str instead of a 12-arg format!. sanitize_endpoint returns Cow<'_, str> so a clean endpoint pays zero copy.
  • Redundant detection indexes N+1 templates in a HashSet once before the 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 bench latency sort use f64::total_cmp for stable ordering.
  • parse_daemon_environment and SanitizerAwareMode::from_config use eq_ignore_ascii_case, dropping a to_ascii_lowercase allocation per call.

Internal

  • parse_scraper_auth_header returns ScraperAuthOutcome::{None, Some, Invalid} instead of Result<Option<_>, ()>, sidestepping the clippy::option-option and clippy::result-unit-err lints.
  • MAX_SIGNATURE_LEN is now pub 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_message and FINDINGS_FETCH_LIMIT are correctly feature-gated, eliminating dead-code warnings on --no-default-features --features daemon and similar matrices.

Notes

  • No public Rust surface change. No new pub API, no removed pub API. Any project depending on perf-sentinel-core 0.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-header UX 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.

Helm operators bump to chart 0.2.30 for the matching appVersion, no values.yaml change required.

Full Changelog: v0.5.26...v0.5.27

Don't miss a new perf-sentinel release

NewReleases is sending notifications on new releases.