obfus.link
Obfuscators

Double-hop URL encryption: keeping destinations out of referrer logs

AES-256-GCM encrypted link shortening with single or double-hop redirect chains, click-limited self-destructing links, and zero-knowledge passphrase gates — for sharing internal URLs without leaking them.

The Link Obfuscator encrypts a destination URL with AES-256-GCM and serves it through one or two redirect hops. Double-hop mode hides the destination from any single referrer log. Optional TTL, max-click, and scrypt-hashed passphrase parameters combine to create self-destructing access links.

1. Insight

Insight

The problem this article addresses and why it matters.

The HTTP referrer header is one of the oldest information leaks on the web. When a user clicks a link from page A to page B, page B receives an HTTP request with Referer: https://A/.... That's not a privacy bug — it's how the web was designed in 1996 and it still ships in every browser. The trouble is that the path of A is usually meaningful: /notion.so/internal-q4-strategy-7f3a2, /figma.com/file/Acme-Auth-Wireframes, /docs.google.com/document/d/1k_internal-rebrand-deck.

When someone shares one of those URLs in Slack, an open analytics endpoint, or a customer support form, the destination URL itself becomes the leak — visible to whoever runs the receiving end of every request that follows. Mozilla's referrer policy reference covers the four mitigations browsers expose (no-referrer, strict-origin, etc.), but every one of them is set by the destination site. If you're the link-sharer, you don't control the destination's policy.

A standard URL shortener (bit.ly, t.co, tinyurl) replaces the long URL with a short code on the shortener's domain. Click the short link and the browser receives a 301 or 302 redirect with the destination in the Location header. The destination site sees Referer: https://bit.ly/abc123 — that fixed the source leak, but only one hop. Anything downstream of the destination (analytics pixels, embedded iframes, third-party scripts) still gets the full destination URL via the request chain. And the bit.ly admin sees every destination URL in plaintext in their database.

The Link Obfuscator solves both halves by encrypting the destination URL with AES-256-GCM before persisting it (the database stores only ciphertext + IV + auth tag), then offering a double-hop redirect chain that keeps the destination off any single referrer log.

What this article delivers

Concrete walkthroughs of single-hop vs double-hop chains, the click-limited self-destruct mechanic, and the zero-knowledge passphrase gate. We'll cover the failure modes (rotated keys, expired links, brute-force surface), the agent-pipeline use cases that no commodity shortener supports, and the trust boundaries that decide whether this tool is right for your threat model.

2. Intent

Intent

What you will be able to do after reading.

By the end of this article you will be able to:

  • Encrypt a destination URL with AES-256-GCM and generate a single-hop or double-hop redirect chain
  • Choose a TTL and a max-click budget that combine to create self-destructing links
  • Add an optional passphrase gate that uses scrypt-hashed verification without ever transmitting the passphrase to the destination
  • Distinguish when single-hop is sufficient (most internal sharing) from when double-hop is necessary (URLs that are themselves sensitive data)
  • Recover from common failure modes — rotated master keys, expired links, exhausted click budgets — without losing user trust

We'll show the request flow at each hop, the encryption parameters that make the database compromise resistant, and the agent-pipeline patterns where double-hop becomes structural rather than cosmetic.

3. Examples

Examples

Annotated code and worked scenarios.

Before / after: sharing an internal Notion page in a public Slack

Before — paste the raw Notion URL:

https://www.notion.so/acme/Q4-Strategy-7f3a2c8d4e1b

The path contains the document slug. Anyone who sees the URL in Slack, in screenshots forwarded to vendors, or in a partner email knows the document exists. Even if Notion gates access by login, the existence of Q4-Strategy is now a public fact.

After — single-hop obfuscation:

curl -X POST https://obfus.link/api/v1/link_obfuscator \
  -d '{
    "url":  "https://www.notion.so/acme/Q4-Strategy-7f3a2c8d4e1b",
    "mode": "single"
  }'

Response:

{
  "shortUrl":  "https://obfus.link/x/a7f3c2",
  "shortCode": "a7f3c2",
  "hops":      1,
  "encryption": { "cipher": "aes-256-gcm" }
}

Share obfus.link/x/a7f3c2. The destination URL exists only in the obfus.link database as AES-256-GCM ciphertext keyed by a server-side master key + the short code (via HKDF). A database leak by itself reveals nothing.

Before / after: protecting the destination from any single referrer log

Single-hop leaves the destination URL in the next request after the redirect. If the destination is itself sensitive (an OAuth callback with a session-bound token, a one-time file download URL, a private Figma frame), even one hop is too many.

Before — single-hop:

obfus.link/x/a7f3c2  →  https://figma.com/file/Acme-Auth-Wireframes
                       Referer: https://obfus.link/x/a7f3c2
                       (Figma's analytics now has the destination)

After — double-hop:

curl -X POST https://obfus.link/api/v1/link_obfuscator \
  -d '{
    "url":  "https://figma.com/file/Acme-Auth-Wireframes",
    "mode": "double"
  }'
obfus.link/x/a7f3c2  →  obfus.link/x/b8d4e1  →  https://figma.com/file/...
                       (first hop sees only the second obfus.link URL)
                       (second hop sees only its own URL as referrer)

The destination URL never appears in any referrer header outside its own request. No analytics pixel anywhere in the chain has it.

curl -X POST https://obfus.link/api/v1/link_obfuscator \
  -d '{
    "url":        "https://files.acme.com/quarterly-report-final.pdf",
    "mode":       "single",
    "ttl":        604800,
    "maxClicks":  3
  }'

ttl: 604800 = 7 days. maxClicks: 3 = at most three resolutions. Whichever fires first, the link returns HTTP 410 Gone thereafter. Use this for sharing files that should expire on use rather than on a schedule alone — vendor proposals, candidate-take-home assignments, embargoed press kits.

When humans use this

A founder shares the company's internal investor update doc with three potential angels via three obfuscated links, each with maxClicks: 1. Each angel gets a personal link that self-destructs on first read. The founder can audit which links resolved without instrumenting Notion. A designer drops a Figma frame into a vendor's design-review portal with mode: double so the vendor's own analytics doesn't index the Figma URL.

When agents use this

Multi-step agent pipelines often produce ephemeral URLs — pre-signed S3 download links, OAuth state callbacks, one-time webhook receivers — and need to ship them through chat APIs, ticket systems, or other agents. Three properties matter here:

  • Deterministic creation API. The agent gets back {shortUrl, expiresAt, hops} synchronously. No follow-up call required to know whether the link is alive.
  • Encrypted at rest. The destination URL never appears in the obfus.link database as plaintext, so an agent operator's compliance review (SOC2, ISO 27001) doesn't have to treat the obfus.link infrastructure as a privileged data store.
  • Double-hop composes with referrer-aware destinations. When an agent posts a link to a customer-facing surface and the destination itself logs referrers (Mixpanel, PostHog, Segment), double-hop is the difference between a clean event log and one polluted with internal infrastructure URLs.

The link_obfuscator MCP tool returns the same shape as the REST endpoint, so agents call it inline in a tool-use loop without a separate auth dance.

Edge cases

Private IP ranges and metadata endpoints

The tool rejects destinations pointing at 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, IPv6 loopback / link-local, and cloud-metadata endpoints (169.254.169.254, metadata.google.internal). This stops SSRF attempts where an attacker uses the obfuscator's redirect to bounce off an internal service.

Non-HTTP schemes

javascript:, data:, file:, chrome-extension: and similar are rejected at creation time. Only http: and https: resolve to a valid destination.

Rotated master key

If the operator rotates LINK_OBFUSCATOR_KEY, every existing obfuscated link becomes undecryptable — they return HTTP 410 Gone. This is the expected behaviour for a security key rotation. Plan for it: if you're using obfus.link's flagship surface for long-lived links, the master key should be treated like a TLS certificate, not a session secret.

Passphrase + max-clicks interaction

A passphrase-gated link with maxClicks: 1 is a one-time bearer credential — once the correct passphrase is submitted, the link resolves and the click counter increments. A second visit (right passphrase or wrong) hits the depleted counter and returns HTTP 410 Gone. This is intentional and the only way to keep "one and only one read" semantics atomic.

4. Documentation

Documentation

Reference signatures, edge cases, and lookup tables.

Input parameters

Field

Type

Required

Default

Description

url

string

Destination URL. Must be http: or https:. Private IPs and metadata endpoints rejected.

mode

'single' | 'double'

One redirect or a two-hop chain.

ttl

number

0 (no expiry)

Seconds until expiry. Max 2592000 (30 days).

maxClicks

number

0 (unlimited)

Auto-expire after N clicks. Max 10000.

passphrase

string

Optional human-facing gate. scrypt-hashed server-side. Never logged.

customAlias

string

Prefer a specific short code. Returns INPUT_INVALID_TYPE if taken.

Output shape

{
  shortUrl:      string;     // "https://obfus.link/x/a7f3c2"
  shortCode:     string;     // "a7f3c2"
  hops:          1 | 2;
  expiresAt?:    string;     // ISO timestamp if ttl > 0
  maxClicks?:    number;
  passphraseSet: boolean;
  encryption: {
    cipher: 'aes-256-gcm';
  };
}

Server-only fields (records, IV, auth tag) are stripped before the response leaves the server. The browser never sees the encrypted records.

Error codes

Code

When it fires

Recovery

INPUT_EMPTY

url missing or empty

Provide a non-empty URL

INPUT_MALFORMED

URL parse fails

Verify the URL is well-formed (https://example.com/path)

INPUT_INVALID_TYPE

Rejected destination (private IP, non-HTTP scheme, taken alias)

Read the message — it names the specific rejection

PAYLOAD_LIMIT

TTL > 2592000 or maxClicks > 10000

Reduce to within bounds

TRANSFORM_FAILED

Master key missing in prod, or DB insert failed

Operator-level fix: set LINK_OBFUSCATOR_KEY or check Supabase health

SECURITY_VIOLATION

Prompt-injection pattern detected in url or passphrase

Sanitise input source upstream — do not retry the same input

When NOT to use this tool

Don't use link_obfuscator as a security measure for content you don't control. The destination URL is still resolvable by anyone who has the short link; encryption protects the URL from the database and the referrer chain, not from the recipient. If you need recipient-level access control, use signed URLs on the destination (S3 pre-signed URLs, JWT-gated CDN endpoints) — the obfuscator can layer on top, but it can't replace them.

Don't use it for high-frequency public links (newsletter CTAs, marketing redirects). Standard shorteners like bit.ly offer click analytics and A/B routing that this tool doesn't aim to compete with. The flagship use case is sensitive internal links — sharing fewer of them, with stronger guarantees.

Performance notes

The encryption + persistence path runs in under 30ms typical against the same-region Supabase pool. Single-hop resolution is one DB read + decrypt + 302 redirect (≈ 20ms). Double-hop resolution adds one additional DB read for the second short code (≈ 35ms total). Click-count increments are atomic via a single Postgres UPDATE with RETURNING so concurrent clicks against a maxClicks: 1 link cannot both succeed.

Try it now

Link Obfuscator

AES-256-GCM encrypted short URLs with double-hop and self-destruct

FAQ

Frequently asked questions

When should I use double-hop instead of single-hop?

When the destination URL itself is sensitive data — OAuth callbacks with state-bound tokens, private Figma frames, pre-signed file download URLs. Single-hop is sufficient when only the source URL needs hiding.

What happens if the master encryption key is rotated?

Every existing obfuscated link becomes undecryptable and returns HTTP 410 Gone. This is the expected behaviour for a security key rotation — treat the master key like a TLS certificate, not a session secret.

Does the passphrase get sent to the destination URL?

No. The passphrase is verified server-side with a scrypt-hashed timing-safe comparison and never reaches the destination. The destination only sees the resolved URL after the gate is passed.

Can I use this as a replacement for bit.ly?

Not for high-volume marketing redirects — bit.ly offers click analytics and A/B routing that this tool doesn't aim to compete with. The flagship use case is sensitive internal links where encryption and double-hop matter more than analytics.

How does it prevent SSRF attacks?

Destinations resolving to private IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), loopback, link-local addresses, and cloud-metadata endpoints (169.254.169.254) are rejected at creation time. Non-HTTP schemes are also rejected.