Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/privacy/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ var trackingSDKPatterns = []struct {
{regexp.MustCompile(`(?i)(mixpanel)`), "Mixpanel"},
{regexp.MustCompile(`(?i)(@segment/|analytics-react-native)`), "Segment"},
{regexp.MustCompile(`(?i)(branch\.io|react-native-branch)`), "Branch"},
{regexp.MustCompile(`(?i)(google.*ads|GADMobileAds|admob)`), "Google Ads/AdMob"},
{regexp.MustCompile(`(?i)(googlemobileads|google-mobile-ads|GADMobileAds|GADApplicationIdentifier|\badmob)`), "Google Ads/AdMob"},
{regexp.MustCompile(`(?i)(unity.*ads|UnityAds)`), "Unity Ads"},
{regexp.MustCompile(`(?i)(applovin|AppLovinSDK)`), "AppLovin"},
{regexp.MustCompile(`(?i)(ironSource|IronSource)`), "ironSource"},
Expand Down
78 changes: 78 additions & 0 deletions internal/privacy/scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,81 @@ func TestRequiredReasonDetectsRealCallSites(t *testing.T) {
}
}
}

// The Google Ads/AdMob pattern used to be `google.*ads`, which is unanchored and
// case-insensitive, so `ads` matched inside ordinary identifiers — `loadSdk`,
// `downloads`, `uploads`, `threads`. Any app that touched a non-ad Google SDK and
// happened to have one of those words on the same line got a CRITICAL §5.1.2
// finding telling it to add an ATT prompt it does not need, and `--exit-code`
// failed the build. https://github.com/RevylAI/greenlight/issues/30
func TestGoogleAdsPatternIgnoresOrdinaryIdentifiers(t *testing.T) {
clean := []struct {
name string
file string
content string
}{
{"google sign-in with loadSdk", "googleSignIn.ts",
"GoogleSignin: Awaited<ReturnType<typeof loadSdk>>[\"GoogleSignin\"],\n"},
{"google with downloads", "Downloads.swift",
"let googleDriveDownloads = try await drive.downloads()\n"},
{"google with threads", "Threads.ts",
"const googleClient = build(); // threads are reused here\n"},
{"google with uploads", "Uploads.ts",
"import { GoogleAuthProvider } from \"firebase/auth\"; const uploads = [];\n"},
}

for _, tc := range clean {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, tc.file, tc.content)

res, err := Scan(dir)
if err != nil {
t.Fatalf("Scan: %v", err)
}
for _, sdk := range res.TrackingSDKs {
if sdk == "Google Ads/AdMob" {
t.Errorf("reported Google Ads/AdMob for an app with no ad SDK; content=%q", tc.content)
}
}
})
}
}

// Anchoring the pattern must not cost us any real AdMob integration. These are the
// forms that appear in the file types detectLang actually scans — Swift, Objective-C
// and JS/TS. Manifest files like Info.plist, Podfile and package.json are not scanned
// at all today, so GADApplicationIdentifier is kept in the pattern for the Swift and
// Objective-C call sites rather than for the plist key.
func TestGoogleAdsPatternStillDetectsRealIntegrations(t *testing.T) {
real := []struct {
name string
file string
content string
}{
{"swift import", "Ads.swift", "import GoogleMobileAds\n"},
{"swift api", "Ads.swift", "GADMobileAds.sharedInstance().start(completionHandler: nil)\n"},
{"objc identifier", "Ads.m", "NSString *key = @\"GADApplicationIdentifier\";\n"},
{"react native import", "ads.ts", "import mobileAds from 'react-native-google-mobile-ads';\n"},
{"expo admob component", "Banner.tsx", "import { AdMobBanner } from 'expo-ads-admob';\n"},
{"bare admob token", "ads.js", "const admob = require('admob');\n"},
}

for _, tc := range real {
t.Run(tc.name, func(t *testing.T) {
dir := t.TempDir()
writeFile(t, dir, tc.file, tc.content)

res, err := Scan(dir)
if err != nil {
t.Fatalf("Scan: %v", err)
}
for _, sdk := range res.TrackingSDKs {
if sdk == "Google Ads/AdMob" {
return
}
}
t.Errorf("missed a real AdMob integration; content=%q TrackingSDKs=%v", tc.content, res.TrackingSDKs)
})
}
}