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

Search for a command to run...

Mind maps are an excellent addition to an MCP ecosystem when they help users understand relationships between data, tools, and reasoning. They should complement—not replace—traditional dashboards, tab
Most AI agents today depend heavily on cloud APIs. They're fast, but every request costs money, depends on an internet connection, and sends your data to external providers. Over the weekend, I experi
How I use Fable 5 to research, build, and maintain custom Claude Code plugins, marketplaces, agents, and skills—creating reusable AI tooling that works across every project. 01 · The problem with copy

The way people search is changing. Is your site ready? For the past two decades, SEO (Search Engine Optimization) was the undisputed king of web visibility. Rank on Google's first page and you win tr
How we made 30 GB PSD uploads feel instant without expensive backend conversions. When we first built our media upload system, the architecture looked completely normal. A user uploaded a file, the b

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.
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.
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.
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.
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 };
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}`);
});
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'
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
To reduce GitHub API calls, you can cache the latest release metadata in memory or Redis and refresh it every X minutes.
| 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.