monetization-sdk-integration — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited monetization-sdk-integration (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Sources/<AdsBridge>/)import GoogleMobileAds over here too" — IMMEDIATE invokeSkip when: changing pure values / protocols inside the monetization core target (no third-party touch).
Default rule: no third-party SDKs in the app. Apple-platform native APIs preferred (OSLog over Sentry, MetricKit over Firebase Crashlytics, GameKit over Steam-style backend, etc.).
Break-glass exception is granted when ALL of:
canImport) — macOS / Catalyst paths must build without the SDKIf any of (1)-(5) fails: deny. Reject the SDK proposal; suggest Apple-native fallback or sit it out.
For every accepted SDK:
<SdkName>Bridge.swift defines the seam. Plain Swift; no SDK import.Live<SdkName>Bridge.swift is the ONLY file allowed to import <SDKModule>.any <SdkName>Bridge for DI. Test seam: Fake<SdkName>Bridge in the matching test target (e.g. Tests/<AdsBridge>Tests/FakeAdMobBridge.swift).Example for AdMob (currently shipped):
Sources/<AdsBridge>/AdMobBridge.swift — protocol (the monetization target's bridge protocol file)Sources/<AdsBridge>/LiveAdMobBridge.swift — sole import GoogleMobileAds siteTests/<AdsBridge>Tests/FakeAdMobBridge.swift — test seamWhen the live bridge's output crosses into UI or other host code, it must cross as a platform-neutral type (e.g. SwiftUI's AnyView), never a raw SDK type — that's what keeps UI targets at zero SDK imports even though they display SDK-provided content. Example: BannerViewProviding.bannerView(for:) -> AnyView? returns AnyView, not a GoogleMobileAds type.
Canonical regex (matches Swift 6 access-level imports too):
# replace <SDKModule> / <project-sources-root> with the project's actual values
rg '^(internal |private |public |@_implementationOnly |@preconcurrency )*import <SDKModule>' <project-sources-root>Expected count: 1 (live bridge file).
If > 1: the contract is broken. Either consolidate behind the existing bridge OR file an exception in the project's architecture-decisions doc (docs/foundations.md for consumers of collaboration-skills:spec-phase-orchestration) documenting WHY a second import site is necessary (with prior reviewer sign-off) — see Documentation pointers below.
iOS-only SDKs (AdMob/UMP/most monetization stack):
#if canImport(GoogleMobileAds)
import GoogleMobileAds
// ... live impl code
#else
// macOS / catalyst fallback — usually NoOp returning sensible empty values
#endifThe Package.swift dep arrow itself must also gate the SDK to iOS:
.product(
name: "GoogleMobileAds",
package: "swift-package-manager-google-mobile-ads",
condition: .when(platforms: [.iOS])
),Without condition:, macOS build fails on swift build because Google ships iOS-only xcframeworks.
A test target for the bridge ships Fake<SdkName>Bridge (actor or class). All unit tests inject the fake; real SDK only loaded at runtime via DI in the app's composition root. A shared testing-scaffolding target may ship shared fakes used across both ad and IAP test targets (e.g. FakeAdProvider, FakeIAPClient); the per-SDK bridge fakes live in their own test target.
Fake must:
Sendable (Swift 6 actor or @unchecked Sendable + lock-guarded)script(...) or per-call setter API for deterministic test outcomesbannerAdUnitID constant first used fatalError("REPLACE_BEFORE_RELEASE: …") as a transitional guard; it has since been replaced by xcconfig injection (→ build-time-secret-injection), which is the standing solution — the fatalError form is not a long-term answer on its ownGADApplicationIdentifier + bridge constant are always updated togetherBannerView SwiftUI host shipped, crossing into the UI layer per the cross-boundary type invariant above. One shared BannerSlotView replaced per-app placeholder slots.#if DEBUG forces Google's universal test unit; Release reads the per-app prod id from Bundle.main via xcconfig.import GoogleMobileAds ungated → macOS build brokecanImport(GoogleMobileAds) + Package.swift condition: .when(platforms: [.iOS]) + macOS fallback uses NoopAdProvidermise exec invocationslefthook.yml pre-commit.parallel: true (matches apple-public-repo-security's baseline); if a specific pair of commands genuinely races, serialize those two, not the whole fileswift build on a Mac, blocking PRs.build-time-secret-injection); a fatalError guard is acceptable only as a transitional step before that lands.NSPrivacyTracking / NSPrivacyTrackingDomains inconsistency (true with an empty domain list, false with domains listed); nothing checks the declaration against the app's actual network behavior. The real cost lands later: while the user hasn't granted ATT permission, requests to the domains listed in NSPrivacyTrackingDomains fail, so the list must be exact — don't pad it for safety; an omitted domain costs a privacy-label mismatch and a 5.1.x rejection, not a runtime failure. Update PrivacyInfo BEFORE adding the SDK.GAD prefix from its Swift API at v12.0.0 (GADBannerView → BannerView, GADRequest → Request, etc.; the GAD-prefixed spellings remain only in the Objective-C API). Check the SDK's migration guide before a major-version bump instead of assuming old names still resolve, and re-run the isolation audit (Build-time audit above) after — a migration can silently widen the import-site count past 1.When proposing a new SDK, fill this in:
SDK: <name + version + GitHub URL>
Capability: <what it does that Apple-native can't>
Privacy domains: <list — must match PrivacyInfo.xcprivacy>
iOS-only / cross-platform: <iOS-only | iOS+macOS | etc.>
Bundle size impact: <KB / MB>
Tracking ATT required: <yes/no>
UMP consent required: <yes/no>
Test seam plan: <how Fake<SdkName>Bridge will look>
Isolation audit grep target count: <expected 1>
Fallback platform behaviour: <Noop / throw / etc.>If any field is "TBD" or "?", do NOT proceed — research first.
docs/foundations.md for consumers of collaboration-skills:spec-phase-orchestration) — the no-3rd-party rule + break-glass exception + isolation contract textSources/<AdsBridge>/<SdkName>Bridge.swift — protocol seam exampleSources/<AdsBridge>/Live<SdkName>Bridge.swift — single-import-site example<App>/Resources/PrivacyInfo.xcprivacy — tracking domains declarationbuild-time-secret-injection — SIBLING; invoke together when wiring AdMob — that skill is the secret-handling layer (xcconfig injection), this skill is the SDK isolation and testing contract.storekit2-iap-defaults — same bridge-isolation pattern for the IAP side; app-store-review-rejections — ATT / PrivacyInfo consequences of shipping AdMob (5.1.2).references/official-docs.md.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.