CSP Violation for internal Atlassian API links for issue icons

I’m attempting to implement an issue picker field in Forge using Jira API (/rest/api/3/issue/picker) within the Custom UI. The objective is to retrieve the icon image URL from Jira API which return this semi-url(/rest/api/2/universal_avatar/view/type/issuetype/avatar/10315?size=medium) and append my site URL to the image URL. However, utilizing this concatenated URL as the icon for the AsyncSelect option in my code results in a Content Security Policy (CSP) error violation.

To address the CSP error, I initially added *.atlassian.net to my manifest as an external source, which resolved the issue. Strangely, when I first attempted to create the issue picker using the API (/rest/api/3/search) and utilized the issuetype.iconUrl attribute, which provides the complete URL for the icon (e.g., https://my-instance-name.atlassian.net/rest/api/2/universal_avatar/view/type/issuetype/avatar/10315?size=medium), and passed it directly to the AsyncSelect option without manually concatenating the instance site, the CSP error violation did not occur.

I’m seeking insights into the potential cause of this discrepancy in CSP behavior.

const iconStyles = {
    singleValue: (provided, state) => {
      if (state?.data?.iconUrl) {
        return {
          ...provided,
          paddingLeft: "24px",
          backgroundImage: `url(${state.data.iconUrl})`,
          backgroundSize: "16px",
          backgroundRepeat: "no-repeat",
        };
      }

      return {
        ...provided,
      };
    },
    option: (provided, state) => {
      return {
        ...provided,
        paddingLeft: "36px",
        backgroundImage: `url(${state.data.iconUrl})`,
        backgroundSize: "16px",
        backgroundRepeat: "no-repeat",
        backgroundPosition: "8px 8px",
      };
    },
  };

  const issueOptions = useCallback(
    (inputValue) =>
      new Promise((resolve) => {
        const payload = {
          params: {
            query: inputValue,
          },
        };
        invoke("getIssues", payload).then((data) => {
          if ("data" in data) {
            data.data = JSON.parse(data.data);
          }
          resolve(
            data.data.sections.flatMap((section) =>
              section.issues.map((issue) => ({
                iconUrl: appContext.siteUrl + issue.img,
                label: `${issue.key} : ${issue.summaryText}`,
                value: issue.key,
              }))
            )
          );
        });
      }),
    []
  );

  const IssueSelectField = (
    <Field key="issue" name="issue" label="Select an issue ">
      {({ fieldProps: { onChange, ...rest } }) => (
        <AsyncSelect
          inputId={`async-select-issue`}
          cacheOptions
          defaultOptions
          styles={iconStyles}
          onChange={(value) => {
            onChange(value);
            setIssue(value);
          }}
          loadOptions={issueOptions}
          {...rest}
        />
      )}
    </Field>
  );    

2 Likes