---
name: appguard-setup
description: Add, configure, or upgrade the AppGuard Android runtime-integrity SDK (pro.paphitis.appguard — Gradle plugin + AAR) in an Android app. Covers dependencies, the licence key, the appGuard { } policy block, SecureApplication / SecureActivity wiring, signing-certificate whitelisting, manifest and Play-policy implications, and verifying the build. Use when the user asks to install, integrate, set up, configure, or upgrade AppGuard, or mentions pro.paphitis.appguard, appGuard { }, SecureApplication, SecureActivity, or AppGuard root/Frida/tamper detection.
---

# AppGuard setup

AppGuard is a commercial Android security SDK. It ships as two artifacts that **must carry the
same version**:

| Artifact | Coordinates | Version |
|---|---|---|
| Gradle plugin | plugin id `pro.paphitis.appguard` | `0.1.9` |
| Library (AAR) | `pro.paphitis.appguard:android-app-guard` | `0.1.9` |

Both are on Maven Central, and neither needs credentials. The build is gated by a **licence key**
that the plugin verifies offline while Gradle configures the project.

This skill is written for **0.1.9**. If the user asks for a different version, or 0.1.9 doesn't
resolve, check the published versions at
`https://repo1.maven.org/maven2/pro/paphitis/appguard/android-app-guard/maven-metadata.xml` and
**ask** which one to use. Either way, both artifacts get the same version.

Integrating it goes wrong in ways that hurt real users. A missing certificate fingerprint, for
example, makes the release build terminate itself on every launch. So this skill works in five
phases, and it **asks the user instead of guessing** whenever something is theirs to supply or
decide.

```
1 Discover (read-only) → 2 Ask (one batch) → 3 Apply → 4 Verify → 5 Hand-off report
```

Full DSL/API reference: [reference.md](reference.md). Error-message lookup: [troubleshooting.md](troubleshooting.md).

---

## Hard rules

1. **Never invent** a licence key, certificate fingerprint, applicationId suffix, or Play
   Console value. When one is missing, ask for it (Phase 2), or leave a clearly marked `TODO`
   and list it in the hand-off report. **Never put a placeholder string in
   `allowedSigningCertificates`.** An unparseable entry crashes the app at launch, just like an
   empty list (see Q4). Leave the list empty with a `TODO` comment instead.
2. **Never commit the licence key** to version control unless the user explicitly confirms that
   the repository is private and that they want it there. Never echo the full key back in chat.
   Quote only its first 12 characters.
3. **Keep the plugin and AAR versions identical.** Use one version-catalog entry or one variable
   for both.
4. Apply the plugin **only to `com.android.application` modules**. On a library module, the
   build fails on purpose.
5. **Never weaken a release policy** (`ENFORCED` → `WARNING`/`ALLOWED`) or disable
   `checkManifest` without the user's explicit confirmation. The defaults are strict on purpose.
6. Never add `tools:overrideLibrary` to get around `minSdk`. Never add broad `-keep` rules for
   `pro.paphitis.appguard.**`. Never wrap `DeviceIntegrityException` in a catch that keeps the
   app running.
7. **Preserve** everything already in the user's Application class and activities. Change the
   superclass and add overrides; don't rewrite their logic.

---

## Phase 1 — Discover (read-only; do not edit anything yet)

Collect these facts from the project. Only ask the user about something you cannot determine
from the files.

| # | Fact | Where to look |
|---|---|---|
| D1 | App module(s): the ones applying `com.android.application` | `*/build.gradle(.kts)` |
| D2 | Build script language: Kotlin DSL or Groovy | file extension |
| D3 | Version catalog in use? | `gradle/libs.versions.toml` |
| D4 | AGP version | catalog / root `build.gradle(.kts)` |
| D5 | `compileSdk`, `minSdk` | app `android { }` |
| D6 | Gradle JDK | `gradle/gradle-daemon-jvm.properties`, `org.gradle.java.home`, IDE settings |
| D7 | `mavenCentral()` present in **`pluginManagement.repositories`** and in dependency repositories? Any corporate mirror? | `settings.gradle(.kts)` |
| D8 | `applicationId`, every `applicationIdSuffix` (build types *and* flavours), all build types | app `android { }` |
| D9 | Signing configs per build type (who signs release / staging / QA builds) | app `signingConfigs`, `buildTypes` |
| D10 | Existing `Application` subclass and its superclass; `android:name` on `<application>` | manifest, sources |
| D11 | Activities, their superclasses, and whether any override `onStart` or `onWindowFocusChanged` | sources |
| D12 | App theme parent (AppCompat-derived or not) | `res/values*/themes.xml`, manifest `android:theme` |
| D13 | Compose? (`ComponentActivity` + `setContent`) | sources |
| D14 | Merged-manifest risks: `usesCleartextTraffic="true"`, `debuggable`, `allowBackup`, exported components without permissions | manifest(s) |
| D15 | ABI restrictions (`abiFilters`, `splits.abi`, `packaging.jniLibs.excludes`) | app `android { }` |
| D16 | Existing AppGuard setup (upgrade case): current versions, `appGuard { }` block, `.sig` files, `appguard.license` | build files, module/root dirs |
| D17 | Licence key already available? `appguard.licenseKey` in `~/.gradle/gradle.properties`, `APPGUARD_LICENSE_KEY` env, `appguard.license` file | those locations. Check for presence only; don't print the value |
| D18 | Distribution channel hints (Play-only? enterprise/MDM? other stores?) | README, CI config, Fastlane |

### Compatibility gate

Check these minimums before planning anything. If one fails, **stop and ask** (Phase 2, Q0).
Don't upgrade AGP, Gradle, or compileSdk on your own. Those are project-wide changes.

| Requirement | Minimum | Why |
|---|---|---|
| `minSdk` | **30** | the AAR's minSdk |
| `compileSdk` | **37** | the AAR's metadata (`minCompileSdk=37`) |
| AGP | **9.1.0** | a transitive AndroidX dependency requires it (`checkAarMetadata` fails otherwise) |
| JDK running Gradle | **17** | the plugin's bytecode target |
| Kotlin | **2.2** (AGP 9's built-in Kotlin qualifies) | the library's Kotlin metadata version |
| Module type | `com.android.application` | the plugin configures the APK's `BuildConfig` |

---

## Phase 2 — Ask (one batch, before editing)

Send **one** message, or use your structured-question tool if you have one, containing only the
questions that apply after Phase 1. For each question give: what you need, why, a **recommended
default**, and what happens if they skip it. Number the questions so the user can answer "Q3: b".

Order them blockers first. If a blocking question goes unanswered, you may still do the
non-dependent edits, but **say clearly** what remains blocked.

### Blocking

**Q0 — Compatibility (only if the gate failed).**
> "AppGuard 0.1.9 needs `<requirement>`; this project has `<found>`. Options:
> (a) I upgrade `<thing>` as a separate first step, (b) you upgrade it yourself and I wait,
> (c) stop here. Recommended: (a) or (b). AppGuard can't be installed below this."

**Q1 — Licence key** (skip if D17 found one).
> "AppGuard needs a licence key (it starts with `AG1.`) or every build fails at configuration.
> Do you have one? If not, a free 3-month Development licence is at
> https://appguard.paphitis.pro/request. I can finish the wiring now, and builds will pass once
> the key is in place."

**Q2 — Where to store the key** (once they have one).
> "Where should the key live?
> (a) `~/.gradle/gradle.properties` as `appguard.licenseKey=…`, per developer and outside the repo
> **(recommended)**. I'll add it only if you confirm I may edit that file, or you can paste it
> there yourself;
> (b) environment variable `APPGUARD_LICENSE_KEY`, for CI; add it as a CI secret;
> (c) an `appguard.license` file in the app module or root directory, only if the repo is
> private. I'd add it to `.gitignore` unless you tell me to commit it;
> (d) inline in `appGuard { licenseKey = … }`, not recommended because it ends up in version
> control."

**Q3 — Licence coverage** (ask only if the decoded key doesn't cover every variant; see
[Licence pre-check](#licence-pre-check)).
> "Your licence covers `<ids>`, but these variants would build as `<uncovered ids>` and fail.
> Options: (a) request a licence that adds suffixes `<list>`, (b) change/remove those
> `applicationIdSuffix` values, (c) leave it, and only the covered variants will build."

**Q4 — Release signing certificates.** Whenever the `signature` check is `WARNING` *or*
`ENFORCED` for a build type (release defaults to `ENFORCED`), the app needs at least one
parseable SHA-256 fingerprint. With an **empty list or a placeholder**, `SecureApplication.onCreate`
throws `IllegalArgumentException`, and **the app crashes on every launch, on every device**. With
**wrong** fingerprints, it treats itself as repackaged and exits.
> "Which certificates sign your release builds? I need SHA-256 fingerprints (colon-hex or
> base64):
> - your **upload/release** certificate: run `./gradlew :<app>:signingReport` or
>   `keytool -list -v -keystore <file> -alias <alias>` and paste the SHA-256 line;
> - if you use **Play App Signing**: also the **app signing key certificate** from Play Console →
>   *App integrity → App signing* ("App signing key certificate", SHA-256);
> - any other non-debug build type signed with a different key (staging, QA, internal).
>
> Until you send these, should I (a) leave the list empty with a `TODO`, so release builds
> **crash on launch** until it's filled, a loud failure that can't ship by accident
> **(recommended)**, or (b) set `release { signature = ALLOWED }` for now, which turns
> repackaging detection off and must be re-enabled before shipping? (`WARNING` doesn't help here:
> it still needs the fingerprints.)"

### Confirm (you have a recommended default; ask only when relevant)

**Q5 — `QUERY_ALL_PACKAGES`.** The AAR's manifest adds this permission. Google Play restricts it
to apps with a qualifying use and requires a declaration in Play Console.
> "Keep it (stronger root/hook detection by package scan; you must declare it in Play Console),
> or remove it with `tools:node="remove"` (the file-, process- and memory-based checks keep
> working)? Recommended: **remove** unless your app already qualifies for the permission."

**Q6 — Application class** (only if D10 found a superclass other than `android.app.Application`).
> "`<YourApp>` extends `<Base>`. AppGuard needs `SecureApplication` as the base class, and Kotlin
> allows only one superclass. Is `<Base>` required (e.g. a vendor SDK), or can it become
> `SecureApplication`?"

**Q7 — Screen protection / activities.**
> "`SecureActivity` adds FLAG_SECURE (no screenshots or recording) and anti-tapjacking touch
> filtering. Apply it to (a) all activities, (b) only these sensitive ones: `<list>`, or (c) none?
> Recommended: (a)."

Add these follow-ups whenever they apply:
- the theme isn't AppCompat-derived (D12/D13): "`SecureActivity` is an `AppCompatActivity`.
  Switch the theme parent to `Theme.AppCompat.DayNight.NoActionBar`?";
- an activity overrides `onStart` or `onWindowFocusChanged` (both are `final` in
  `SecureActivity`): "`<Activity>` overrides `<method>`, which it can't do once it extends
  `SecureActivity`. Move that logic to `onResume` or a lifecycle observer, or leave this activity
  unprotected?"

**Q8 — Distribution / installer check.**
> "Is the app distributed **only** through Google Play? If so, `release { installer = ENFORCED }`
> blocks sideloaded copies. With other stores or MDM, keep it `ALLOWED` (the default), or tell me
> the installer package names to trust."

**Q9 — Non-debug build types** (only if there are build types besides `debug`/`release`).
> "`<staging>` uses the **release** policy, which enforces everything, including emulator
> detection and the signature whitelist. Will those builds run on emulators or be signed with
> another key? If so, tell me the extra fingerprint, or confirm you want a looser policy there."

**Q10 — Manifest lint** (only if D14 found cleartext or debuggable in release).
> "AppGuard's release check fails the build on `android:usesCleartextTraffic="true"` / a
> debuggable release. Should I (a) move cleartext to a `network_security_config` scoped to the
> hosts that need it **(recommended)**, (b) remove it, or (c) disable the check with
> `checkManifest = false`?"
>
> For `allowBackup` (a warning only): "Set `android:allowBackup="false"`? This stops `adb
> backup`/cloud backup of app data, and it changes backup behaviour for your users."

**Q11 — Frida / instrumentation in debug** (mention it; no answer needed).
> "FYI: the native Frida watchdog can't be disabled, even in debug builds. If your team attaches
> Frida/objection to its own debug builds, use a build variant without AppGuard for that."

---

## Phase 3 — Apply

Make the edits in this order, confirming the plan in one line before starting. Use the user's
conventions (catalog vs inline, Kotlin DSL vs Groovy). Snippets for both are in
[reference.md](reference.md).

1. **Repositories.** Make sure `mavenCentral()` is in `pluginManagement.repositories` (the plugin
   marker resolves from there) and in `dependencyResolutionManagement.repositories`. If
   there's a corporate mirror, don't touch it. Ask the user to proxy Maven Central.
2. **Plugin + dependency, same version.** With a catalog:
   ```toml
   [versions]
   appguard = "0.1.9"
   [libraries]
   appguard = { module = "pro.paphitis.appguard:android-app-guard", version.ref = "appguard" }
   [plugins]
   appguard = { id = "pro.paphitis.appguard", version.ref = "appguard" }
   ```
   App module: `alias(libs.plugins.appguard)` in `plugins { }`, `implementation(libs.appguard)`.
   Without a catalog: `id("pro.paphitis.appguard") version "0.1.9"` and
   `implementation("pro.paphitis.appguard:android-app-guard:0.1.9")`.
3. **Licence key.** Store it as answered in Q2. Never inline it by default.
4. **`appGuard { }` block** in the app module. Start from the defaults and only change what the
   user decided:
   ```kotlin
   import pro.paphitis.appguard.gradle.SecurityPolicy   // top of the file

   appGuard {
       allowedSigningCertificates = listOf<String>(
           // TODO(appguard): release SHA-256 fingerprints. Until set, release builds crash on launch
       )
       debug {
           secureWindow = false        // screenshots while developing; remove if unwanted
       }
       release {
           // Defaults: signature/debugger/emulator/root/hooking ENFORCED, installer ALLOWED.
       }
   }
   ```
   Only write lines that differ from the defaults, plus the ones the user asked for. The defaults
   are listed in [reference.md](reference.md#policy-defaults).
5. **Application class.** Change the superclass to `SecureApplication`, keeping every existing
   member, annotation (`@HiltAndroidApp` works), and interface. Add these overrides:
   ```kotlin
   class MyApp : SecureApplication() {
       override val securityPolicies = SecurityPolicies.parse(BuildConfig.APPGUARD_POLICIES)
       override val allowedSigningCertificates =
           BuildConfig.ALLOWED_SIGNING_CERTIFICATES.split(",").map { it.trim() }.filter { it.isNotEmpty() }
       override val screenProtection = ScreenProtection(
           secureWindow = BuildConfig.APPGUARD_SECURE_WINDOW,
           touchFiltering = BuildConfig.APPGUARD_TOUCH_FILTERING,
       )
       override val runtimeCheckIntervalSeconds = BuildConfig.APPGUARD_RUNTIME_CHECK_INTERVAL_SECONDS
       // Harmless when empty. Picks up appguard-config-<buildType>.sig files if the user adds them later.
       override val signedPolicyConfig = BuildConfig.APPGUARD_SIGNED_POLICY_CONFIG
       // ...existing members unchanged...
   }
   ```
   If it overrides `onCreate()`, it **must still call `super.onCreate()` first**, because that's
   where the checks run. If no Application class exists, create one in the app's base package
   and set `android:name` on `<application>`.
6. **Activities** (per Q7). Change the superclass to `pro.paphitis.appguard.SecureActivity`, then
   **move the body of `onCreate` into `onSecureCreate`**, leaving out the `super.onCreate` call:
   ```kotlin
   class MainActivity : SecureActivity() {
       override fun onSecureCreate(savedInstanceState: Bundle?) {
           setContent { AppTheme { MainScreen() } }   // or setContentView(...)
       }
   }
   ```
   Fix the theme parent if the user agreed (Q7 follow-up). `onDeviceNotSecure()` can be
   overridden to show a blocked screen; the default is `finishAffinity()`.
7. **Manifest.** Apply the Q5/Q10 answers. To remove the permission:
   ```xml
   <manifest xmlns:tools="http://schemas.android.com/tools" ...>
       <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:node="remove" />
   ```
8. **Optional, only if asked:** event logging (see [reference.md](reference.md#events)), Play
   Integrity, signed policy config, R8 hardening flags.

Don't add ProGuard/R8 rules. The AAR ships its own consumer rules.

---

## Phase 4 — Verify

1. `./gradlew :<app>:assembleDebug`. Configuration errors come first (licence, coverage).
   Look up the message in [troubleshooting.md](troubleshooting.md). If the fix needs something
   only the user has, **stop and ask**. Don't work around it.
2. If the release signing config is available: `./gradlew :<app>:assembleRelease` or
   `bundleRelease`. This also runs AppGuard's release manifest check and signed-config check.
   If it isn't available, tell the user to run it themselves.
3. If a device or emulator is available, install the debug build and launch it. The app should
   stay open. Debug policies report events rather than kill, **except** when Frida is attached.
   Note that release builds with `emulator = ENFORCED` exit on an emulator by design.
4. Don't claim success for a step you didn't run. Say which steps ran and which didn't.

### Licence pre-check

Before the first build, you may decode the key's payload locally to confirm coverage. It's
signed, not encrypted. Take the middle segment of `AG1.<payload>.<signature>` and base64url-decode
it:

```
appId=com.example.app
variants=,.qa,.uat      ← empty first entry = the base id itself
exp=2027-01-01T00:00:00Z
```

Covered IDs are `appId` + each suffix, **exact match**. Compute each variant's final ID
(`applicationId` + flavour suffix + build-type suffix). A common miss is
`debug { applicationIdSuffix = ".debug" }`. Anything uncovered → Q3. If `exp` is within 30 days,
tell the user. There's no grace period.

---

## Phase 5 — Hand-off report (the reverse prompt)

End with this structure, and **always include sections 2 and 3, even when they're empty.**
That's how the user learns what's still theirs to do.

```
## AppGuard 0.1.9 — setup report

### 1. Done
- <file>: <what changed>   (one line each)
- Verified: <commands run and their result>   /   Not verified: <what, and why>

### 2. Action needed from you  ← blocking before a release
- [ ] <e.g. Add release + Play app-signing SHA-256 to allowedSigningCertificates (TODO in app/build.gradle.kts:NN)>
- [ ] <e.g. Add APPGUARD_LICENSE_KEY as a secret in your CI>
- [ ] <e.g. Declare QUERY_ALL_PACKAGES in Play Console, or tell me to remove it>

### 3. Please confirm  ← decisions I made with a default; reply to change any
- <e.g. Kept release installer = ALLOWED (you distribute outside Play?)>
- <e.g. Applied SecureActivity to all 4 activities>
- <e.g. debug { secureWindow = false } so screenshots work while developing>

### 4. Good to know
- <e.g. Frida attached to a debug build kills the process; the release build exits on emulators>
```

If anything in section 2 would make a build crash or terminate itself (an empty fingerprint
list with `signature` enabled, an uncovered variant), **say so in bold at the top of the
report**.
