Skip to main content

Command Palette

Search for a command to run...

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

Updated
4 min read
🛡️ 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):

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

  • 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):

// 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:

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:

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

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

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

StepWhat You Do
Create GH tokenWith repo access only
Setup proxy serverDownload and stream release via backend
Use Electron genericPoint to your server instead of GitHub
Hide tokenNever 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.