Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Android 앱의 의도치 않은 변경 방지하기

Android 앱의 의도치 않은 변경 방지하기

NAVER ENGINEERING DAY 2026 (5월) 행사에서 발표한 내용입니다.

"Android 앱의 의도치 않은 변경 방지하기"
Android 앱 및 라이브러리 개발자가 외부 라이브러리 업데이트 시 발생하는 의도치 않은 변경을 사전에 감지하고,
Baseline 기반의 방어 체계를 구축하는 방법을 소개합니다.

Link
- YouTube: WIP
- NAVER TV: WIP
- NAVER D2: WIP

Avatar for Sungyong An

Sungyong An

May 18, 2026

More Decks by Sungyong An

Other Decks in Programming

Transcript

  1. 목차 01 배경 Baseline 기반의 의존성 추적 시 스템에서 AndroidManifest

    추적 까지 확장이 필요했던 이유 02 Manifest Shield 글로벌웹툰 앱에 적용한 플러그인 소개 및 SDK Activity 자동 추적 사례 언급 03 내부 동작 AGP 입력을 정규화된 텍스트로 만들어 비교하고, 변경이 어디서 왔는지 추적하는 방법 설명 04 AI 로 만들기 AI와 함께 만든 도구가 다시 AI 의 검토 범위를 구체화
  2. 변경 사항 종류 변경의 종류와 검출 방법 01 코드 스타일

    ex. \n, space 공백·줄바꿈 같은 표면 변경. ktlint 로 검출 가능. 02 구조 변경 ex. ܻಂష݂ 함수 시그니처·클래스 구조 변경. Unit Test 로 검출 가능. 03 버전 업데이트 ex. Kotlin / SDK 라이브러리·툴 버전 상승. ???
  3. 문제가 된 실제 사례 13개의 광고 SDK 버전을 업데이트하는 과정에서

    사람의 눈으로 잡지 못했던 경우 담당자가 PR 단계에서 변경을 꼼꼼히 검토했다 ✓ Google Ad v20 클래스 이름 변경 대응 UnifiedNativeAd → NativeAdView ✓ 헤더비딩 라이브러리 APS 업데이트 8.4.3 → 9.2.0 ✓ Firebase Analytics 업데이트 17.5.0 → 18.0.0 결 과 Inmobi 9.2.0 이 내부적으로 appcompat 1.3.0 을 사용 → 글로벌웹툰 앱에 영향 → 전체 PR 롤백 사람의 눈으로 transitive dependency 까지 추적하는 건 사실상 어렵습니다.
  4. 검출 시점과 수정 비용 Develop → QA → Release 단계별

    수정 비용 01 Develop develop branch 개발자가 빌드를 돌리자마자 변경이 잡힙니다. 바로 코드를 고치면 됩니다. 02 QA పझ౟ ױ҅ 운이 좋으면 QA에서 검출됩니다. 테스트 시나리오를 다시 돌려야 합니다. 03 Release Play Console 스토어에 등록하는 시점에 문제가 드러나고, 결국 롤백 외엔 답이 없습니다. 수정 비용 증가 Develop 단계에서 잡으면 수정 비용이 가장 적습니다. 늦게 발견될수록 영향 범위가 커집니다.
  5. Baseline 기반의 검증 프로세스 기준 파일을 두고, 검증 시점에 비교합니다

    01 Snapshot dependencies.txt develop branch 기준의 의존성 목록을 baseline 파일로 보관합니다 02 Compare diff against baseline 현재 의존성 목록과 baseline 을 비교해 차이를 찾습니다 03 Block / Update fail or re-baseline 의도된 변경이면 baseline 을 갱신하고, 아니면 검증을 실패시킵니다
  6. Dependency Guard Baseline 패턴을 의존성에 적용한 오픈소스 # baseline ࢤࢿ

    → Git ழ޿ $ ./gradlew :app:dependencyGuardBaseline # Ѩૐ द ࠺Ү → ׮ܰݶ पಁ $ ./gradlew :app:dependencyGuard Dependencies Changed in :app for releaseRuntimeClasspath - androidx.appcompat:appcompat:1.2.0 + androidx.appcompat:appcompat:1.3.0 If this is intentional, re-baseline using ./gradlew :app:dependencyGuardBaseline 광고 SDK 만 올렸는데 transitive 로 따라온 appcompat 까지 올라간 게 즉시 보입니다.
  7. 현재의 방어선 의존성은 오픈소스로, 권한은 자체 스크립트로 감지하던 상태 Develop

    QA Release Dependency Guard (OSS) + Permission Guard (자체 스크립트)
  8. Permission Guard Merged Manifest에서 Permission 목록만 뽑아낸다 <uses-permission android:name="com.google.android.gms.permission.AD_ID" />

    ... <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> <supports-screens ... /> <uses-permission android:name="com.google.android.gms.permission.AD_ID" /> ... <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> <uses-feature ... /> <uses-permission android:name="com.google.android.gms.permission.AD_ID" /> ... <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> <supports-screens ... /> <uses-permission android:name="com.google.android.gms.permission.AD_ID" /> ... <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" /> <uses-feature ... /> com.google.android.gms.permission.AD_ID ... com.google.android.c2dm.permission.RECEIVE :app:process{build variant}Manifest
  9. Permission Guard current branch와 base branch를 서로 비교한다 📄 permissions.txt

    List<String> develop branch current branch List<String> process{build variant}Manifest 📄 permissions.txt List<String> process{build variant}Manifest
  10. Permission Guard $ ./gradlew applyPermission $ # Change a permission...

    $ ./gradlew checkPermission 📄 permission.txt -> ->
  11. 라이브러리 업데이트의 숨겨진 변경 PR 에서 보이는 것 vs AndroidManifest

    에 실제로 들어온 것 PR 리뷰어가 보는 것 build.gradle dependencies { - implementation "com.example:sdk:1.0.0" + implementation "com.example:sdk:2.0.0" } AndroidManifest 에 실제로 들어온 것 (PR 에 표시되지 않음) AndroidManifest.xml (merged) <!-- ࢜۽਍ ӂೠ --> <uses-permission name="WRITE_EXTERNAL_STORAGE" /> <!-- exported=true ੋ Activity (৻ࠗ ഐ୹ оמ) --> <activity name="WebViewActivity" exported="true" /> <!-- required=true ੋ ӝӝ ӝמ --> <uses-feature name="hardware.camera" required="true" /> build.gradle 한 줄만 보이고, AndroidManifest 의 실제 변화는 PR 어디에도 표시되지 않습니다.
  12. 변경이 주는 영향 세 항목 모두 영향이 큽니다 항목 영향

    <uses-permission> Play Store 심사 대상이 됩니다. <activity exported="true"> 외부 앱이 이 컴포넌트를 임의로 호출할 수 있게 되어 보안 위험이 생깁니다. <uses-feature required="true"> 해당 기능이 없는 기기는 Play Store 설치 대상에서 제외됩니다. Merged AndroidManifest 는 눈으로 비교하기 어렵고, Play Console 에 올린 뒤에야 문제를 알게 됩니다.
  13. Merged AndroidManifest.xml AGP 가 AndroidManifest 들을 하나로 머지해서 APK ·

    AAB 에 넣습니다 App Manifest app/src/main/ Library Modules :feature:login, ... External AARs androidx.*, Firebase Flavor / BuildType src/release/ AGP Manifest Merger processReleaseMainManifest Merged AndroidManifest.xml → 실제로 APK · AAB 에 들어가는 최종 결과물
  14. Merged Manifest 수동 확인 할 수는 있지만, 매 업데이트마다 눈으로

    비교하는 건 현실적이지 않습니다 app/build/intermediates/merged_manifests/{variant}/AndroidManifest.xml Android Studio 에서 AndroidManifest.xml 을 열고 하단의 'Merged Manifest' 탭을 누르면 최종 결과물을 볼 수 있다. 그런데… 매 의존성 업데이트마다 눈으로 비교하는 건 비현실적이고, 사람의 눈으로 잡는 건 답이 아니다.
  15. Permission Guard 의 아쉬운 점 <uses-permission> 만 추출해서 비교하는 자체

    Gradle 스크립트 • 한계 1: 권한 변경만 알 수 있고, exported activity·service·provider·required feature 변경은 알 수 없습니다. • 한계 2: 어떤 라이브러리가 원인인지 추적할 수 없습니다. 변경된 항목만 보일 뿐 출처를 알기 어렵습니다. • 한계 3: 권한 외 요소까지 확장하려니 정규식 파싱이 복잡해져서 유지보수가 어려웠습니다. 그래서 AndroidManifest 의 모든 요소를 같은 패턴으로 추적할 도구가 필요합니다.
  16. P A R T 0 2 Manifest Shield AndroidManifest 변경

    감지부터 baseline 모듈 활용까지
  17. Manifest Shield Merged AndroidManifest 의 의도치 않은 변경을 감지하는 Gradle

    플러그인 Merged AndroidManifest를 정규화된 텍스트로 변환하고, 이것을 기반으로 변경사항을 감지한다. Dependency Guard 의존성 목록 변경을 감지 Manifest Shield AndroidManifest 요소 변경을 감지
  18. Dependency Guard 와 비교 패턴이 같으니 학습 비용이 거의 없습니다

    기준 Dependency Guard Manifest Shield 추적 대상 의존성 목록 AndroidManifest 요소 Baseline 파일 dependencies/{variant}RuntimeClasspath.txt manifestShield/{variant}AndroidManifest.txt 변경 시 동작 빌드 실패 + diff 빌드 실패 + diff re-baseline ./gradlew dependencyGuardBaseline ./gradlew manifestShieldBaseline
  19. 설치 01 버전 카탈로그에 등록 gradle/libs.versions.toml [versions] manifestShield = "0.1.9"

    [plugins] manifestShield = { id = "io.github.fornewid.manifest-shield", version.ref = "manifestShield" } root build.gradle 에 등록 plugins { alias(libs.plugins.manifestShield) apply false } 03 앱 모듈에 적용 + 설정 যڃ variant ܳ хदೡ૑ plugins { alias libs.plugins.manifestShield } manifestShield { configuration("release") } 02
  20. Baseline 생성 → Git 커밋 $ ./gradlew :app:manifestShieldBaselineRelease → 생성된

    파일: app/manifestShield/releaseAndroidManifest.txt uses-feature: android.hardware.camera uses-permission: android.permission.INTERNET android.permission.ACCESS_NETWORK_STATE activity: com.example.SplashActivity (exported) 이 파일을 Git 에 커밋합니다. PR 의 baseline diff 가 곧 보안 리뷰가 됩니다.
  21. 변경 감지 빌드를 돌릴 때마다 비교 → 다르면 실패 $

    ./gradlew manifestShield Manifest Changed in :app for release/releaseAndroidManifest + android.permission.WRITE_EXTERNAL_STORAGE + com.example.WebViewActivity (exported) If this is intentional, re-baseline using ./gradlew :app:manifestShieldBaselineRelease Or use ./gradlew manifestShieldBaseline to re-baseline in entire project. 의도한 변경이면 baseline 갱신 → 같은 PR 에서 함께 커밋 → 리뷰어가 보안 영향을 같이 검토 의도치 않은 변경이면 어떤 라이브러리가 가져왔는지 추적 → sources=true 로 원인 파악
  22. CI 연동 check 또는 manifestShield task를 별도 step 으로 추가

    # .github/workflows/pr-build.yml steps: - name: Test run: ./gradlew check # .github/workflows/pr-build.yml steps: - name: Manifest Shield run: ./gradlew manifestShield
  23. 사례: 인앱 팝업 차단 목록 자동화 수동으로 관리하던 차단 목록을

    baseline 으로 자동화 manifest-shield 의 baseline 을 차단 목록의 데이터 소스 로 쓰자 문제 인앱 팝업이 광고 SDK 의 전면 광고 위에 오버레이로 떠서 UX 가 망가지는 것을 막기 위해 차단 목록이 필요합니다. 기존: 광고 SDK 액티비티를 차단 목록에 수동 등록 한계: SDK 업데이트로 새 Activity 가 추가되면 누락 위험 모듈 구조 core:ads 광고 SDK 의존성을 모은 라이브러리 core:ads-baseline baseline 을 만드는 최소 app 모듈 feature:ads · AdsPromotionPopupBlocklistInfo 차단 목록을 정의해 Hilt 로 주입 → baseline 갱신과 차단 목록 갱신이 자동으로 동기화됩니다
  24. 사례: 인앱 팝업 차단 목록 자동화 기존 — AdsModule 이

    차단 목록 구현체를 Hilt multibinding 으로 등록 AdsModule.kt // ରױ ݾ۾ ҳഅ୓ܳ multibinding ਵ۽ Set ী ୶о @Module interface AdsModule { @Binds @IntoSet fun bindAdsBlocklist( info: AdsPromotionPopupBlocklistInfo ): PromotionPopupBlocklistInfo } 광고 SDK 의 차단 목록이 PromotionPopupBlocklistInfo Set 에 추가됩니다. 그런데 차단 대상은 어디서 정의할까요?
  25. 사례: 인앱 팝업 차단 목록 자동화 기존 — 구현체 안에

    차단 대상 Activity 를 사람이 직접 나열 AdsPromotionPopupBlocklistInfo.kt class AdsPromotionPopupBlocklistInfo @Inject constructor() : PromotionPopupBlocklistInfo { override fun inAppMessageBlocklist(): Set<Class<*>> = setOf( AdSdkAActivity::class.java, AdSdkBActivity::class.java, Class.forName("com.example.AdSdkCActivity"), ) } 차단 대상 Activity 를 직접 나열합니다. SDK 가 업데이트되어 새 Activity 가 들어와도 사람이 추가해 주지 않으면 누락됩니다.
  26. 사례: 인앱 팝업 차단 목록 자동화 자동화 — manifest-shield 의

    baseline 을 차단 목록의 데이터 소스로 사용 core/ads-baseline/build.gradle.kts manifestShield { configuration("release") { // ׮ܲ ஠పҊܻח ݽف ՍҊ activity ݅ ୶੸ usesPermission = false usesFeature = false activityAlias = false service = false receiver = false provider = false // exported ೙ఠܳ ಽয ݽٚ activity ನೣ exportedOnly = false } } 다른 카테고리는 모두 끄고 activity 만 추적하면, baseline 의 모든 줄이 곧 차단 대상 Activity 가 됩니다.
  27. 사례: 인앱 팝업 차단 목록 자동화 자동화 — manifest-shield 가

    만들어 준 baseline 파일이 곧 차단 목록 core/ads-baseline/manifestShield/releaseAndroidManifest.txt activity: com.adsdk.a.AdSdkAActivity com.adsdk.a.AdSdkAFullscreenActivity com.adsdk.a.AdSdkAOfferwallActivity com.adsdk.b.AdSdkBActivity com.adsdk.b.AdSdkBVideoActivity com.example.AdSdkCActivity com.example.AdSdkCInterstitialActivity ...
  28. 사례: 인앱 팝업 차단 목록 자동화 .claude/rules 로 AI 가

    baseline 과 차단 목록을 함께 갱신하도록 강제 어 떻 게 동 작 하 나 01 paths 패턴 매칭 baseline 또는 BlocklistInfo 경로가 매칭되면 룰이 자동 활성화 02 동기화 규칙 적용 한쪽만 수정하지 않도록 AI 의 행동을 강제 03 검증 명령 실행 :manifestShield 와 :assembleDevDebug 로 AI 가 자체 검증 .claude/rules/manifest-shield.md --- paths: core/ads-baseline/manifestShield/*.txt, AdsPromotionPopupBlocklistInfo.kt --- # baseline ® BlocklistInfo زӝച ӏ஗ ## ੸ਊ ӏ஗ 1. baseline ߸҃ द - ୶о/ઁѢػ Activity ୶୹ - BlocklistInfo ী زੌ ߈৔ 2. BlocklistInfo ߸҃ द - baseline ী ઓ੤ೞח૑ ഛੋ 3. Ѩૐ ./gradlew :core:ads-baseline:manifestShield
  29. P A R T 0 3 내부 동작 AGP 의

    입력을 정규화해 비교하고, 변경이 어디서 왔는지까지 추적
  30. 한 장으로 보는 동작 XML → 정규화 텍스트 → baseline

    비교 → 다르면 빌드 실패 Input Merged Manifest.xml AGP 가 만든 입력 01 ManifestVisitor DOM 파싱 → Kotlin data class 변환 02 정규화 텍스트 toBaselineString() + sorted + distinct 03 Baseline Diff 양방향 차집합 → 변경 감지 변경 없음 → 빌드 성공 변경 감지 → GradleException + diff 메시지
  31. (1) ManifestVisitor: XML → 도메인 객체 AndroidManifest 의 element 를

    Kotlin data class 로 변환 [Input] AndroidManifest.xml <uses-permission android:name="android.permission.INTERNET" android:maxSdkVersion="28" /> <activity android:name="com.example.MainActivity" android:exported="true" /> <uses-feature android:name="android.hardware.camera" android:required="true" /> [Transform] ManifestVisitor fun visit(element: Element) = when (element.tagName) { "uses-permission" -> ManifestPermission(name, maxSdkVersion) "activity" -> ManifestActivity(name, exported, permission) "uses-feature" -> ManifestFeature(name, required) ... } [Output] 도메인 객체 ManifestPermission(name="android.permission.INTERNET", maxSdkVersion=28) ManifestActivity(name="com.example.MainActivity", exported=true, permission=null) ManifestFeature(name="android.hardware.camera", required=true)
  32. (2) 정규화: 텍스트로 만드는 이유 XML 그대로 저장하면 false positive

    diff 가 자주 발생할 수 있습니다 XML 그대로 저장하면 AGP 버전이 바뀌면, attribute 순서·공백 등 달라질 수 있습니다. 의존성 변경이 없어도 false positive diff 가 발생할 수 있습니다. → 도구의 신뢰도가 떨어집니다. plain text 로 정규화하면 의미가 같으면 항상 같은 한 줄입니다. AGP 버전이 바뀌어도 달라지지 않습니다. → baseline diff 가 의미 있는 변화에만 반응하도록 할 수 있습니다 [ੑ۱ XML] <uses-permission android:name="android.permission.INTERNET" android:maxSdkVersion="28" /> ↓ DOM ౵य → ManifestPermission(name, maxSdkVersion=28) ↓ .distinct().sortedBy { it.name } + toBaselineString() [Output] ੿ӏച ೠ ઴ "android.permission.INTERNET (maxSdkVersion=28)"
  33. (3) 단순한 차집합 비교 단순히 차이점만 비교하면 되어서, 순서를 고려한

    알고리즘은 필요 없습니다 val removedLines = expected.filter { !actual.contains(it) } // baseline ী ੓חؘ ૑Ә হ਺ val addedLines = actual.filter { !expected.contains(it) } // ૑Ә ੓חؘ baseline ী হ਺ [Input] expected (baseline) = ["ACCESS_NETWORK_STATE", "INTERNET (maxSdkVersion=28)"] actual (അ੤ ࠽٘) = ["INTERNET (maxSdkVersion=28)", "WAKE_LOCK"] ↓ filter × 2 [Output] removedLines = ["ACCESS_NETWORK_STATE"] ← baseline ী ੓חؘ ૑Ә হ਺ addedLines = ["WAKE_LOCK"] ← ૑Ә ੓חؘ baseline ী হ਺ 정규화 단계에서 이미 정렬된 리스트가 만들어지기 때문에 단순한 차집합으로 충분합니다.
  34. check task 에 묻어가는 검증 한 줄 설정으로 check 가

    돌 때마다 manifest-shield 도 함께 실행됩니다 target.tasks .named(LifecycleBasePlugin.CHECK_TASK_NAME) .configure { dependsOn(guardTask) } → 이미 CI 에서 check 가 돌고 있다면, manifest-shield 도 함께 검증.
  35. 출처 추적: sources = true 옵션 어떤 라이브러리에서 무엇이 들어왔는지까지

    알고 싶다면 이 옵션을 활성화합니다 manifestShield { configuration("release") { sources = true } // ਫ਼द ഝࢿച റ baseline ਸ ׮द ࢤࢿ } baseline 이 출처별로 묶여서 표시됩니다: [:app] uses-permission: android.permission.INTERNET [com.google.android.gms:play-services-auth] activity: com.google.android.gms.auth.api.signin.internal.SignInHubActivity (exported) [androidx.work:work-runtime:2.7.0] uses-permission: android.permission.WAKE_LOCK
  36. 출처 추적: AGP 가 만들어주는 blame log 외부 라이브러리 AndroidManifest

    를 직접 읽지 않고, AGP 가 만들어두는 로그를 활용합니다 위치: app/build/outputs/logs/manifest-merger-<variant>-report.txt # (1) ղ জ ݽٕ (੺؀҃۽) uses-permission#android.permission.INTERNET ADDED from /Users/.../sample/app/src/main/AndroidManifest.xml:4:5-67 # (2) э਷ ೐۽ં౟੄ ׮ܲ ݽٕ ([ ] উী Gradle ҃۽) MERGED from [:sample:module1] /Users/.../module1/src/main/AndroidManifest.xml:7:5-67 # (3) ৻ࠗ ۄ੉࠳۞ܻ ([ ] উী Maven Group ID + Artifact ID + Version ઝ಴) uses-permission#android.permission.WAKE_LOCK ADDED from [androidx.work:work-runtime:2.7.0] /Users/.gradle/caches/.../AndroidManifest.xml:24:5-66 → 정규식으로 첫 줄에서 type#name 을, 두 번째 줄에서 [ ] 안의 출처 를 뽑아내면: Map<String, List<String>> "uses-permission#android.permission.INTERNET" → [":app", ":sample:module1"] "uses-permission#android.permission.WAKE_LOCK" → ["androidx.work:work-runtime:2.7.0"]
  37. 출처 추적: 두 input을 type#name 키로 join 정규화된 텍스트 +

    blame log 의 Map → 출처별 entry 로 변환합니다 elementType XML కӒݺ (৘: uses-permission, activity) entry.name android:name ч (৘: android.permission.INTERNET) val key = "$elementType#${entry.name}" // ex) uses-permission#android.permission.INTERNET val sources = sourceMap[key] ?: listOf("unknown") // blame log ੄ Map ীࢲ ୹୊ ઑഥ for (source in sources) sourceTagEntries[source][tag] += entry.toBaselineString() [Input] INTERNET ӂೠ੉ ف ݽٕী ݽف ࢶ঱ػ ҃਋ entry: ManifestPermission(name="android.permission.INTERNET") sourceMap["uses-permission#android.permission.INTERNET"] = [":app", ":module1"] ↓ for (source in sources) → ୹୊߹ entry ۽ ߸ജ [Output baseline] [:app] uses-permission: android.permission.INTERNET [:module1] uses-permission: android.permission.INTERNET ← э਷ ઴੉ নଃী 두 모듈이 다 선언했으니 baseline 에도 둘 다 표시됩니다.
  38. 내부 동작 정리 manifest-shield 의 동작 원리 4가지 구분 설명

    (1) 정규화 XML 의 형식적 차이(공백·순서)에 흔들리지 않도록 의미 있는 정보만 추출해 plain text 한 줄로 만듭니다. AGP/JDK 가 바뀌어도 같은 결과가 나옵니다. (2) 양방향 차집합 정렬된 두 리스트를 비교할 때 Myers diff 같은 알고리즘은 필요 없습니다. filter 두 번이면 추가/제거 항목이 정확히 나옵니다. (3) check 의존성 Gradle 의 check task 에 의존성을 한 줄 걸어 두는 표준 관용구입니다. CI 에서 check 가 돌고 있다면 manifest-shield 도 함께 검증됩니다. (4) 출처 추적 AGP 가 함께 만들어두는 blame log 텍스트와 type#name 키로 join 해서, 어떤 라이브러리·모듈에서 왔는지까지 baseline 에 표시합니다.
  39. P A R T 0 4 AI 로 만들기 AI

    와 함께 만들고, 다시 AI 의 검토 범위를 구체화한다
  40. 누구나 만들 수 있는 시대 AI 와 함께라면 이런 플러그인을

    직접 만들 수 있습니다 01 기존 오픈소스 잘 만들어진 오픈소스를 시작점으로 삼아 처음부터 새로 만들지 않습니다 02 AI 코드 작성·테스트·문서화 비용을 AI 가 크게 낮춥니다 03 Dogfooding 프로덕션에 적용해보면서 사람이 개선 방향을 결정합니다
  41. (1) 시작점: 검증된 오픈소스 dependency-guard 를 통째로 복사해서 manifest-shield 로

    확장 기 존 오 픈 소 스 dependency-guard 의존성 baseline + 빌드 시 비교 + 변경 시 실패 이미 검증된 패턴 - Gradle 플러그인 구조 - baseline 파일 비교 알고리즘 - re-baseline task - 자동 통합 (check.dependsOn) 확 장 manifest-shield AndroidManifest baseline + 출처 추적까지 확장 새로 만든 부분 - AGP 빌드 산출물 파싱 - XML → 정규화 텍스트 변환 - blame log 로 출처 추적 - AndroidManifest 도메인 옵션 처음부터 새로 만들지 않습니다. 검증된 오픈소스의 구조를 통째로 가져와 도메인만 바꿉니다.
  42. (2) AI 로 코드 작성 비용 낮추기 코드 작성·테스트·문서화는 AI

    가, 방향 결정·반려·검토는 사람이 단계 사람 AI 초기 구현 dependency-guard 통째 복사 결정 Claude Code 로 AndroidManifest 도메인 맞춤 변환 교차 검토 의도와 일치 여부 확인 Claude Opus + Gemini Pro 로 상호 리뷰 문서 / PR 핵심 메시지 · 반려 / 머지 PR 본문 · 테스트 · 문서 자동 생성 릴리즈 결과 확인 Maven Central 자동 publish
  43. (3) Dogfooding 으로 방향 잡기 프로덕션에 적용해보면서 사람이 개선 방향을

    결정 관 찰 모든 항목을 추적하면 의미 없는 변경까지 잡힙니다 글로벌웹툰 baseline 에 잡힌 라인 수 (activity·service·receiver·provider) 253 → 20 (8%) 그중 보안 관점에서 의미 있는 항목은 일부였습니다. 결 정 Play Store 심사·보안 기준으로 디폴트 재정의 exportedOnly 외부 호출 가능한 컴포넌트만 requiredOnly Play Store 필터링에 영향 주는 feature 만 queries · startup 보안 무관한 영역은 기본값에서 제외 AI 가 코드를 만들 수는 있어도, 무엇이 노이즈인지 판단하는 건 사람의 몫입니다.
  44. Baseline 패턴의 확대 이외에도 추적이 필요한 영역이 더 있습니다 중복

    리소스 res / assets / classes / .so 의존성 사이에서 같은 리소스가 충돌하면 빌 드 결과가 예상과 달라질 수 있습니다. 어떤 파일이 최종 APK 에 들어갔는지 추적이 필요 할 수 있습니다. fornewid/highlander R8 rules ProGuard / R8 keep rules 라이브러리가 주입한 keep rule 이 누적되면 난독화·최적화가 의도치 않게 약해질 수 있습 니다. merged rule set 의 변경 감지가 필요 할 수 있습니다. fornewid/proguard-shield 라이선스 য়೑ࣗझ ۄ੉ࢶझ ഐജࢿ transitive 의존성으로 GPL 같은 비호환 라 이선스가 들어오면 제품 전체가 영향을 받을 수 있습니다. 허용 라이선스 외 진입을 차단 해야 할 수 있습니다. cashapp/licensee
  45. baseline + .claude/rules = AI 의 검토 가이드 두 input이

    결합되어 AI 의 행동을 명확히 정의합니다 b a s e l i n e AI 가 읽을 명시적 입력 정렬된 plain text 로 떨어진 baseline 은 AI 가 자연어로 설명하기 좋은 포맷입니다. PR 의 baseline diff 가 그대로 검토 입력이 됩니다. . c l a u d e / r u l e s AI 가 따를 명시적 규약 baseline 변경 시 무엇을 함께 갱신해야 하는지를 markdown 한 장으로 정의합니다. AI 는 이 규약을 따라 자율 수행합니다. 라이브러리 업데이트 PR → baseline 이 무엇이 바뀌었는지 보여주고 → .claude/rules 가 무엇을 해야 하는지 알려주면 → AI 가 차단 목록 갱신·검증까지 자율 수행합니다.
  46. 정리 • 라이브러리 업데이트만으로도 의존성, AndroidManifest 등 다양한 영역에 의도치

    않은 변경이 발생할 수 있습니다. • Dependency Guard와 같은 baseline 패턴을 적용해 의도치 않은 변경을 차단할 수 있습니다. • AndroidManifest를 정규화된 텍스트로 변환하고 필요한 요소만 필터링하여, baseline을 효과적으로 관리할 수 있습니다. • 라이브러리 업데이트 시, baseline을 바탕으로 AI가 변경사항을 심층 검토하도록 자동화 할 수 있습니다. • AGP가 생성하는 두가지 input을 이용하여, 변경사항의 출처까지 추적할 수 있습니다. • 이외에도 리소스 중복, Proguard 규칙 등 다양한 부분에 baseline 패턴을 적용해볼 수 있습니다. • 이미 잘 구현된 오픈소스 프로젝트를 시작점으로 하면, AI 도구를 구축하는데 리소스를 절약할 수 있습니다. • AI 와 함께 만든 도구가 다시 AI의 검토 범위를 구체화하는데 사용됩니다.