Skip to content

CAMEL-23588: camel-undertow - extend UndertowHeaderFilterStrategy to filter the legacy websocket.* exchange-header prefix#23522

Open
oscerd wants to merge 1 commit into
apache:mainfrom
oscerd:fix/CAMEL-23588
Open

CAMEL-23588: camel-undertow - extend UndertowHeaderFilterStrategy to filter the legacy websocket.* exchange-header prefix#23522
oscerd wants to merge 1 commit into
apache:mainfrom
oscerd:fix/CAMEL-23588

Conversation

@oscerd
Copy link
Copy Markdown
Contributor

@oscerd oscerd commented May 26, 2026

Summary

Adds "websocket." to both setOutFilterStartsWith and setInFilterStartsWith
on UndertowHeaderFilterStrategy, in addition to the existing Camel* /
camel* prefixes inherited from HttpHeaderFilterStrategy. This follows the
dedicated-HeaderFilterStrategy fix shape used by CAMEL-23532 for
camel-vertx-websocket / camel-atmosphere-websocket / camel-iggy and is
the camel-undertow follow-on called out by CAMEL-23577.

Why not rename the constants

The constants in UndertowConstants (CONNECTION_KEY, CONNECTION_KEY_LIST,
SEND_TO_ALL, EVENT_TYPE, EVENT_TYPE_ENUM, CHANNEL, EXCHANGE) keep
their existing string values (websocket.connectionKey,
websocket.connectionKey.list, websocket.sendToAll, ...) because they are
part of undertow's externally-visible API contract — they appear in the
component docs and route examples. The CAMEL-23577 epic explicitly cites
websocket.connectionKey as the canonical case for the
dedicated-filter-strategy shape rather than the rename shape.

Behaviour change (transport-boundary only)

  • Outbound (exchange → wire): exchange headers whose name starts with
    websocket. are no longer propagated onto the outbound HTTP/websocket
    request as wire-level headers.
  • Inbound (wire → exchange): wire-level headers whose name starts with
    websocket. are no longer mapped into the resulting Camel exchange.

The undertow consumer's programmatic setHeader(CONNECTION_KEY, ...) and the
producer's in.getHeader(CONNECTION_KEY, ...) operate on the exchange
directly and are unaffected by the filter strategy.

Residual gap and defence-in-depth recommendation

The HeaderFilterStrategy only governs the transport boundary; it does not
prevent cross-component header injection (for example,
from("jetty:...").to("undertow:ws://...") — an attacker-supplied
websocket.connectionKey HTTP header is mapped into the exchange by jetty and
then read by the undertow producer to target a specific peer). The
upgrade-guide entry documents the residual gap and recommends
.removeHeaders("websocket.*") at trust boundaries as the
defence-in-depth pattern:

from("jetty:http://0.0.0.0:8080/api")
    .removeHeaders("websocket.*")
    .to("undertow:ws://internal-broker/notifications");

Opt-out

Routes that intentionally relied on undertow mapping websocket.* wire
headers in or out can supply a custom headerFilterStrategy endpoint option
to restore the previous behaviour.

Backports

  • camel-4.18.xUndertowHeaderFilterStrategy has the same shape (extends
    HttpHeaderFilterStrategy); backport applies cleanly.
  • camel-4.14.xUndertowHeaderFilterStrategy extends
    DefaultHeaderFilterStrategy (not HttpHeaderFilterStrategy) and already
    sets CAMEL_FILTER_STARTS_WITH explicitly; backport needs a small
    adaptation. Will be filed as follow-up PR.

Test plan

  • mvn test in components/camel-undertow — 161 tests pass (1 skipped)
  • Diff vs origin/main is purely additive (65 insertions, 0 deletions)
  • Upgrade guide entry added under === camel-undertow - potential breaking change documenting the behaviour change, the cross-component-injection
    residual gap, and the recommended .removeHeaders("websocket.*")
    mitigation

Tracker: CAMEL-23577

Reported by Claude Code on behalf of Andrea Cosentino

@oscerd oscerd requested review from davsclaus and gnodet May 26, 2026 08:49
@github-actions
Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 26, 2026

🧪 CI tested the following changed modules:

  • components/camel-undertow
  • docs

ℹ️ Dependent modules were not tested because the total number of affected modules exceeded the threshold (50). Use the test-dependents label to force testing all dependents.

Build reactor — dependencies compiled but only changed modules were tested (2 modules)
  • Camel :: Docs
  • Camel :: Undertow

⚙️ View full build and test results

@apupier
Copy link
Copy Markdown
Contributor

apupier commented May 26, 2026

ERROR] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 10.03 s <<< FAILURE! -- in org.apache.camel.component.undertow.ws.UndertowWsConsumerRouteTest
[ERROR] org.apache.camel.component.undertow.ws.UndertowWsConsumerRouteTest.echo -- Time elapsed: 10.03 s <<< FAILURE!
org.opentest4j.AssertionFailedError: expected: <true> but was: <false>
	at org.junit.jupiter.api.Assertions.assertTrue(Assertions.java:190)
	at org.apache.camel.component.undertow.ws.UndertowWsConsumerRouteTest.echo(UndertowWsConsumerRouteTest.java:173)

Copy link
Copy Markdown
Contributor

@apupier apupier left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Be careful, the test jobs failed to be queued and so the PR check seems green but the last build was red and there is no new build for trying the test

@oscerd
Copy link
Copy Markdown
Contributor Author

oscerd commented May 26, 2026

CI failure (build (17, false)) appears unrelated to this PR

The UndertowWsConsumerRouteTest.echo failure in run 26442254234 (3/3 surefire reruns, all expected: <true> but was: <false> at exactly 10.02 s — the wsclient1.await(10) timeout) does not appear to be caused by this PR's WEBSOCKET_FILTER_STARTS_WITH change.

Why the change is not in the failure path

  • UndertowConsumer.sendMessage sets CONNECTION_KEY via exchange.getIn().setHeader(...) (direct Message API).
  • UndertowProducer.processWebSocket reads CONNECTION_KEY via in.getHeader(...) (direct Message API).
  • The peer match uses peer.getAttribute(CONNECTION_KEY) on the WebSocketChannel, not a wire header.

None of these go through HeaderFilterStrategy, so adding "websocket." to setIn/OutFilterStartsWith cannot affect the echo flow at the transport boundary.

Likely root cause

The test was previously skipped on GitHub Actions via @DisabledIfSystemProperty(named = "ci.env.name", matches = ".*", disabledReason = "Flaky on GitHub Actions"). The annotation was removed by 685e1a4eca3 (CAMEL-22539, 2026-05-07) without other hardening of this specific test. This PR's CI run is among the first to actually exercise it on GHA, and the 10 s await timeout under a contended runner is the same failure shape that motivated the original disablement.

Local repro on fix/CAMEL-23588

Run against the PR branch with -Dci.env.name=github.com -Dsurefire.rerunFailingTestsCount=2:

  • 5/5 isolated UndertowWsConsumerRouteTest#echo runs PASS (~1.1 s each)
  • 3/3 full-class UndertowWsConsumerRouteTest runs PASS (~1.7 s each)

Suggested follow-ups (independent of this PR):

  • Rerun the build (17, false) job to confirm the flake.
  • Open a CAMEL Jira against camel-undertow for follow-up to CAMEL-22539 — either Awaitility-harden the test for slow GHA runners or restore @DisabledIfSystemProperty until it can be properly stabilized.

Copy link
Copy Markdown
Contributor

@gnodet gnodet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good defense-in-depth: extending UndertowHeaderFilterStrategy to filter the websocket.* prefix on both inbound and outbound. This prevents untrusted WebSocket clients from injecting headers that could be interpreted as configuration by downstream processors.

Adding WEBSOCKET_FILTER_STARTS_WITH = "websocket." to both in-filter and out-filter prefix lists is consistent with the existing pattern for Camel* and org.apache.camel.* prefixes. Upgrade guide with defense-in-depth guidance is well written.

LGTM.

Fully automatic review from Claude Code

…filter the legacy websocket.* exchange-header prefix

Adds "websocket." to both setOutFilterStartsWith and setInFilterStartsWith on
UndertowHeaderFilterStrategy, in addition to the existing Camel*/camel*
prefixes inherited from HttpHeaderFilterStrategy. This follows the
dedicated-HeaderFilterStrategy fix shape used by CAMEL-23532 for
camel-vertx-websocket / camel-atmosphere-websocket / camel-iggy and is the
camel-undertow follow-on called out by CAMEL-23577.

The constants in UndertowConstants (CONNECTION_KEY, CONNECTION_KEY_LIST,
SEND_TO_ALL, EVENT_TYPE, EVENT_TYPE_ENUM, CHANNEL, EXCHANGE) keep their
existing string values (websocket.connectionKey, websocket.connectionKey.list,
websocket.sendToAll, ...) because they are part of undertow's
externally-visible API contract; renaming them would break a documented
protocol surface that route authors and downstream consumers rely on.

The behaviour change applies only at undertow's transport boundary:
- Outbound (exchange -> wire): exchange headers whose name starts with
  websocket. are no longer propagated onto the outbound HTTP/websocket request
  as wire-level headers.
- Inbound (wire -> exchange): wire-level headers whose name starts with
  websocket. are no longer mapped into the resulting Camel exchange.

The undertow consumer's programmatic setHeader(CONNECTION_KEY, ...) and the
producer's in.getHeader(CONNECTION_KEY, ...) operate on the exchange directly
and are unaffected by the filter strategy. All 161 existing tests pass.

Note: the HeaderFilterStrategy does NOT prevent cross-component header
injection (e.g. http -> undertow with attacker-supplied websocket.connectionKey
in the HTTP request). The upgrade-guide entry documents the residual gap and
recommends .removeHeaders("websocket.*") at trust boundaries as the
defence-in-depth pattern.

Routes that intentionally relied on undertow mapping websocket.* wire headers
in or out can supply a custom headerFilterStrategy endpoint option to restore
the previous behaviour.

Tracker: CAMEL-23577

Reported by Claude Code on behalf of Andrea Cosentino

Signed-off-by: Andrea Cosentino <ancosen@gmail.com>
@oscerd oscerd force-pushed the fix/CAMEL-23588 branch from 9e54367 to 892b306 Compare May 27, 2026 07:25
@oscerd
Copy link
Copy Markdown
Contributor Author

oscerd commented May 27, 2026

@apupier — rebased onto current main and force-pushed to refresh CI: the previous Build and test workflow was stuck in queued since 2026-05-26 (never picked up by a runner), which masked the underlying stale-red build state you flagged. The new push (892b30639) should trigger a fresh workflow run.

Will re-request your review once the new Build and test reports a result.

Claude Code on behalf of Andrea Cosentino

@davsclaus
Copy link
Copy Markdown
Contributor

LGTM

@apupier
Copy link
Copy Markdown
Contributor

apupier commented May 28, 2026

so the new push retriggered a build (which is passing) but now there is a conflict to resolve

the old job is still stuck in the queue. I tried to cancel it but there is an error and i cannot. I hope this will be cleared when the PR will be merged

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants