stablyai/orca · error · JiraApiError

Jira attachment URL must use the configured site origin.

Error message

Jira attachment URL must use the configured site origin.

What it means

Thrown by jiraRequestBinary when a fully-qualified attachment URL (or a relative path that resolves) has an origin different from the configured Jira site origin. This is a deliberate SSRF guard: Jira attachment metadata is provider-controlled, so forwarding Authorization credentials to an unexpected origin would leak them. It is a JiraApiError with status null.

Source

Thrown at src/main/jira/client.ts:505

  }
  if (response.status === 204) {
    return null as T
  }
  return (await response.json()) as T
}

export async function jiraRequestBinary(
  client: JiraClientForSite,
  pathOrUrl: string
): Promise<{ data: ArrayBuffer; contentType: string }> {
  const siteUrl = new URL(client.site.siteUrl)
  const requestUrl = /^https?:\/\//i.test(pathOrUrl)
    ? new URL(pathOrUrl)
    : new URL(`${client.site.siteUrl}${pathOrUrl}`)
  if (requestUrl.origin !== siteUrl.origin) {
    // Why: attachment metadata is provider-controlled; never forward Jira
    // credentials if a malformed response points at another origin.
    throw new JiraApiError('Jira attachment URL must use the configured site origin.', null)
  }
  const headers = new Headers()
  // Why: attachment content is binary; forcing JSON Accept/Content-Type can
  // break downloads and confuses some Atlassian edge responses.
  headers.set('Accept', '*/*')
  headers.set('User-Agent', JIRA_API_USER_AGENT)
  headers.set('Authorization', client.authorization)
  const response = await jiraFetch(requestUrl.toString(), { headers })
  if (!response.ok) {
    throw new JiraApiError(await readJiraError(response), response.status)
  }
  const contentType = response.headers.get('content-type') || 'application/octet-stream'
  return {
    data: await response.arrayBuffer(),
    contentType
  }
}

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Reconfigure the Jira site URL so its origin matches where attachments are actually served.
  2. Refetch the attachment metadata to get a fresh URL and retry.
  3. Do NOT bypass this guard by stripping Authorization; instead download the URL unauthenticated out-of-band if the alternate origin is trusted.
  4. Report the malformed attachment to the Jira admin.

Example fix

// before
const { data } = await jiraRequestBinary(client, attachment.content)
// after
const attachmentUrl = new URL(attachment.content)
if (attachmentUrl.origin !== new URL(client.site.siteUrl).origin) {
  await refreshAttachmentMetadata(attachment.id)
}
const { data } = await jiraRequestBinary(client, attachment.content)
Defensive patterns

Strategy: validation

Validate before calling

const attachmentUrl = new URL(pathOrUrl.startsWith('http') ? pathOrUrl : client.site.siteUrl + pathOrUrl)
if (attachmentUrl.origin !== new URL(client.site.siteUrl).origin) await refreshAttachmentMetadata()

Type guard

function isSameOrigin(clientUrl: string, candidate: string): boolean {
  return new URL(candidate).origin === new URL(clientUrl).origin
}

Try / catch

try { return await jiraRequestBinary(client, pathOrUrl) }
catch (e) {
  if (e instanceof JiraApiError && /configured site origin/.test(e.message)) { await refreshAttachmentMetadata(); return downloadUnauthenticated(pathOrUrl) }
  throw e
}

Prevention

When it happens

Trigger: A Jira attachment record returns a content URL on a different host (e.g. an Atlassian CDN domain not matching the site origin, a malformed/restaged attachment, or a malicious/corrupted response). Also if the user's siteUrl was changed to one origin while cached attachment URLs point at the old origin.

Common situations: Atlassian serving attachments from a different subdomain than the API site. Site URL reconfigured after attachments were cached. A proxy rewriting attachment hosts.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/ddcafdf8f2ef00b4. Report an issue: GitHub.