Summary
getRouteDiscoveryHeaders() in src/integrations/discoveryService.ts spreads caller-supplied headers after resolveAimlapiAttributionHeaders() has sanitized them, silently undoing that function's guarantees on the aimlapi model-discovery path.
Details
resolveAimlapiAttributionHeaders() (src/integrations/aimlapi/config.ts) enforces two rules: on the canonical endpoint it drops any incoming X-AIMLAPI-Partner-ID and re-stamps it via resolvePartnerId(); on a non-canonical base URL it strips both attribution headers.
getRouteDiscoveryHeaders() passes only descriptorHeaders through it, then spreads options.headers last — unsanitized, winning every collision:
const headers = {
...(routeId === 'aimlapi'
? resolveAimlapiAttributionHeaders(descriptorHeaders, getRouteBaseUrl(routeId, options))
: descriptorHeaders),
...(options?.headers ?? {}),
}
src/services/api/bootstrap.ts populates that from parseCustomHeadersEnv(process.env.ANTHROPIC_CUSTOM_HEADERS), so a caller-supplied partner ID overrides the resolved one, and caller-supplied attribution headers are re-added after the non-canonical filter removed them.
The comment directly above the code states the intent — "the /models request must be filtered on the same canonical predicate the inference shim uses ... Without this the discovery path would hand the partner identity to an arbitrary host" — so this reads as an oversight rather than deliberate precedence.
History
The ordering predates #2084; it arrived with the aimlapi foundation in #1995. #2084 then removed the gate that had limited it:
- ...(acceptsCallerHeaders ? (options?.headers ?? {}) : {}),
+ ...(options?.headers ?? {}),
Caller headers previously applied only when discovery.requiresAuth !== false; they now always apply, so routes with requiresAuth === false receive them where they previously did not. #2084 did not introduce the ordering flaw but did widen its reach — my PR, my omission.
Impact
Low. DEFAULT_PARTNER_ID is a hardcoded public constant, so nothing secret is exposed, and this is discovery rather than billable inference. The effect is that the discovery path does not honor its own documented invariant and diverges from the inference shim.
Suggested fix
Fold caller headers into the sanitizer's input, preserving existing precedence for non-aimlapi routes:
const callerHeaders = options?.headers ?? {}
const headers =
routeId === 'aimlapi'
? resolveAimlapiAttributionHeaders(
{ ...callerHeaders, ...descriptorHeaders },
getRouteBaseUrl(routeId, options),
)
: { ...descriptorHeaders, ...callerHeaders }
I have a patch with tests covering both branches and will open a PR referencing this issue.
Summary
getRouteDiscoveryHeaders()insrc/integrations/discoveryService.tsspreads caller-supplied headers afterresolveAimlapiAttributionHeaders()has sanitized them, silently undoing that function's guarantees on theaimlapimodel-discovery path.Details
resolveAimlapiAttributionHeaders()(src/integrations/aimlapi/config.ts) enforces two rules: on the canonical endpoint it drops any incomingX-AIMLAPI-Partner-IDand re-stamps it viaresolvePartnerId(); on a non-canonical base URL it strips both attribution headers.getRouteDiscoveryHeaders()passes onlydescriptorHeadersthrough it, then spreadsoptions.headerslast — unsanitized, winning every collision:src/services/api/bootstrap.tspopulates that fromparseCustomHeadersEnv(process.env.ANTHROPIC_CUSTOM_HEADERS), so a caller-supplied partner ID overrides the resolved one, and caller-supplied attribution headers are re-added after the non-canonical filter removed them.The comment directly above the code states the intent — "the
/modelsrequest must be filtered on the same canonical predicate the inference shim uses ... Without this the discovery path would hand the partner identity to an arbitrary host" — so this reads as an oversight rather than deliberate precedence.History
The ordering predates #2084; it arrived with the aimlapi foundation in #1995. #2084 then removed the gate that had limited it:
Caller headers previously applied only when
discovery.requiresAuth !== false; they now always apply, so routes withrequiresAuth === falsereceive them where they previously did not. #2084 did not introduce the ordering flaw but did widen its reach — my PR, my omission.Impact
Low.
DEFAULT_PARTNER_IDis a hardcoded public constant, so nothing secret is exposed, and this is discovery rather than billable inference. The effect is that the discovery path does not honor its own documented invariant and diverges from the inference shim.Suggested fix
Fold caller headers into the sanitizer's input, preserving existing precedence for non-
aimlapiroutes:I have a patch with tests covering both branches and will open a PR referencing this issue.