githubcors

How to fetch a resource from a github release?


I have a public github repo with some climate data at https://github.com/garyo/sea-surface-temp-viz/releases. Each day a github action creates a new release with some assets.

I'd like to create a separate web app that uses those resources (png and json files) to do some further processing/visualization, but I'm getting trapped by CORS issues. I can download the files in the browser, but my web app front end refuses due to missing CORS headers.

I know I could upload the files somewhere else where I'm in control of the outgoing headers (make an S3 account with keys, create a bucket, upload in my action, bla bla bla), but they're already there, with perfectly fine github URLs. Seems like it shouldn't be so painful.

I even tried using the github API to find the release, get the asset's raw URL and fetch that, but it has the same CORS problem even though the doc says it should support CORS. I even tried a free CORS proxy but couldn't get it to work. Why would anyone care about the origin of a github release asset?

Here's some code I tried:

async function getGithubAssets() {
  const latestReleaseUrl = 'https://api.github.com/repos/garyo/sea-surface-temp-viz/releases/latest'
  const result = await fetch(latestReleaseUrl)
  const releaseData = await result.json()
  const assets = releaseData.assets // array of assets
  let sstTextureUrl = assets.find((elt: any) => elt.name == 'sst-temp-equirect.png').url
  let anomTextureUrl = assets.find((elt: any) => elt.name == 'sst-temp-anomaly-equirect.png').url
  let sstCmapUrl = assets.find((elt: any) => elt.name == 'sst-temp-equirect-cmap.json').url
  let anomCmapUrl = assets.find((elt: any) => elt.name == 'sst-temp-anomaly-equirect-cmap.json').url
  const fetch_opts = {
    headers: {
      'Accept': 'application/octet-stream'
    }
  }
  return {
    sstTexture: await (await fetch(sstTextureUrl, fetch_opts)).blob(),
    anomTexture: await (await fetch(anomTextureUrl, fetch_opts)).blob(),
    sstCmap: await (await fetch(sstCmapUrl, fetch_opts)).json(),
    anomCmap: await (await fetch(anomCmapUrl, fetch_opts)).json()
  }
}

Here's a ts playground to test it. Open dev tools, run it, you'll see the fetch errors.


Solution

  • The reason why you are getting CORS error is, first, looking at the raw asset link headers:

    curl -I https://github.com/garyo/sea-surface-temp-viz/releases/download/2025-01-08/sst-temp-equirect.png
    HTTP/2 302 
    server: GitHub.com
    date: Thu, 09 Jan 2025 08:20:32 GMT
    content-type: text/html; charset=utf-8
    vary: X-PJAX, X-PJAX-Container, Turbo-Visit, Turbo-Frame, Accept-Encoding, Accept, X-Requested-With
    location: https://objects.githubusercontent.com/github-production-release-asset-2e65be/754842033/36cb52c4-f6ef-482d-8932-19c415a20442?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=releaseassetproduction%2F20250109%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250109T082032Z&X-Amz-Expires=300&X-Amz-Signature=0e59c632ed3ade4f524024093b0da4dbae482e17ee929df931e30436afea042f&X-Amz-SignedHeaders=host&response-content-disposition=attachment%3B%20filename%3Dsst-temp-equirect.png&response-content-type=application%2Foctet-stream
    cache-control: no-cache
    strict-transport-security: max-age=31536000; includeSubdomains; preload
    x-frame-options: deny
    x-content-type-options: nosniff
    x-xss-protection: 0
    referrer-policy: no-referrer-when-downgrade
    

    It is responding with a redirect status code, which will fail the preflight request (OPTIONS), since it doesn't support redirect.

    Second, even if we succeed in doing the redirect, when trying to fetch the actual link, it does not have the appropriate CORS headers (Access-Control-Allow-Origin).

    curl -I 'https://objects.githubusercontent.com/github-production-release-asset-2e65be/754842033/36cb52c4-f6ef-482d-8932-19c415a20442?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=releaseassetproduction%2F20250109%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250109T082032Z&X-Amz-Expires=300&X-Amz-Signature=0e59c632ed3ade4f524024093b0da4dbae482e17ee929df931e30436afea042f&X-Amz-SignedHeaders=host&response-content-disposition=attachment%3B%20filename%3Dsst-temp-equirect.png&response-content-type=application%2Foctet-stream'
    HTTP/2 200 
    content-type: application/octet-stream
    last-modified: Wed, 08 Jan 2025 13:22:23 GMT
    etag: "0x8DD2FE77CD40722"
    server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
    x-ms-request-id: 3307a9e6-e01e-006f-44d1-61eba6000000
    x-ms-version: 2024-11-04
    x-ms-creation-time: Wed, 08 Jan 2025 13:22:23 GMT
    x-ms-blob-content-md5: Bl11+QKpG+0BIBUDz9KupQ==
    x-ms-lease-status: unlocked
    x-ms-lease-state: available
    x-ms-blob-type: BlockBlob
    content-disposition: attachment; filename=sst-temp-equirect.png
    x-ms-server-encrypted: true
    via: 1.1 varnish, 1.1 varnish
    fastly-restarts: 1
    accept-ranges: bytes
    age: 1949
    date: Thu, 09 Jan 2025 08:22:23 GMT
    x-served-by: cache-iad-kjyo7100150-IAD, cache-qpg120088-QPG
    x-cache: HIT, HIT
    x-cache-hits: 0, 0
    x-timer: S1736410943.602729,VS0,VE1
    content-length: 354996
    

    Here are your options

    Use a backend to fetch the content

    If you have a running server/backend, instead of your frontend fetching directly to GitHub, you can instead request to your backend. Then in the backend, you can have an endpoint for fetching the GitHub assets. For example when using Express:

    app.get('/asset', (req, res) => {
      const response = await fetch('https://github.com/garyo/sea-surface-temp-viz/releases/download/2025-01-08/sst-temp-equirect.png');
      const contentType = response.headers.get('content-type');
      const buffer = await response.buffer();
    
      res.set('Content-Type', contentType);
      res.send(buffer);
    });
    

    (Of course if you use other backend, you need to adapt the code, but the idea is still the same)

    Use a CORS Proxy

    You mentioned CORS proxy, and you are on the right track, especially if you don't have a backend. You can either use a self-hosted (cors-anywhere) or a hosted CORS proxy (Corsfix). Typically for a CORS proxy you will add the proxy URL before your target URL.

    Example

    fetch("https://proxy.corsfix.com/?https://github.com/garyo/sea-surface-temp-viz/releases/download/2025-01-08/sst-temp-equirect.png")
    

    The CORS proxy will handle the redirect and add the appropriate CORS headers so you don't get the CORS errors when trying to make the request from your frontend.


    You also mentioned using S3 to essentially mirror your GitHub release files, and that could work too. Since you will have control to the CORS headers in S3. I checked your code, and find that this is the route you went through. I'm still sharing my answer in case you are considering other available options.

    (I am affiliated with Corsfix)