Skip to content

fix(privacy): anchor the remaining tracking SDK patterns - #34

Open
Maher-Reven wants to merge 3 commits into
RevylAI:mainfrom
Maher-Reven:fix-tracking-pattern-false-positives
Open

Maher-Reven wants to merge 3 commits into
RevylAI:mainfrom
Maher-Reven:fix-tracking-pattern-false-positives

Conversation

@Maher-Reven

@Maher-Reven Maher-Reven commented Sep 19, 2026

Copy link
Copy Markdown

Stacked on #33. This branch contains that commit as its base, so the diff here shows two commits — review the second one, or merge #33 first and this will reduce to it. Opened separately because the two changes are independently reviewable.

Problem

#33 fixes google.*ads matching loadSdk. The rest of the table has the same shape, and one of them is worse than the reported case.

An unanchored .* between two short tokens matches across unrelated code on the same line:

pattern ordinary code that trips it reported as
adjust.*sdk label.adjustsFontSizeToFitWidth = true // call before sdkInit() Adjust SDK
adjust.*sdk scrollView.adjustedContentInset = insets; let sdkReady = true Adjust SDK
unity.*ads unityWebView.loadThreads(); Unity Ads
unity.*ads const unityBridge = init(); const uploads = []; Unity Ads
google.*analytics { googleClientId: ID, analyticsEnabled: false } Google Analytics
firebase.*analytics import { getAuth } from 'firebase/auth'; // analytics intentionally omitted Firebase Analytics
facebook.*sdk // the facebook login flow was removed; see sdkMigration.md Facebook SDK

adjust.*sdk is the one I would prioritise. adjustsFontSizeToFitWidth and adjustedContentInset are ordinary UIKit that appear in most iOS codebases, so the trigger is just "this file also contains the letters sdk" — and like #30 it produces a CRITICAL §5.1.2 that fails --exit-code.

Fix

Require a distinctive token in each case rather than two fragments with a gap:

{regexp.MustCompile(`(?i)(firebaseanalytics|firebase[-/]analytics)`), "Firebase Analytics"},
{regexp.MustCompile(`(?i)(googleanalytics|google-analytics)`), "Google Analytics"},
{regexp.MustCompile(`(?i)(fbsdk|facebooksdk|facebook-sdk)`), "Facebook SDK"},
{regexp.MustCompile(`(?i)(adjust[-_]?sdk|react-native-adjust|com\.adjust\b|Adjust\.(appDidLaunch|trackEvent|initSdk|getAdid))`), "Adjust SDK"},
{regexp.MustCompile(`(?i)(unityads|unity-ads|unity3d\.ads)`), "Unity Ads"},

Adjust keeps its API call sites in the alternation because the iOS SDK is imported as AdjustSdk but used as Adjust.appDidLaunch(...), and a file may contain only the latter.

Two alternations are also collapsed because (?i) already makes their branches identical: (applovin|AppLovinSDK)applovin, (ironSource|IronSource)ironsource.

What I deliberately did not change

mixpanel, appsflyer, amplitude, @segment/ and branch.io are left as they are. The issue on #30 suggested mixpanel might have the same problem — it does not. It is a distinctive brand token that cannot appear inside an ordinary identifier the way ads appears inside loadSdk. Changing it would be churn.

(Those patterns do still match inside comments and string literals, but that is a property of how the scan is run rather than of the patterns, and it is not in scope here.)

Testing

Both directions, in internal/privacy/scanner_test.go:

  • TestTrackingPatternsIgnoreOrdinaryCode — the seven rows in the table above. All seven fail against the old patterns; I verified by reverting them.
  • TestTrackingPatternsStillDetectRealSDKs — twelve real integrations that must keep matching, covering both the import form and the API-call form for Adjust, plus Unity Ads, Google Analytics, Firebase Analytics (RN and Swift), Facebook SDK (RN and iOS), AppLovin and ironSource.

go build ./... and go test ./... are clean.


Note

Medium Risk
Changes compliance scan heuristics; anchored patterns could miss uncommon SDK spellings, though broad positive/negative tests reduce that risk.

Overview
Tightens tracking/ad SDK detection in the privacy scanner so loose token.*token regexes stop firing on unrelated same-line code (e.g. UIKit adjustsFontSizeToFitWidth + sdk, googleClientId + analyticsEnabled, firebase/auth comments). That class of matches was incorrectly raising CRITICAL §5.1.2 “tracking SDK without ATT” and failing --exit-code.

Patterns for Firebase/Google Analytics, Facebook, Adjust, Google Ads/AdMob, Unity Ads, AppLovin, and ironSource now match distinctive package/API tokens (imports, RN package names, Adjust.* call sites, Firebase compat paths, etc.) instead of unanchored gaps. AppLovin/ironSource alternations are simplified under (?i).

Adds scanner_test.go coverage in both directions: ordinary code must not report those SDKs, and representative real integrations must still be detected (including AdMob cases from #30).

Reviewed by Cursor Bugbot for commit ab79e2e. Bugbot is set up for automated code reviews on this repo. Configure here.

Maher added 2 commits September 20, 2026 00:03
`(?i)(google.*ads|GADMobileAds|admob)` is unanchored, so `ads` matched
inside `loadSdk`, `downloads`, `uploads` and `threads`. Any app that used
a non-ad Google SDK and had one of those words on the same line got a
CRITICAL §5.1.2 finding, which `--exit-code` turns into a failed build,
and the suggested fix was to add an ATT prompt the app does not need.

Require an ad-specific token instead. `\badmob` is anchored only at the
start so it still covers `AdMobBanner` and `expo-ads-admob` while
rejecting words that merely end in those letters.

Fixes RevylAI#30
The other `X.*Y` patterns share the defect fixed for Google Ads: an
unanchored `.*` between two short tokens matches across unrelated code on
the same line.

`adjust.*sdk` is the worst — `adjustsFontSizeToFitWidth` and
`adjustedContentInset` are ordinary UIKit, so any line carrying one of
them and the letters `sdk` reported the Adjust SDK. `unity.*ads` matches
`unityWebView.loadThreads()`. `google.*analytics`, `firebase.*analytics`
and `facebook.*sdk` have the same shape.

Also collapse two alternations that are duplicates under `(?i)`:
`(applovin|AppLovinSDK)` and `(ironSource|IronSource)`.

Mixpanel, AppsFlyer, Amplitude, Segment and Branch are left alone — they
already require a distinctive brand token that cannot collide with an
ordinary identifier.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

Reviewed by Cursor Bugbot for commit 9666c1b. Configure here.

Comment thread internal/privacy/scanner.go Outdated
Comment thread internal/privacy/scanner.go Outdated
Review catch: anchoring those two patterns lost integrations the old ones
detected.

Firebase only allowed `firebase` and `analytics` to be adjacent or joined
by a single `-` or `/`, which drops the namespaced `firebase.analytics()`
API and the `firebase/compat/analytics` import path. Allow `.` as a
separator and the `compat/` segment.

Facebook required `fbsdk`, `facebooksdk` or `facebook-sdk`, which drops
`facebook-ios-sdk` and the `facebook-jssdk` script id used by the web
loader snippet. Allow the `ios-`, `android-` and `js` infixes.

Neither widening reintroduces the false positives: both still require
the tokens to be joined, so `firebase/auth` followed by the word
analytics, and `facebook` followed by a separate `sdk`, stay clean.
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