fix(captain): send custom tool API key headers (#15008)

Captain custom tools configured with API key authentication now send the
configured key in the requested HTTP header. Existing tools begin
working without needing to be recreated or reconfigured, while
credentials remain protected across redirects.

## How to reproduce

1. Create a Captain custom tool using API Key authentication.
2. Configure `X-API-Key` as the header name and save the tool.
3. Invoke the tool and inspect the incoming request.
4. Before this change, the API key header is absent; after this change,
the configured endpoint receives it.

## What changed

The UI persists API key authentication as `name` and `key`, but the
request builder also required an unused `location: header` property. The
request builder now treats API key authentication as header-based,
matching the only mode exposed by the UI.

Custom authentication headers are also registered as sensitive with
`SafeFetch`. They are retained for the configured endpoint and
same-origin redirects, but stripped when a redirect crosses origins to
prevent credential leakage. Factory, request, and redirect specs cover
the real UI payload and both public and private-network fetch paths.
This commit is contained in:
Aakash Bakhle
2026-07-15 22:43:30 +05:30
committed by GitHub
parent 6d38b4d39c
commit 9c68eed676
7 changed files with 102 additions and 24 deletions

View File

@@ -6,6 +6,7 @@ class SafeFetch::RequestOptions
open_timeout: SafeFetch::DEFAULT_OPEN_TIMEOUT,
read_timeout: SafeFetch::DEFAULT_READ_TIMEOUT,
headers: nil,
sensitive_headers: [],
http_basic_authentication: nil,
allowed_content_type_prefixes: SafeFetch::DEFAULT_ALLOWED_CONTENT_TYPE_PREFIXES,
allowed_content_types: SafeFetch::DEFAULT_ALLOWED_CONTENT_TYPES,
@@ -13,7 +14,7 @@ class SafeFetch::RequestOptions
}.freeze
attr_reader :allowed_content_type_prefixes, :allowed_content_types, :body, :headers,
:http_basic_authentication, :method, :open_timeout, :read_timeout, :uri, :url
:http_basic_authentication, :method, :open_timeout, :read_timeout, :sensitive_headers, :uri, :url
def initialize(url:, **options)
config = DEFAULTS.merge(options)
@@ -25,6 +26,7 @@ class SafeFetch::RequestOptions
@open_timeout = config[:open_timeout]
@read_timeout = config[:read_timeout]
@headers = normalize_headers(config[:headers])
@sensitive_headers = normalize_sensitive_headers(config[:sensitive_headers])
@http_basic_authentication = config[:http_basic_authentication]
@allowed_content_type_prefixes = Array(config[:allowed_content_type_prefixes])
@allowed_content_types = Array(config[:allowed_content_types])
@@ -84,6 +86,10 @@ class SafeFetch::RequestOptions
value&.to_h
end
def normalize_sensitive_headers(value)
(SafeFetch::DEFAULT_SENSITIVE_HEADERS + Array(value)).map { |header| header.to_s.downcase }.uniq
end
def request_proc
proc do |request|
credentials = http_basic_authentication.presence || basic_authentication_for(request.uri)
@@ -91,10 +97,6 @@ class SafeFetch::RequestOptions
end
end
def sensitive_headers
SafeFetch::DEFAULT_SENSITIVE_HEADERS
end
def basic_authentication_for(request_uri)
uri_basic_authentication(request_uri) || original_uri_basic_authentication(request_uri)
end