What's new in v0.5.5
OTLP ingest hardening. Three independent bugs in v0.5.4 made the default OTel Collector setup harder than it should have been: framework-aware suggestions never fired on JPA, EF Core or Diesel stacks because perf-sentinel only read code.* from the I/O span itself; the OTLP/HTTP endpoint rejected the gzip-compressed exports the OTel Collector ships by default; and the new stable semconv v1.33.0 attribute names (code.function.name, code.file.path, code.line.number) were silently ignored. v0.5.5 fixes all three. As a side effect of the audit pass, the OTLP hot path also got a single-pass attribute classifier (~13x fewer key comparisons per span) and a wire-level cap on compressed bodies that bounds attacker decompression CPU.
After upgrading, an OTel Collector with default gzip works out of the box, JPA and Hibernate findings carry actionable suggested_fix again, and agents emitting either legacy or stable code.* are both supported.
Fixed
- Walk parent span chain to recover
code.*attributes. OTel auto-instrumentation typically attachescode.namespace,code.functionand friends to the user-frame span (controller, service), not to the inner JDBC or HTTP-client span. perf-sentinel previously read these only from the I/O span itself, soJAVA_RULES,CSHARP_RULESandRUST_RULESnever fired on stacks that delegate I/O to a driver. The walk is bounded at depth 8 to stay safe on malformed parent chains.suggested_fixnow appears on JPA, Hibernate, EF Core and Diesel findings even when the agent emits nothing on the leaf span. - Accept gzip-compressed OTLP/HTTP exports on
POST /v1/traces. The OTel Collector ships gzip by default, which previously triggered HTTP 400 on every export and forced users to setcompression: nonein the Collector config. perf-sentinel now wirestower-http'sRequestDecompressionLayeroutside the existingDefaultBodyLimit, soBytesextraction caps the decompressed payload at[daemon] max_payload_sizeand tower-http streams the decode with backpressure (no gzip-bomb amplification). Uncompressed clients keep working unchanged. The OTLP/gRPC path was already covered by tonic and is not affected.
Changed
- Read OpenTelemetry semconv v1.33.0 stable code attribute names (
code.function.name,code.file.path,code.line.number) with a fallback to the legacy names (code.function,code.filepath,code.lineno,code.namespace). When only the stable FQ function name is present, the namespace is derived by splitting on the last., which keepsJAVA_RULES's segment-anchored substring matching firing onorg.springframework.data.jpaand friends. An explicit legacycode.namespacealways wins over the derived value. The legacycode.function(documented as a bare function name) is deliberately not fed into the namespace derivation, to avoid surfacing false positives on agents that pack a dotted value into the legacy attribute. - Single-pass span attribute classifier on the OTLP hot path.
convert_spanpreviously ran ~14 separate linear scans over the attribute list (one perget_str_attributelookup). It now classifies the full set in a single iteration with amatchon the key. At typical 30-attribute HTTP spans the saving is ~13x fewer key comparisons per span. The parent walk forcode.*no longer re-scans the leaf span attributes, since classification already produced them.
Security
- Compressed body wire-cap on OTLP/HTTP. A new
RequestBodyLimitLayeris layered as the outermost middleware on/v1/traces, so the request flow is nowRequestBodyLimit (compressed wire bytes) -> RequestDecompression -> DefaultBodyLimit (decompressed bytes via the Bytes extractor) -> handler. WithContent-Lengthset, compressed bodies abovemax_payload_sizeget a clean 413 before any decompression CPU is spent. Closes the amplification path where a relaxedmax_payload_sizecould let an attacker burn ~100 ms of decompress CPU per request with a near-pathological compression ratio. - Drop
code.*span attributes containing control characters. ANSI escapes, NULs, newlines and other ASCII control bytes insidecode_function,code_filepathorcode_namespaceare now silently dropped at the canonicalsanitize_span_eventboundary, mirroring the existing posture forcloud.region. Closes a defense-in-depth gap where attacker-controlled ancestor spans could feed control bytes intocode.*(now read via the parent walk) and surface them in TUI/CLI output or tracing logs.
Docs
- OTLP/HTTP gzip support documented in
docs/INSTRUMENTATION.mdanddocs/FR/INSTRUMENTATION-FR.mdunder "Production: via OpenTelemetry Collector". Notes that perf-sentinel accepts gzip natively, nocompression: noneworkaround required, and that the decompressed body still respects[daemon] max_payload_size. - Helm chart 0.2.8 bumps
appVersionand the default daemon image tag to0.5.5.
Install
Prebuilt binaries (Linux amd64 / arm64, macOS arm64, Windows amd64):
curl -LO https://github.com/robintra/perf-sentinel/releases/download/v0.5.5/perf-sentinel-linux-amd64
chmod +x perf-sentinel-linux-amd64
sudo mv perf-sentinel-linux-amd64 /usr/local/bin/perf-sentinelLinux binaries are statically linked against musl and run on any distribution (Alpine, Debian, RHEL, Ubuntu any version) regardless of glibc version, and inside FROM scratch images.
From crates.io:
cargo install perf-sentinelDocker:
docker run --rm -p 4317:4317 -p 4318:4318 \
ghcr.io/robintra/perf-sentinel:0.5.5 watch --listen-address 0.0.0.0Also available on Docker Hub: robintrassard/perf-sentinel:0.5.5.
Helm (chart 0.2.8 ships 0.5.5 as its appVersion default):
helm install perf-sentinel oci://ghcr.io/robintra/charts/perf-sentinel \
--version 0.2.8 \
--namespace observability --create-namespaceVerify the binary against SHA256SUMS.txt:
curl -LO https://github.com/robintra/perf-sentinel/releases/download/v0.5.5/SHA256SUMS.txt
sha256sum -c SHA256SUMS.txt --ignore-missingFull diff: v0.5.4...v0.5.5