추적 까지 확장이 필요했던 이유 02 Manifest Shield 글로벌웹툰 앱에 적용한 플러그인 소개 및 SDK Activity 자동 추적 사례 언급 03 내부 동작 AGP 입력을 정규화된 텍스트로 만들어 비교하고, 변경이 어디서 왔는지 추적하는 방법 설명 04 AI 로 만들기 AI와 함께 만든 도구가 다시 AI 의 검토 범위를 구체화
사람의 눈으로 잡지 못했던 경우 담당자가 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 까지 추적하는 건 사실상 어렵습니다.
수정 비용 01 Develop develop branch 개발자가 빌드를 돌리자마자 변경이 잡힙니다. 바로 코드를 고치면 됩니다. 02 QA పझ ױ҅ 운이 좋으면 QA에서 검출됩니다. 테스트 시나리오를 다시 돌려야 합니다. 03 Release Play Console 스토어에 등록하는 시점에 문제가 드러나고, 결국 롤백 외엔 답이 없습니다. 수정 비용 증가 Develop 단계에서 잡으면 수정 비용이 가장 적습니다. 늦게 발견될수록 영향 범위가 커집니다.
01 Snapshot dependencies.txt develop branch 기준의 의존성 목록을 baseline 파일로 보관합니다 02 Compare diff against baseline 현재 의존성 목록과 baseline 을 비교해 차이를 찾습니다 03 Block / Update fail or re-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 까지 올라간 게 즉시 보입니다.
에 실제로 들어온 것 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 어디에도 표시되지 않습니다.
<uses-permission> Play Store 심사 대상이 됩니다. <activity exported="true"> 외부 앱이 이 컴포넌트를 임의로 호출할 수 있게 되어 보안 위험이 생깁니다. <uses-feature required="true"> 해당 기능이 없는 기기는 Play Store 설치 대상에서 제외됩니다. Merged AndroidManifest 는 눈으로 비교하기 어렵고, Play Console 에 올린 뒤에야 문제를 알게 됩니다.
비교하는 건 현실적이지 않습니다 app/build/intermediates/merged_manifests/{variant}/AndroidManifest.xml Android Studio 에서 AndroidManifest.xml 을 열고 하단의 'Merged Manifest' 탭을 누르면 최종 결과물을 볼 수 있다. 그런데… 매 의존성 업데이트마다 눈으로 비교하는 건 비현실적이고, 사람의 눈으로 잡는 건 답이 아니다.
Gradle 스크립트 • 한계 1: 권한 변경만 알 수 있고, exported activity·service·provider·required feature 변경은 알 수 없습니다. • 한계 2: 어떤 라이브러리가 원인인지 추적할 수 없습니다. 변경된 항목만 보일 뿐 출처를 알기 어렵습니다. • 한계 3: 권한 외 요소까지 확장하려니 정규식 파싱이 복잡해져서 유지보수가 어려웠습니다. 그래서 AndroidManifest 의 모든 요소를 같은 패턴으로 추적할 도구가 필요합니다.
기준 Dependency Guard Manifest Shield 추적 대상 의존성 목록 AndroidManifest 요소 Baseline 파일 dependencies/{variant}RuntimeClasspath.txt manifestShield/{variant}AndroidManifest.txt 변경 시 동작 빌드 실패 + diff 빌드 실패 + diff re-baseline ./gradlew dependencyGuardBaseline ./gradlew manifestShieldBaseline
파일: 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 가 곧 보안 리뷰가 됩니다.
./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 로 원인 파악
baseline 으로 자동화 manifest-shield 의 baseline 을 차단 목록의 데이터 소스 로 쓰자 문제 인앱 팝업이 광고 SDK 의 전면 광고 위에 오버레이로 떠서 UX 가 망가지는 것을 막기 위해 차단 목록이 필요합니다. 기존: 광고 SDK 액티비티를 차단 목록에 수동 등록 한계: SDK 업데이트로 새 Activity 가 추가되면 누락 위험 모듈 구조 core:ads 광고 SDK 의존성을 모은 라이브러리 core:ads-baseline baseline 을 만드는 최소 app 모듈 feature:ads · AdsPromotionPopupBlocklistInfo 차단 목록을 정의해 Hilt 로 주입 → baseline 갱신과 차단 목록 갱신이 자동으로 동기화됩니다
차단 목록 구현체를 Hilt multibinding 으로 등록 AdsModule.kt // ରױ ݾ۾ ҳഅܳ multibinding ਵ۽ Set ী ୶о @Module interface AdsModule { @Binds @IntoSet fun bindAdsBlocklist( info: AdsPromotionPopupBlocklistInfo ): PromotionPopupBlocklistInfo } 광고 SDK 의 차단 목록이 PromotionPopupBlocklistInfo Set 에 추가됩니다. 그런데 차단 대상은 어디서 정의할까요?
차단 대상 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 가 들어와도 사람이 추가해 주지 않으면 누락됩니다.
만들어 준 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 ...
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
비교 → 다르면 빌드 실패 Input Merged Manifest.xml AGP 가 만든 입력 01 ManifestVisitor DOM 파싱 → Kotlin data class 변환 02 정규화 텍스트 toBaselineString() + sorted + distinct 03 Baseline Diff 양방향 차집합 → 변경 감지 변경 없음 → 빌드 성공 변경 감지 → GradleException + diff 메시지
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)"
돌 때마다 manifest-shield 도 함께 실행됩니다 target.tasks .named(LifecycleBasePlugin.CHECK_TASK_NAME) .configure { dependsOn(guardTask) } → 이미 CI 에서 check 가 돌고 있다면, manifest-shield 도 함께 검증.
(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 에 표시합니다.
확장 기 존 오 픈 소 스 dependency-guard 의존성 baseline + 빌드 시 비교 + 변경 시 실패 이미 검증된 패턴 - Gradle 플러그인 구조 - baseline 파일 비교 알고리즘 - re-baseline task - 자동 통합 (check.dependsOn) 확 장 manifest-shield AndroidManifest baseline + 출처 추적까지 확장 새로 만든 부분 - AGP 빌드 산출물 파싱 - XML → 정규화 텍스트 변환 - blame log 로 출처 추적 - AndroidManifest 도메인 옵션 처음부터 새로 만들지 않습니다. 검증된 오픈소스의 구조를 통째로 가져와 도메인만 바꿉니다.
가, 방향 결정·반려·검토는 사람이 단계 사람 AI 초기 구현 dependency-guard 통째 복사 결정 Claude Code 로 AndroidManifest 도메인 맞춤 변환 교차 검토 의도와 일치 여부 확인 Claude Opus + Gemini Pro 로 상호 리뷰 문서 / PR 핵심 메시지 · 반려 / 머지 PR 본문 · 테스트 · 문서 자동 생성 릴리즈 결과 확인 Maven Central 자동 publish
결정 관 찰 모든 항목을 추적하면 의미 없는 변경까지 잡힙니다 글로벌웹툰 baseline 에 잡힌 라인 수 (activity·service·receiver·provider) 253 → 20 (8%) 그중 보안 관점에서 의미 있는 항목은 일부였습니다. 결 정 Play Store 심사·보안 기준으로 디폴트 재정의 exportedOnly 외부 호출 가능한 컴포넌트만 requiredOnly Play Store 필터링에 영향 주는 feature 만 queries · startup 보안 무관한 영역은 기본값에서 제외 AI 가 코드를 만들 수는 있어도, 무엇이 노이즈인지 판단하는 건 사람의 몫입니다.
리소스 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
결합되어 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 가 차단 목록 갱신·검증까지 자율 수행합니다.
않은 변경이 발생할 수 있습니다. • Dependency Guard와 같은 baseline 패턴을 적용해 의도치 않은 변경을 차단할 수 있습니다. • AndroidManifest를 정규화된 텍스트로 변환하고 필요한 요소만 필터링하여, baseline을 효과적으로 관리할 수 있습니다. • 라이브러리 업데이트 시, baseline을 바탕으로 AI가 변경사항을 심층 검토하도록 자동화 할 수 있습니다. • AGP가 생성하는 두가지 input을 이용하여, 변경사항의 출처까지 추적할 수 있습니다. • 이외에도 리소스 중복, Proguard 규칙 등 다양한 부분에 baseline 패턴을 적용해볼 수 있습니다. • 이미 잘 구현된 오픈소스 프로젝트를 시작점으로 하면, AI 도구를 구축하는데 리소스를 절약할 수 있습니다. • AI 와 함께 만든 도구가 다시 AI의 검토 범위를 구체화하는데 사용됩니다.