# 🛡️ Protect Your GitHub Token: Proxying Private Releases for Electron Apps

When building apps that use GitHub Releases to distribute updates (e.g., via Electron), things are straightforward if your repo is public. But for **private GitHub repos**, there's a problem:

> ⚠️ You can’t directly expose the GitHub Release URL to your app without leaking your GitHub token.

In this post, we’ll walk through how to securely **proxy GitHub Release downloads through your own server**, so that your token stays hidden while your app can still download updates.

---

## 🚩 Why You Can’t Use the GitHub URL Directly

GitHub requires authentication for private releases. That means you need to include an Authorization header with a personal access token (PAT):

```javascript
Authorization: token ghp_123abc...
```

Including this directly in your frontend or Electron app would be a **huge security risk**, since users could extract the token and access your private repo.

---

## ✅ The Secure Approach: Use a Proxy Server

Instead of downloading from GitHub directly, you set up a **backend server** that:

* Authenticates with GitHub using your token
    
* Downloads the release asset
    
* Proxies the file to the requesting client (your app)
    

This way, only your server knows the token — the app just talks to your proxy.

---

## 🛠 Step-by-Step: Proxy Setup

### 1\. **Create a GitHub Token**

* Go to [GitHub Developer Settings](https://github.com/settings/tokens)
    
* Create a **classic token** with:
    
    * `repo` scope (for private repositories)
        
    * `read:packages` (if you're using GitHub Packages too)
        

Save this token in your backend as an environment variable, e.g., `GH_TOKEN`.

---

### 2\. **Create the Proxy Endpoint**

Here’s a full example using Node.js (Express or similar):

```javascript
// downloadProxy.js
const fetch = require("node-fetch");

const GITHUB_OWNER = "your-org-or-username";
const GITHUB_REPO = "your-repo";
const GH_TOKEN = process.env.GH_TOKEN;

const headers = {
  Authorization: `token ${GH_TOKEN}`,
  "User-Agent": "SecureReleaseProxy",
  Accept: "application/vnd.github.v3+json"
};

async function getLatestRelease() {
  const res = await fetch(`https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/latest`, { headers });
  if (!res.ok) throw new Error("Failed to fetch latest release");
  return await res.json();
}

async function getAssetDownloadUrl(assetName) {
  const release = await getLatestRelease();
  const asset = release.assets.find(a => a.name === assetName);
  if (!asset) throw new Error(`Asset ${assetName} not found`);
  return asset.url; // This is the API URL, not a direct download link
}

async function proxyAssetDownload(req, res) {
  try {
    const { filename } = req.params;
    const assetApiUrl = await getAssetDownloadUrl(filename);

    const githubRes = await fetch(assetApiUrl, {
      headers: {
        ...headers,
        Accept: "application/octet-stream"
      }
    });

    if (!githubRes.ok) {
      res.status(500).send("Error fetching asset");
      return;
    }

    res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
    res.setHeader("Content-Type", "application/octet-stream");

    githubRes.body.pipe(res);
  } catch (err) {
    console.error("Proxy error:", err);
    res.status(500).send("Proxy error");
  }
}

module.exports = { proxyAssetDownload };
```

---

### 3\. **Add the Express Route**

In your main server file:

```javascript
const express = require("express");
const { proxyAssetDownload } = require("./downloadProxy");

const app = express();
const PORT = 3200;

app.get("/updates/download/:filename", proxyAssetDownload);

app.listen(PORT, () => {
  console.log(`Proxy server listening on http://localhost:${PORT}`);
});
```

---

### 4\. **Configure Electron Auto-Updater**

In your Electron app (using `electron-updater`), point to your proxy instead of GitHub:

```javascript
{
  "publish": [
    {
      "provider": "generic",
      "url": "http://localhost:3200/updates"
    }
  ]
}
```

Your `latest.yml` file should also have URLs that match your proxy endpoint:

```javascript
version: 1.0.0
files:
  - url: Lets-Flo-Desktop-1.0.0-mac.zip
    sha512: abc123...
    size: 12345678
path: Lets-Flo-Desktop-1.0.0-mac.zip
sha512: abc123...
releaseDate: '2025-05-24T08:00:00.000Z'
```

---

## 🔐 Security Best Practices

* Store the `GH_TOKEN` only on your backend
    
* Set strict scopes (no `write`, `delete`, or `admin`)
    
* Use environment variables, not hardcoded strings
    
* Add rate limiting / IP restrictions if possible
    

---

## 💡 Bonus: Cache the Release Info

To reduce GitHub API calls, you can cache the latest release metadata in memory or Redis and refresh it every X minutes.

---

## ✅ Summary

| Step | What You Do |
| --- | --- |
| Create GH token | With `repo` access only |
| Setup proxy server | Download and stream release via backend |
| Use Electron `generic` | Point to your server instead of GitHub |
| Hide token | Never expose token to frontend/app |

By proxying downloads through a secure server, you protect your GitHub credentials while still delivering updates seamlessly to your users.
