Skip to content

fix(integrations): AIMLAPI attribution sanitizing is undone by caller headers on the discovery path #2178

Description

@euxaristia

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions