Skip to main content

Private Apps in faynoSync — Keep Your Software Secure

· 7 min read

When developing software, sometimes you need to keep things private. Whether it's internal tools, beta versions, or enterprise applications — not everything should be publicly accessible. That's why faynoSync comes with built-in support for private applications.

This post covers what private apps are, how to create one, how downloads and update checks are gated, and how private apps combine with team access control. Every flag and endpoint here is verified against the Create Application and private download docs.


What are Private Apps? 🤔​

Private apps in faynoSync are applications that are stored in a separate, private S3 bucket. This gives you an extra layer of security and control over who can access your software.

Once an app is marked as private, it stays private forever — this is a one-way decision to ensure consistency and security.


How to Create a Private App? 🛠️​

There are two ways to create a private app:

1. Using the Web Dashboard 🖥️​

Simply check the "Private" option when creating your app through the dashboard interface. It's that simple!

2. Using the API 📡​

When making a POST request to /app/create, include the private parameter. The endpoint uses multipart/form-data, so the JSON goes in a data form field:

curl --location 'http://localhost:9000/app/create' \
--header 'Authorization: Bearer <jwt_token>' \
--form 'data="{\"app\":\"appName\", \"private\": true, \"download_mode\": \"strict\"}"'
{ "createAppResult.Created": "641459ffb8760d74164e7e3c" }

The private field sits alongside the other creation flags — description, a logo file, tuf, and cdn (the edge cache toggle). Only app is required. A private app also takes download_mode (unlisted or strict, covered below), and it cannot enable cdn: edge manifests are public objects, so the API rejects that combination with 400.

Heads up: private is irreversible. Once it's true, the app cannot be made public later. Decide before you create, not after.


How Private Apps Work? 🔐​

  1. Storage: Private apps are stored in a separate S3 bucket (defined by S3_BUCKET_NAME_PRIVATE in your environment)
  2. Access Control: Each private app has a download_mode, set on create and changeable on update:
    • unlisted — anyone who has an artifact link can download it and check for updates
    • strict — update checks and downloads need a download token for the app and channel, or a JWT of the owner or of a team user with access
  3. Updaters: a private app works with the manual, tauri and squirrel_darwin updaters. Feed-based updaters (velopack, sparkle, electron-builder, squirrel_windows) need a publicly served feed and are rejected on upload.

ENABLE_PRIVATE_APP_DOWNLOADING is deprecated: it only picks the default download_mode for new private apps (true → unlisted, false → strict). See the Environment Variables Overview for the full storage configuration (the private bucket is configured per provider — MinIO, AWS, DigitalOcean Spaces, or GCS).


Download tokens for strict apps​

A download token (fnd_…) is scoped to one app and one channel. Create or rotate it with POST /download-tokens/regenerate:

curl -X POST --location 'http://localhost:9000/download-tokens/regenerate' \
--header 'Authorization: Bearer <jwt_token>' \
--header 'Content-Type: application/json' \
--data '{"app_id": "<app_id>", "channel_id": "<channel_id>"}'

The value is returned once; only its hash is stored. Ship it with the build and send it as the X-Download-Token header on every update check and download:

curl --location 'http://localhost:9000/checkVersion?app_name=appName&version=0.0.1&channel=stable&platform=linux&arch=amd64&owner=admin' \
--header 'X-Download-Token: fnd_<token>'

Without a valid token, a strict app answers exactly as an app that does not exist — no version, no changelog, no links. The Go SDK sends the header for you through CheckOptions.DownloadToken.


Downloading from a private bucket​

Artifacts in the private bucket aren't served by a plain public URL. You fetch them through GET /download, passing the object key:

curl -X GET --location 'http://localhost:9000/download?key=secondapp-admin%2Fstable%2Flinux%2Famd64%2Fsecondapp-0.0.1.deb' \
--header 'X-Download-Token: fnd_<token>'

You don't build the key yourself: the links in update-check responses of a private app already point to /download?key=…. What comes back depends on the credential:

  • Download token, or an unlisted app — the request is redirected to a signed URL of the file.
  • JWT with access — the response is a JSON download_url: a signed URL that expires after 15 minutes.
  • Anything else — 404, the same answer as for a key that does not exist.
{
"download_url": "https://<bucket>.s3.amazonaws.com/secondapp/stable/linux/amd64/secondapp-0.0.1.deb?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Expires=900&X-Amz-Signature=..."
}

Short-lived signed URLs are the key property: even if a link leaks, it expires in minutes, so it can't be reshared as a permanent public download. Full contract in the download docs.


Security Features 🛡️​

Think of private apps in faynoSync as your software's VIP section! Here's what makes them special:

  • 🔒 Separate Storage: Your private apps live in their own secure S3 bucket, like having a private vault for your most valuable assets
  • 🔐 One-Way Privacy: Once you mark an app as private, it stays private forever. This might sound strict, but it's actually a good thing — it ensures your security settings can't be accidentally changed
  • 👥 Smart Access Control: You're in charge! Each private app picks unlisted or strict, and strict apps get per-channel download tokens you can rotate at any time
  • 🚫 Download Protection: In strict mode, update checks and downloads without a token or a JWT with access get the same answer as a nonexistent app — nothing leaks about versions or files

Private apps and team access control​

Privacy and team-based authorization stack cleanly. The private bucket decides where artifacts live and download_mode decides whether downloads need a credential; team permissions decide who on your team can manage and download them via the apps.download permission and their allowed apps and channels. Use strict plus scoped team users when you need both restricted distribution and internal least-privilege access. Creating download tokens requires the permission to edit apps.

The same private bucket (S3_BUCKET_NAME_PRIVATE) is reused by report ingestion for storing debug blobs — sensitive payloads never touch the public/CDN bucket, and are only retrievable through the same short-lived presigned-URL mechanism.


Best Practices 💡​

  1. Use private apps for:

    • Internal tools and utilities
    • Beta versions of your software
    • Enterprise-specific applications
    • Software requiring license validation
  2. Decide on private before creating the app — it can't be undone.

  3. Use download_mode: strict when you need real access control, give each channel its own download token, and send it on update checks as well as downloads.

  4. Treat signed URLs as ephemeral — generate them on demand rather than caching or sharing them; they expire by design.


Common questions​

Can I make a private app public later? No. The private flag is irreversible by design. Create a new public app instead.

Why does /download return a URL instead of the file? The request carried a JWT, so the API hands back a short-lived signed URL as JSON. Requests with a download token, and requests for unlisted apps, are redirected to the file instead.

Why does my strict app look like it doesn't exist? The request has no valid X-Download-Token for that app and channel, or the JWT has no access. A denied request is answered exactly like a nonexistent app on purpose.

Where do private artifacts actually live? In the bucket named by S3_BUCKET_NAME_PRIVATE, separate from your public artifact bucket, on whichever provider your STORAGE_DRIVER points to.


How to try faynoSync?​

  1. Follow the Getting Started guide:
    👉 https://faynosync.com/docs/getting-started

  2. Create your app using the REST API or web dashboard:
    📦 API Docs: https://faynosync.com/docs/api
    🖥️ Dashboard UI: https://github.com/ku9nov/faynoSync-dashboard

  3. Upload at least two versions of your application.

  4. Check for updates with this simple request:
    📡 /info/latest



If you find this project helpful, please consider subscribing, leaving a comment, or giving it a star, create Issue or feature request on GitHub.
Your support keeps the project alive and growing 💚