Skip to content

Fix GraphiQL :simple interface to send headers from the Headers panel#308

Open
nallwhy wants to merge 1 commit intoabsinthe-graphql:mainfrom
nallwhy:fix-graphiql-simple-headers
Open

Fix GraphiQL :simple interface to send headers from the Headers panel#308
nallwhy wants to merge 1 commit intoabsinthe-graphql:mainfrom
nallwhy:fix-graphiql-simple-headers

Conversation

@nallwhy
Copy link
Copy Markdown

@nallwhy nallwhy commented Feb 19, 2026

Problem

In the GraphiQL :simple interface, headers entered in the built-in Headers panel were not sent to the server. The original
AbsintheSocketGraphiql.createFetcher does not forward headers passed from the GraphiQL UI, so they were silently ignored.

Note: the :advanced interface (graphiql_workspace.html.eex) already had default_headers support.

Changes

  • Wrap the original fetcher with a custom fetcher that:
    • Delegates subscription queries to absintheFetcher (WebSocket)
    • Handles query/mutation via native fetch(), merging headers
      from the GraphiQL Headers panel into the request
  • Add default_headers support to the :simple interface, consistent with the :advanced interface

Copilot AI review requested due to automatic review settings February 19, 2026 01:19
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes the :simple GraphiQL interface so headers entered in the built-in Headers panel (and configured default_headers) are actually included in requests, aligning behavior with the :advanced interface.

Changes:

  • Wraps the original Absinthe Socket fetcher with a custom fetcher that forwards UI-provided headers for HTTP query/mutation operations.
  • Preserves WebSocket handling for subscription operations via the original Absinthe fetcher.
  • Adds default_headers rendering/support to the :simple GraphiQL template.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
lib/absinthe/plug/graphiql/graphiql.html.eex Adds custom fetcher logic to merge header-editor headers into HTTP requests and initializes the header editor with default_headers.
lib/absinthe/plug/graphiql.ex Threads default_headers through the :simple template render function (updates template function arity/call).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 346 to 351
opts[:query],
opts[:var_string],
opts[:result],
opts[:default_headers],
opts[:socket_url],
opts[:assets]
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

:simple interface now renders and enables the Headers editor and threads default_headers through to the template, but the existing test suite assertions for default_headers only cover the :advanced workspace output (defaultHeaders: ...). Add a test that renders with interface: :simple and asserts the HTML includes the serialized default headers (and that header editor is enabled), to prevent regressions.

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +76
var query = (graphQLParams.query || '').trim();
if (/^subscription\b/i.test(query)) {
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

Subscription detection uses /^subscription\b/i on the trimmed document. This fails for valid subscription documents that begin with comments or fragment definitions (e.g., fragment ... then subscription ...), causing the operation to be sent over HTTP instead of the WebSocket fetcher. Consider determining the operation type via a GraphQL parser (preferred) or at least expanding the regex to skip leading comments/whitespace and fragment definitions before checking for subscription.

Suggested change
var query = (graphQLParams.query || '').trim();
if (/^subscription\b/i.test(query)) {
var query = graphQLParams.query || '';
var isSubscription = /^(?:\s*(?:#.*\n|fragment\s+[A-Za-z0-9_]+\s+on\s+[A-Za-z0-9_]+\s*\{[\s\S]*?\}\s*))*\s*subscription\b/i.test(query);
if (isSubscription) {

Copilot uses AI. Check for mistakes.
Comment on lines 119 to 121
query: '<%= query_string %>',
response: '<%= result_string %>',
variables: '<%= variables_string %>',
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

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

query_string, result_string, and variables_string are interpolated directly into a JavaScript string literal inside a <script> tag with only minimal escaping, which does not prevent </script> or other HTML-significant sequences from being injected. If an attacker can influence GraphQL queries/variables or any string data returned in the GraphQL result (for example via database-backed fields), they can craft values containing </script><script>... to prematurely terminate this script block and execute arbitrary JavaScript in the context of the GraphiQL origin. These values should be passed through a JS/HTML-safe encoder that also escapes </script>/</& (or embedded via a data attribute/JSON script tag and read at runtime) before being written into the script block.

Suggested change
query: '<%= query_string %>',
response: '<%= result_string %>',
variables: '<%= variables_string %>',
query: <%= query_string |> Jason.encode!() |> String.replace("<", "\\u003C") |> String.replace("&", "\\u0026") %>,
response: <%= result_string |> Jason.encode!() |> String.replace("<", "\\u003C") |> String.replace("&", "\\u0026") %>,
variables: <%= variables_string |> Jason.encode!() |> String.replace("<", "\\u003C") |> String.replace("&", "\\u0026") %>,

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants