@incy/link-encoder
v1.0.1
Published
Encode subscription URLs into incy://crypt1/<payload> deep links so they don't sit in chat histories as plain VPN-URL text. AES-256-GCM with a key shared with the INCY iOS/Android/Desktop clients.
Maintainers
Readme
@incy/link-encoder
Encode VPN subscription URLs into incy://crypt1/<payload> deep links
that the INCY iOS, Android, and Desktop clients
decode automatically.
https://sub.your-provider.example/abc123token
⬇
incy://crypt1/AAECAwQFBgcICQoLNyIQL3rDwRZqnyoD8pGK…Open the resulting link on a device with INCY installed → the subscription imports without the user copy-pasting anything.
Install
npm install @incy/link-encoderUsage
import { encryptLink, decryptLink } from '@incy/link-encoder';
const link = encryptLink('https://sub.your-provider.example/abc123token', {
name: 'My Provider VPN',
});
console.log(link);
// → incy://crypt1/AAECAwQFBgcICQoLNyIQL3rDwRZqnyoD8pGK…
// Decryption mainly for testing — the INCY apps do this end-side.
const decoded = decryptLink(link);
console.log(decoded.url, decoded.name);encryptLink(url, opts?) accepts:
| Field | Type | Notes |
|----------|-----------|----------------------------------------------------|
| url | string | The http(s) subscription URL. Required. |
| opts.name | string? | Display name shown in the receiver's import sheet. |
What this is
A small, dependency-free encoder for embedding subscription URLs in chat messages and websites without exposing the raw URL to scanners, moderation bots, or screenshots.
What this is NOT
This is not encryption-for-secrecy. The AES-256-GCM key is derived from constants and binary assets shipped inside this package — anyone reading the source can reconstruct it.
The exact same key already lives inside every INCY client (iOS, Android, Desktop). Anyone with a copy of those apps could already extract it using standard mobile reverse-engineering tools. Publishing this package reveals nothing new — it just makes the limitation explicit.
Threat model
| | Defended |
|----------------------------------------------|:--------:|
| Telegram chat moderation bots | ✅ |
| Russian regulator (RKN) automated scanners | ✅ |
| Casual screenshots and clipboard mishaps | ✅ |
| grep over chat dumps | ✅ |
| Determined reverse engineer with Frida | ❌ |
If the key is ever published publicly (e.g. extracted and shared on
Twitter), a future INCY release will introduce crypt2/ with a fresh
key. Existing crypt1/ links in chat histories will keep working
forever — the clients never remove old schemes.
API
encryptLink(url: string, opts?: { name?: string }): string
decryptLink(link: string): { url: string; name?: string }
// For deterministic tests only — never reuse an IV with different
// plaintexts in production code.
encryptLinkDeterministic(url: string, opts: { iv: Buffer; name?: string }): string
// Runtime info
VERSION: string // package version
SCHEME_VERSION: string // current deep-link scheme, e.g. "crypt1"
KEY_FINGERPRINT: string // SHA-256 of K1 — for sanity checksCross-platform compatibility
A link generated by this package decodes bit-for-bit identically on
iOS (CryptoKit), Android (javax.crypto), and Desktop (Compose
Multiplatform JVM, also javax.crypto). A test vector pinned in the
test suite guards against drift between updates.
License
MIT
