Skip to content

fix(router)!: prevent set from being forwarded to client#2686

Open
SkArchon wants to merge 14 commits intomainfrom
milinda/eng-8718-weird-response-header-forwarding-logic
Open

fix(router)!: prevent set from being forwarded to client#2686
SkArchon wants to merge 14 commits intomainfrom
milinda/eng-8718-weird-response-header-forwarding-logic

Conversation

@SkArchon
Copy link
Copy Markdown
Contributor

@SkArchon SkArchon commented Mar 24, 2026

This PR refactors the response side of header forwarding

Warning

THIS IS A BREAKING CHANGE

Which includes

  • "set" is now not propagated to the client, instead the goal of set is to add a header to the response from the subgraph, so it looks like the set header "came from the subgraph directly".

  • Since "set" is now acts like a header that came directly from the subgraph, if users want to they "technically" can add a propagate op to send this header to the client. Though this is still not recommended (mentioned in the docs), as the same limitations of how set was before applies. (Limitations such as if no subgraph runs nothing will get propagated).

  • Previously if a user uses "set" to set a Cache-Control header, this would hard override any values set from the cache control configuration without looking at what is the most restrictive configration. This has been removed. Since "set" now acts like a header sent directly from the subgraph, it would behave the same way as it would for an actual Cache Control header that was sent from a subgraph.

Why these changes?

  • Currently "set" returns values to the client by default, but the API for this is highly confusing, this is as "set" is interpreted as "set" a header to the response from the subgraph. It is also not document correctly / clearly. This also leads to weird and potentially unexpected behaviour

For example

 headers:
  subgraphs:
    products:
      response:
        - op: "set"
          name: "X-Powered-By2232"
          value: "wundergraph"

The header would be sent to the client ONLY IF the products subgraph was called. Likewise when using headers.all for the same, in case of errors the client header is not set. This PR's goal is to realign set to mean "set (or add) a header to the subgraph response headers".

If user's still want to do this they can use the following alternative (currently only supports expressions).

headers:
  router:
    response:
      - name: X-Demo-Header
        # The expression can also be dynamic and not just a static value
        expression: "'static-value'"
  • Cache Control override
    Previously a user was allowed to use the "set" header to do a hard override on the Cache-Control header. Usually we would only pick the most restrictive cache control header, however upon usage of set, whatever was provided would be overridden. After discussing internally we decided to not allow users a hard override for Cache-Control headers, as it could be a potential security issue (i.e. :- When a subgraph wants to expire its data, it's not expired because the "set" override set a higher ttl, and we don't use the lower ttl because of the "set" override for the header)

Summary by CodeRabbit

  • Documentation
    • Clarified response header rule semantics and execution order; added guidance and examples showing how injected headers affect processing.
  • Bug Fixes / Behavior
    • Response-header "set" operations are internal-only (not forwarded) unless explicitly followed by a "propagate".
    • Injected Cache-Control values now feed into the restrictive cache-selection algorithm so per-subgraph injections influence final client cache headers.

Checklist

Open Source AI Manifesto

This project follows the principles of the Open Source AI Manifesto. Please ensure your contribution aligns with its principles.

@mintlify
Copy link
Copy Markdown
Contributor

mintlify Bot commented Mar 24, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
wundergraphinc 🟢 Ready View Preview Mar 24, 2026, 6:11 PM

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 24, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Refactors response header rule execution so set injects values into subgraph responses for internal processing (including feeding the restrictive cache-control algorithm) rather than forwarding them to clients. Introduces post-response rule collections, updates HeaderPropagation APIs, and aligns docs and tests with the new semantics.

Changes

Cohort / File(s) Summary
Documentation
docs-website/router/proxy-capabilities/adjusting-cache-control.mdx, docs-website/router/proxy-capabilities/subgraph-response-header-operations.mdx
Rename and reword sections to state set injects headers into subgraph responses (internal-only), add execution order, set config docs, and examples showing Cache-Control injection for the restrictive algorithm.
Core header rule engine
router/core/header_rule_engine.go
Remove responseHeaderPropagation.setCacheControl early-return logic; add PostResponseRules type and plumbing; change NewHeaderPropagation signature to accept post-response rules; move cache-control rule creation into post-response rules; make Set write into subgraph http.Response headers.
Header rule engine tests
router/core/header_rule_engine_test.go, router/core/header_rule_engine_buildheader_test.go
Replace/Add tests for CreateCacheControlPolicyHeaderRules; verify Set writes to subgraph responses; update all NewHeaderPropagation constructor call sites to pass additional postRules argument (nil in tests).
Router wiring / integration
router/core/router.go, router/core/graph_server.go
Refactor NewRouter to create postRules via CreateCacheControlPolicyHeaderRules and pass into NewHeaderPropagation; change EnableResponseHeaderPropagation check to use headerPropagation.HasResponseRules().
Protocol & singleflight tests
router-tests/protocol/header_propagation_test.go, router-tests/protocol/header_set_test.go, router-tests/protocol/header_propagation_race_test.go, router-tests/operations/singleflight_test.go
Rename subtests to indicate set is internal-only; update assertions so set-configured headers are not forwarded to client (expect empty), add tests for set+propagate forwarding and set feeding cache-control algorithm.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and concisely describes the primary change: preventing the 'set' operation from being forwarded to the client, which aligns with the core objective across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 24, 2026

Router image scan passed

✅ No security vulnerabilities found in image:

ghcr.io/wundergraph/cosmo/router:sha-469ab9b80856fe199f351d06a5bf64b6647daaef

@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 24, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 65.88%. Comparing base (a73636b) to head (282e6cc).
⚠️ Report is 17 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2686      +/-   ##
==========================================
+ Coverage   59.41%   65.88%   +6.46%     
==========================================
  Files         238      254      +16     
  Lines       25867    26449     +582     
==========================================
+ Hits        15370    17427    +2057     
+ Misses       8994     7636    -1358     
+ Partials     1503     1386     -117     
Files with missing lines Coverage Δ
router/core/graph_server.go 85.05% <100.00%> (+2.38%) ⬆️
router/core/header_rule_engine.go 89.98% <100.00%> (+5.92%) ⬆️
router/core/router.go 70.07% <100.00%> (+1.26%) ⬆️

... and 124 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@docs-website/router/proxy-capabilities/subgraph-response-header-operations.mdx`:
- Line 67: The sentence describing the `set` operation overpromises reuse —
update the wording so it clarifies that only `Cache-Control` values stored via
the `set` operation are reused by the router, while other headers set this way
are neither forwarded to the client nor reused later; reference the operations
`set` and `propagate` and the header `Cache-Control` when adjusting the sentence
on that line.

In `@router/core/header_rule_engine.go`:
- Around line 460-464: The code checks rule.Name == cacheControlKey
case-sensitively, so canonicalize or perform a case-insensitive compare before
the Cache-Control special-case branch: replace the equality check with a
case-insensitive comparison (e.g., strings.EqualFold(rule.Name, cacheControlKey)
or compare strings.ToLower(rule.Name) to a lowercased cacheControlKey) in the
block that currently references rule.Name and cacheControlKey so
propagation.setCacheControl is correctly set when header names differ only by
case; keep the existing propagation.header.Set(rule.Name, rule.Value) behavior
unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8e257883-8e4d-4cbe-a0f7-e7a56d4e36b2

📥 Commits

Reviewing files that changed from the base of the PR and between d413a26 and 79ae5b1.

📒 Files selected for processing (6)
  • docs-website/router/proxy-capabilities/adjusting-cache-control.mdx
  • docs-website/router/proxy-capabilities/subgraph-response-header-operations.mdx
  • router-tests/protocol/header_propagation_race_test.go
  • router-tests/protocol/header_set_test.go
  • router/core/header_rule_engine.go
  • router/core/header_rule_engine_test.go

Comment thread docs-website/router/proxy-capabilities/subgraph-response-header-operations.mdx Outdated
Comment thread router/core/header_rule_engine.go Outdated
@SkArchon SkArchon force-pushed the milinda/eng-8718-weird-response-header-forwarding-logic branch from fcce914 to 874bce1 Compare March 26, 2026 18:06
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs-website/router/proxy-capabilities/adjusting-cache-control.mdx`:
- Around line 157-165: The YAML example under cache_control_policy is invalid:
the list item is incorrectly indented under value and the subgraphs key is
missing; update the block for the cache_control_policy config (keys:
cache_control_policy, enabled, value) by adding a top-level subgraphs: key and
dedenting the list so the - name: "specific-subgraph" and its value:
"max-age=5400, public" are entries under subgraphs, producing valid YAML that
readers can copy/paste.

In `@router/core/header_rule_engine_test.go`:
- Around line 137-161: The test incorrectly expects subgraph-specific cache
rules to be appended to HeaderRules.AfterSubgraphResponse; update the assertions
in the AddCacheControlPolicyToRules tests so the global policy is asserted only
against result.AfterSubgraphResponse (e.g., check length 1 and Default ==
"max-age=300") and assert the subgraph-specific rule is checked via
result.Subgraphs["sg1"].Response (Default == "max-age=60"), referencing
AddCacheControlPolicyToRules, config.CacheControlPolicy and
HeaderRules.AfterSubgraphResponse/Subgraphs to locate the code to change.

In `@router/core/header_rule_engine.go`:
- Around line 470-475: The current HeaderRuleOperationSet branch writes
synthetic values directly into res.Header (res.Header.Set(rule.Name,
rule.Value)), which allows later propagate rules to see and export them;
instead, stop mutating res.Header for synthetic “set” operations and store these
values in a separate internal map on the response (e.g., res.syntheticSetHeaders
or similar) so they are available to downstream rule logic that needs them but
are not considered real response headers for propagation (also update
propagate/propagation handling to read only real res.Header for
propagation.header and consult the new synthetic map only where appropriate).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 23accb33-dfce-4949-be4d-fed78b77036b

📥 Commits

Reviewing files that changed from the base of the PR and between fcce914 and 874bce1.

📒 Files selected for processing (8)
  • docs-website/router/proxy-capabilities/adjusting-cache-control.mdx
  • docs-website/router/proxy-capabilities/subgraph-response-header-operations.mdx
  • router-tests/operations/singleflight_test.go
  • router-tests/protocol/header_propagation_test.go
  • router-tests/protocol/header_set_test.go
  • router/core/header_rule_engine.go
  • router/core/header_rule_engine_test.go
  • router/pkg/config/config.go
✅ Files skipped from review due to trivial changes (1)
  • docs-website/router/proxy-capabilities/subgraph-response-header-operations.mdx
🚧 Files skipped from review as they are similar to previous changes (3)
  • router-tests/operations/singleflight_test.go
  • router-tests/protocol/header_set_test.go
  • router-tests/protocol/header_propagation_test.go

Comment thread docs-website/router/proxy-capabilities/adjusting-cache-control.mdx
Comment thread router/core/header_rule_engine_test.go Outdated
Comment thread router/core/header_rule_engine.go
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
router/core/header_rule_engine.go (1)

207-207: Simplify redundant hasResponseRules check.

len(rhrrs) > 0 already includes postRules via getAllRules(), so the extra || postRules.hasRules() is unnecessary.

♻️ Suggested simplification
-	hf.hasResponseRules = len(rhrrs) > 0 || postRules.hasRules()
+	hf.hasResponseRules = len(rhrrs) > 0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@router/core/header_rule_engine.go` at line 207, The assignment to
hf.hasResponseRules is redundant because rhrrs (from getAllRules()) already
includes postRules; replace the current condition "len(rhrrs) > 0 ||
postRules.hasRules()" with a single check "len(rhrrs) > 0" so
hf.hasResponseRules is true only when the aggregated rule slice rhrrs is
non-empty; update the code where hf.hasResponseRules is set (the line
referencing hf.hasResponseRules, rhrrs, and postRules) to use only len(rhrrs) >
0.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@router/core/header_rule_engine.go`:
- Line 207: The assignment to hf.hasResponseRules is redundant because rhrrs
(from getAllRules()) already includes postRules; replace the current condition
"len(rhrrs) > 0 || postRules.hasRules()" with a single check "len(rhrrs) > 0" so
hf.hasResponseRules is true only when the aggregated rule slice rhrrs is
non-empty; update the code where hf.hasResponseRules is set (the line
referencing hf.hasResponseRules, rhrrs, and postRules) to use only len(rhrrs) >
0.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2df74a91-36d6-4264-870b-fd02a63e951a

📥 Commits

Reviewing files that changed from the base of the PR and between f886c7a and 6c6b23a.

📒 Files selected for processing (5)
  • docs-website/router/proxy-capabilities/adjusting-cache-control.mdx
  • router/core/header_rule_engine.go
  • router/core/header_rule_engine_buildheader_test.go
  • router/core/header_rule_engine_test.go
  • router/core/router.go
✅ Files skipped from review due to trivial changes (1)
  • docs-website/router/proxy-capabilities/adjusting-cache-control.mdx
🚧 Files skipped from review as they are similar to previous changes (1)
  • router/core/header_rule_engine_test.go

@SkArchon SkArchon marked this pull request as ready for review March 26, 2026 19:09
Comment thread docs-website/router/proxy-capabilities/adjusting-cache-control.mdx Outdated
Comment thread docs-website/router/proxy-capabilities/adjusting-cache-control.mdx Outdated
Comment thread docs-website/router/proxy-capabilities/subgraph-response-header-operations.mdx Outdated
Comment thread docs-website/router/proxy-capabilities/subgraph-response-header-operations.mdx Outdated
Comment thread router-tests/operations/singleflight_test.go
Comment thread docs-website/router/proxy-capabilities/subgraph-response-header-operations.mdx Outdated
@github-actions
Copy link
Copy Markdown

This PR was marked stale due to lack of activity. It will be closed in 14 days.

@github-actions github-actions Bot added Stale and removed Stale labels Apr 15, 2026
@SkArchon SkArchon changed the title fix: prevent set from being forwarded to client fix(breaking): prevent set from being forwarded to client Apr 23, 2026
@SkArchon SkArchon changed the title fix(breaking): prevent set from being forwarded to client fix(router)!: prevent set from being forwarded to client Apr 29, 2026
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.

2 participants