Skip to content

Add setting to strip tracking parameters from links - #14970

Open
kees wants to merge 1 commit into
signalapp:mainfrom
kees:feature/strip-tracking-params
Open

Add setting to strip tracking parameters from links#14970
kees wants to merge 1 commit into
signalapp:mainfrom
kees:feature/strip-tracking-params

Conversation

@kees

@kees kees commented Aug 30, 2026

Copy link
Copy Markdown

First time contributor checklist

Contributor checklist

  • Pixel XL, Android 10
  • My contribution is fully baked and ready to be merged as is
  • I ensure that all the open issues my contribution fixes are mentioned in the commit message of my first commit using the Fixes #1234 syntax

Description

Links shared from Facebook, Instagram, YouTube and similar platforms carry per-share tracking
tokens: e.g. fbclid, igshid, YouTube's si, X's s/t, the whole utm_* family. These are not
generic analytics: a si or t token is minted for a specific share by a specific account, so
when the recipient taps the link, the destination site learns that those two people are in contact
and that the link travelled between them. Signal protects the message but clicking the link exposes
the relationship to a third party anyway.

This adds a Privacy setting (on by default) that removes known tracking parameters from a link
at the moment it is opened, copied, or fetched for a preview.

Message content is never modified

This is the central design constraint, and it is what distinguishes this from
#9125 (see below).

Text that was sent to you is rendered exactly as the sender wrote it. Nothing rewrites a
message body, inbound or outbound. What changes is only:

  1. the URL a tap hands to the browser,
  2. the URL a long-press copies,
  3. the URL of Signal's own outbound HTTP request when generating a link preview.

The reasoning:

  • A received message is the sender's content and the client authenticated it. Silently editing it
    would make quoting, search, and "what did they actually send me?" unreliable, and would mean the
    rendered text no longer matches what was received.
  • Rewriting outgoing bodies is worse, not better. A LinkPreview.url must appear verbatim in the
    message body or every recipient discards the preview
    (DataMessageProcessor.getLinkPreviewspresentInBody). Cleaning the attached preview URL
    therefore requires also editing the body the user typed. This PR sidesteps that entirely: only
    the sender's own preview fetch is cleaned, so the preview still renders for recipients.

Because the visible text is left alone, the displayed URL and the tap target can differ. That
is safe here: the cleaned URL is rebuilt with HttpUrl.newBuilder(), so scheme, host, port,
path and fragment are carried over untouched and only query parameters can change. A
cleaned link is always the same destination with less attached, never a different one. There
is a test asserting exactly that (never changes anything but the query), because it is
the property the whole design rests on.

Parameter selection

Two tiers:

  • Global: removed everywhere: the utm_* prefix family, fbclid, gclid/gclsrc/dclid/
    gbraid/wbraid, msclkid, twclid, ttclid, yclid, _openstat, igshid, mc_cid/
    mc_eid, mkt_tok, _hsenc and similar. Every one is a click/campaign identifier that
    attributes a visit; none select what the URL resolves to.
  • Host-scoped: names too generic to strip globally, matched by domain suffix so subdomains are
    covered: is/si/feature on YouTube, si on Spotify, mibextid on Facebook, s/t on
    twitter.com and x.com. t is a legitimate timestamp parameter on plenty of other sites, so
    it is never touched off those hosts.

A URL that parses to nothing strippable is returned as the same instance, so links that need no
cleaning are never re-encoded or normalised as a side effect.

Prior art

There is no open issues or PRs for this. There are two relevant discussions:

  • Community forum #16467 "Clean Sent Links (strip tracking/UTM parameters)",
    open since 2020, 22 posts / 125 likes. Post No max. thread size setting #23 independently identifies the si and t
    parameters as "an extremely invasive and powerful way to map an individual's network of
    contacts".
  • #9125 "Shared links cleaner" (2019),
    closed by the stale bot in 2022 without a review on the merits. It cleaned links arriving through
    ShareActivity, i.e. it rewrote the text being shared. This new PR is deliberately the complement:
    it never touches text, and instead cleans at the point of use, which also covers links that were
    typed, pasted, or received rather than shared in.

@cody-signal noted on that PR in 2023 that "this sort of thing has been brought up internally a
couple times and is gaining popularity in other products", and pointed the discussion to the forum.

On "The answer is not more options"

The contributing guide's first principle argues against adding new settings.

The principle's neighbours seem to explain the rationale: "The user doesn't know what a key is",
"There are no power users", etc. It is aimed at options that expose people to
cryptographic or protocol concepts they should never have to reason about, and at capability handed
to self-identified experts. "Remove link tracking" is not that. It is a phrase users already meet
in Firefox, Safari and Brave, all of which ship this behaviour, and understanding it requires no
model of anything Signal does internally.

There is also direct precedent in the same screen area: Generate link previews is already a
user-facing toggle, and it exists for the same reason: the feature discloses something to a third
party, and some users want to decide about that disclosure. This setting is the same category of
control over the same category of disclosure.

There is a failure mode where some site treats a listed global parameter for another purpose.
The host-scoped tier exists to make that unlikely, but a user-recoverable solution (the setting)
provides a way around this in a pinch.

That said, if folks disagree, the setting is trivial to remove. I am happy to push that version instead,
and would rather land the protection without the toggle than not land it.

The setting defaults to on for new and existing installs. This too, I'm fine
switching this to "off by default"; I'd rather have the feature available than not. :)

Scope

Stripping happens for:

  • message body links (tap and long-press-copy)
  • styled/formatted links
  • link-preview card taps
  • text-story preview taps
  • story replies
  • group descriptions
  • contact notes
  • outbound preview fetch

Deliberately excluded:

  • the og:image thumbnail fetch (the site's own asset, no share provenance to leak, and CDN URLs can carry signed parameters)
  • the share actions in StoryContextMenu / MediaOverviewContextMenu, which hand text/plain to another app rather than explicitly opening a link.

Testing

  • 17 new unit tests in TrackingParametersTest, covering each parameter tier, case-insensitive
    matching, valueless and repeated parameters, parameter order preservation, fragment/port/path
    preservation, non-HTTP and unparseable input, and the "only the query changes" invariant.
  • Full app unit suite green: 297 suites, 3397 tests, 0 failures, 0 errors
    (./gradlew :Signal-Android:testPlayProdDebugUnitTest).
  • ./gradlew :Signal-Android:ktlintCheck :fast-lint:fastLint checkStopship clean.
  • ./gradlew format produces no diff.
  • Manually verified on a playStagingDebug build against the
    staging environment: the setting is present and on for a fresh install, and links behave as
    described above when enabled.

@kees
kees force-pushed the feature/strip-tracking-params branch from 1f87be0 to 5cca6c5 Compare September 1, 2026 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant