feat: detect Koin DI and HMAC request-signing schemes

Two gaps in the previous coverage:

1. Koin was not mentioned anywhere — Hilt/Dagger got a full section in
   call-flow-analysis.md but Koin (the dominant DI in KMP and a large
   share of Kotlin-only Android apps) had zero patterns. Add a Koin
   subsection with the runtime-DSL patterns (module {}, single<>,
   factory<>, viewModel<>, by inject, by viewModel) plus the practical
   trick for resolving an interface to its impl after R8 obfuscation:
   intersect "files that import org.koin.core.module" with "files that
   reference the interface name".

2. The --auth mode caught Bearer / API-key / OAuth header patterns but
   missed HMAC and other request-signing schemes. A hardcoded HMAC
   secret embedded in an APK is a security finding worth surfacing —
   the same kind of authority the user gets is the same authority a
   decompiler grants to anyone. Add patterns for:

     * JCA primitives:  HmacSHA{1,256,512}, Mac.getInstance(...),
       SecretKeySpec(...), Signature.getInstance(...)
     * Header conventions: X-Signature, X-Hmac, X-Amz-Signature,
       X-Client-Authorization, AWS4-HMAC, signRequest(), signaturev2/3
     * Likely secret-bearing identifiers: app_secret, client_secret,
       signing_key, hmac_secret, consumer_secret, private_key
     * Ktor BearerTokens / loadTokens / refreshTokens DSL

These survive R8 because the JCA and Ktor APIs are public and not
shrunk. On a real-world app with a homegrown HMAC scheme they pinpoint
the signing class and its hardcoded key directly.
This commit is contained in:
Michał Tajchert 2026-04-29 01:26:40 +02:00
parent 2e6fc63453
commit ec2b14c171
2 changed files with 58 additions and 7 deletions

View File

@ -84,9 +84,9 @@ Look for:
- Firebase/analytics initialization
- Base URL configuration
## 5. Dependency Injection (Dagger / Hilt)
## 5. Dependency Injection
Modern Android apps use DI. Trace bindings to find implementations:
### Dagger / Hilt
```bash
# Hilt modules
@ -102,10 +102,43 @@ grep -rn '@Component\|@Subcomponent' sources/
grep -rn '@Inject' sources/
```
To trace a call flow through DI:
1. Find where an interface is used (e.g., `ApiService` injected into a repository)
2. Find the `@Provides` or `@Binds` method that creates the implementation
3. Follow the implementation to the actual HTTP call
### Koin
Koin is the dominant DI framework in Kotlin Multiplatform and a large
share of Kotlin-only Android apps. It uses a runtime DSL rather than
compile-time generated factories, so the search patterns are different:
```bash
# Confirm Koin is actually wired up
grep -rn 'org\.koin\.' sources/
# DI module declarations
grep -rn 'fun [A-Za-z]\+Module\|module\s*{\|module(' sources/
# Bindings inside a module DSL
grep -rn 'single\s*[<{(]\|factory\s*[<{(]\|viewModel\s*[<{(]\|scoped\s*[<{(]\|singleOf\|factoryOf' sources/
# Resolution call-sites (where a binding is consumed)
grep -rn '\bget\s*<\|\binject\s*<\|by\s\+inject\b\|by\s\+viewModel\b\|getKoin' sources/
```
After R8, every binding lambda becomes an anonymous
`Function2<Scope, ParametersHolder, T>` impl. To find the binding for an
interface `Foo`, look for files that contain both a Koin import / module
DSL marker and a reference to `Foo`:
```bash
grep -rln 'org\.koin\.core\.module' sources/ | xargs grep -l 'Foo'
```
### Trace through DI
1. Find where an interface is used (e.g. `ApiService` injected into a
repository).
2. Find the `@Provides` / `@Binds` method (Hilt) **or** the
`single { ... }` / `factory { ... }` block (Koin) that creates the
implementation.
3. Follow the implementation to the actual HTTP call.
## 6. Find Constants and Configuration

View File

@ -226,9 +226,27 @@ fi
# --- Auth patterns ---
if [[ "$SEARCH_ALL" == true || "$SEARCH_AUTH" == true ]]; then
section "Authentication & API Keys"
run_grep -i '(api[_-]?key|auth[_-]?token|bearer|authorization|x-api-key|client[_-]?secret|access[_-]?token)'
run_grep -i '(api[_-]?key|auth[_-]?token|bearer|authorization|x-api-key|client[_-]?secret|access[_-]?token|refresh[_-]?token)'
# Request-signing schemes: a hardcoded HMAC / RSA secret in an APK is a
# security finding worth surfacing prominently. These patterns catch the
# common shapes of homegrown / SDK-issued request signers.
section "Request Signing (HMAC / signature schemes)"
run_grep '(HmacSHA(1|256|512)|Mac\.getInstance\("Hmac|SecretKeySpec\(|Signature\.getInstance\()'
run_grep -i '(x-signature|x-client-authorization|x-amz-signature|x-hmac|aws4-hmac|signRequest|signatureFor|computeSignature|signaturev[0-9])'
# Hardcoded high-entropy strings adjacent to "secret"/"key" assignments
# are the canonical leaked-credential pattern.
section "Possible Hardcoded Secrets / Keys"
run_grep -i '(app[_-]?secret|client[_-]?secret|signing[_-]?key|hmac[_-]?secret|consumer[_-]?secret|private[_-]?key)'
section "Base URLs and Constants"
run_grep -i '(BASE_URL|API_URL|SERVER_URL|ENDPOINT|API_BASE|HOST_NAME)'
# Ktor BearerTokens / refresh DSL — common on Kotlin apps and lives on
# Ktor's public API, so it survives R8 unchanged.
section "Ktor Auth (Bearer + Refresh)"
run_grep '(BearerTokens|loadTokens\s*\{|refreshTokens\s*\{|\bbearer\s*\{)'
fi
echo