Read the profiler cookie by the name the documentation shows - #369
Merged
diolektor merged 1 commit intoSep 19, 2026
Merged
Conversation
Fix:
- Look the `OXPROF` cookie up by its whole name instead of through the per-plugin cookie namespace. That namespace admits only cookies prefixed `__oxp_profiler_` and hands them over with the prefix removed, so the only spelling that ever reached the trigger was `__oxp_profiler_OXPROF`, a name documented nowhere. The bare `OXPROF` printed in the README and on every page of the profiling documentation since 0.3.0 was filtered out before the profiler saw the request, so of the three explicit triggers the one recommended for debugging in a browser was the only one that could not fire, and an operator who set it from DevTools got a server that behaved exactly as if profiling were switched off. The namespaced spelling stops working, which costs nothing to anyone following the documentation and is pinned by a test so it is not restored by accident. The new read is `pub(crate)`: cookie isolation stays in force for everything built against the public plugin API, and the public `header()` still refuses `cookie`.
- Read every `Cookie` field line rather than the first. RFC 9113 section 8.2.3 lets an HTTP/2 client split its cookies across several field lines for HPACK efficiency and puts the job of joining them back together on the server; neither `h2` nor `hyper` does it, and `HeaderMap::get` returns only the head value. The server speaks h2 both by ALPN and by prior knowledge on a plaintext port, so without this the fix would still have missed the cookie in precisely the case it exists for — a browser over HTTP/2 — and the miss is indistinguishable from a disabled profiler. Verified against a live h2 connection: on the previous build two field lines gave `expected: true, actual: false`. `extract_plugin_cookies` and `strip_plugin_cookies` still read the first field line only, so this function sees cookies they do not; that gap predates this change and is not closed here.
Tests:
- Rebuild the trigger fixture on a real `Cookie` header. It used to construct `PluginCookies` directly, skipping the parse that contained the defect, which is why a dead trigger sat behind green unit tests; on the unfixed code the rewritten fixture turns five of them red. The suite probe now sends the documented spelling — sending the namespaced one made the only end-to-end coverage of this trigger a false green.
Docs:
- Drop the migration note promising that `runs_total{source="cookie"}` stays at zero, and record that this cookie, unlike an `__oxp_*` one, is not stripped: the token reaches the application, in `$_COOKIE` or in `$_SERVER['HTTP_COOKIE']` depending on how the client sent its cookies.
1716 unit tests, 25 profiler-profile tests.
diolektor
deleted the
fix/profiler-cookie-trigger-unreachable-under-plugin-prefix
branch
September 19, 2026 21:50
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.
What was wrong
Of the three explicit profiler triggers, the one the documentation recommends for debugging in a browser was the only one that could never fire.
trigger.rsasked for the cookie throughPluginRequestView::cookie, which reads the per-plugin cookie namespace. That namespace is built byextract_plugin_cookies, which keeps only cookies prefixed__oxp_profiler_and hands them over with the prefix removed. So the only spelling that ever reached the trigger was__oxp_profiler_OXPROF— a name documented nowhere. The bareOXPROFthat the README and every page of the profiling documentation have printed since 0.3.0 was filtered out before the profiler saw the request.From the outside this is indistinguishable from a server with profiling switched off: no error, no log line,
runs_total{source="cookie"}simply stays at zero. The end-to-end suite did cover the trigger, but it sent the namespaced spelling, so it was a false green on a dead path. The unit tests were green for the same reason from the other side: the fixture constructedPluginCookiesdirectly and never ran the parse that contained the defect.Why the bare name is canonical
The documented behaviour is the reasonable one; the implementation was the broken half. The bare name is what an operator reads and types, it is published in three places, and it has been there for five releases. The namespaced spelling is documented nowhere and used by nothing except our own suite line. So the code moves to the documentation, not the other way round.
Rejected: documenting the prefixed name (it makes the published examples wrong for every existing reader and exposes an internal naming convention as a public interface), and accepting both names (two spellings for one knob, one of which nobody can discover, with no way to ever retire it).
global_cookieispub(crate), deliberately. Cookie isolation stays fully in force for anything built against the public plugin API, and the publicheader()still refusescookie; the exception is available only to code in this crate, for a cookie whose whole name is part of an interface an operator types by hand.The HTTP/2 half
find_raw_cookiereads everyCookiefield line, not the first.RFC 9113 section 8.2.3 allows an HTTP/2 client to split its cookies across several
Cookiefield lines for better HPACK compression, and puts the job of joining them back with"; "on the server "before being passed into a non-HTTP/2 context, such as … a generic HTTP server application". Nothing below us does it:h2 0.4.17passes the field lines through as they arrived,hyper 1.10does not rejoin them, andHeaderMap::getreturns only the head value of a multi-valued entry. The server speaks h2 both by ALPN and by prior knowledge on a plaintext port.Without this, the fix would still have missed the cookie in precisely the case it exists for — a browser over HTTP/2 — and the miss would again have looked like a disabled profiler. Measured on a live h2 connection against the pre-fix build: cookies split across two field lines gave
expected: true, actual: false; after the fix one, two and three field lines all activate, in any order, and the namespaced spelling in a later field line stays inert.Scope boundary:
extract_plugin_cookiesandstrip_plugin_cookiesstill read the first field line only. That gap predates this change and is not closed here — it is tracked separately, because closing it touches the plugin cookie contract and the SAPI's$_COOKIEconstruction, both well outside this fix.Behaviour worth knowing before merging
This cookie, unlike an
__oxp_*one, is not stripped from the request. The token stays visible to the application being profiled and to anything that logs or forwards cookies — in$_COOKIEwhen the client sent its cookies on a single field line, and in$_SERVER['HTTP_COOKIE']when it split them, because those two superglobals are built from different field lines. That is measured, not inferred. The documentation already advises against putting a production token on this path; §5.2 now says why in one sentence, and the changelog entry repeats it.Proof
Cookieheader:left: None, right: Some("tok"), five tests red.profiler/test_source_cookiefails onis_active() === false.filter_map→map_whileon the unreadable-line skip. A third mutant showed atrim()call was doing nothing and it was removed.Verification
cargo fmt -- --check— cleancargo clippy --all-targets --no-default-features -- -D warnings— cleancargo test --no-default-features --features plugin-apm,plugin-async,plugin-profiler,plugin-shared— 1716 passed, 0 failedscripts/gen-llms-txt.sh --check— up to date (58 pages)./tests/run_all.sh --profile=profiler— 25/25