LogDrop Taint for Android
Taint data-flow analysis for Android, Kotlin, and Java source code
LogDrop Taint follows a value from a source—user input, an Intent, a server response, or a constant in code—to a sink and reports values that arrive unsanitised with a label accepted by that sink.
It also checks for hardcoded credentials and unsafe manifest network configuration.
The analyzer opens no network connection: not for licensing, usage counting, or telemetry. The licence is verified offline. Your repository is never uploaded. A report contains finding metadata, file paths, locations, and the relevant data-flow trace. By default it also contains the offending line with two lines of context on either side—never the whole file or the rest of your source.
Set snippets: "false" to remove every source line from the report. Sending a report to the LogDrop panel is a separate step and stays off unless you enable it.
The iOS counterpart emits the same SARIF report shape. Both analyzers use the same .logdrop.json format and accept the same signed suppressions format. Most configuration can be shared; custom sinks use platform-specific rule ids and must match the analyzer that reads them.
GitHub Actions
name: Security scan
on: [pull_request]
jobs:
taint:
runs-on: ubuntu-latest # Linux. No Mac or Android SDK.
permissions:
contents: read
security-events: write # only if you upload to Code Scanning
steps:
- uses: actions/checkout@v4
- uses: initialcodess/logdrop-taint-android-action@v0
with:
license: ${{ secrets.LOGDROP_LICENSE }}
path: . # scan the repository, including every app module
fail-on-findings: "true"The analyzer runs on a standard Linux runner. It requires no macOS, Android SDK, or Gradle installation.
Run it without GitHub
The analyzer is a single JAR and needs only a JVM.
# Download and verify once; change the version when needed
V=v0.8.2
curl -fsSL -o install.sh \
"https://raw.githubusercontent.com/initialcodess/logdrop-taint-android-action/$V/examples/install-logdrop-taint.sh"
chmod +x install.sh
LOGDROP_VERSION=$V LOGDROP_DIR="$HOME/logdrop" ./install.sh
# Run it
export LOGDROP_LICENSE="LOGDROP...."
java -jar "$HOME/logdrop/logdrop-taint-android-$V.jar" . \
--sarif report.sarif --verbose --fail-on-findingsUse it in several environments:
- Developer machines: scan your own source before pushing.
- Build servers: add the commands to Jenkins, TeamCity, Bitrise, or another JVM-capable host. Exit code
1means findings. - Gradle tasks: use the ready-made integration in
examples/gradle. - Self-hosted runners: run the Action as-is.
The output is standard SARIF 2.1.0 and works with SARIF viewers or your own dashboard. The repository's examples/ directory includes recipes for CircleCI, GitLab CI, Jenkins, Bitrise, and Gradle.
Where findings appear
All three views work on every GitHub plan:
- Pull request annotation: the finding appears above the relevant line in Files changed.
- Job summary: the run page shows a location, rule, and finding table.
- CI gate: with
fail-on-findings: "true", findings block the merge.
If Code Scanning is enabled, SARIF is uploaded there too. It is free for public repositories and depends on GitHub Code Security licensing for private repositories. When upload is unavailable, the step warns and continues; it does not break the build.
Test code is skipped
Test fixtures often contain fake credentials that look exactly like real credentials. Files under src/test or src/androidTest and files named *Test.kt or *Test.java are excluded by default.
The exclusion is printed clearly:
Skipped 214 test file(s). Use --include-tests to scan them.A file named directly on the command line is always scanned. Set include-tests: "true" to scan the full test tree.
What it finds
| Scenario | CWE |
|---|---|
| User, network, or Intent data reaches a WebView unsanitised | CWE-79 |
| User or network data is built into a shell command | CWE-78 |
| User or network data is concatenated into a SQL query | CWE-89 |
| Personal data is written to the log | CWE-532 |
| Personal data or credentials are stored locally in clear text in SharedPreferences, DataStore, Room, SQLite, or a file | CWE-312 |
| An API key, token, or secret is written directly into source | CWE-798 |
A key hardcoded in source reaches SecretKeySpec | CWE-321 |
| Personal data or credentials are copied to the clipboard | CWE-200 |
Untrusted data is built into a selection clause instead of using selectionArgs | CWE-943 |
| The manifest permits cleartext HTTP for every host without a network security configuration | CWE-319 |
EncryptedSharedPreferences is recognised as the fix and is never reported, even though its edit().putString(...) calls resemble regular SharedPreferences.
The analyzer also uses the name a value is read from. For example, cvvEditText.text is a CVV, while searchEditText.text is ordinary user input and does not become personal data merely because a user typed it.
Flows are followed across functions and files. A value that passes through a recognised sanitizer is not reported for the matching label. Sanitisation is label-specific: escaping HTML prevents injection but does not make an email non-personal, so logging that escaped email is still a finding.
Send reports to the LogDrop panel
Panel reporting is optional and off by default. Enable it to track findings over time, view binary and source scans for the same app together, and carry false-positive decisions across scans.
- uses: initialcodess/logdrop-taint-android-action@v0
with:
license: ${{ secrets.LOGDROP_LICENSE }}
path: .
bundle-id: com.company.app
panel-url: https://analyze.logdrop.ioSending requires panel-url, license, and bundle-id together. Without all three, nothing is sent. The bundle id must be registered for the project; an unknown id is rejected so a typo cannot fail silently.
Every recipe in examples/ ends with examples/report-to-panel.sh, which performs the same POST from CircleCI, GitLab, Jenkins, Bitrise, or a developer machine. It does nothing until PANEL_URL, LOGDROP_LICENSE, and BUNDLE_ID are all present.
The analyzer itself still contacts nothing. Sending is a separate step that authenticates with the licence and uploads the SARIF together with the bundle id, app name, version/ref, and commit metadata used to group the report. SARIF includes finding metadata, file paths, locations, flow traces, and—when snippets are enabled—the offending line with context. It never uploads repository files or the complete source tree. Disable snippets with snippets: "false", or disable sending entirely by omitting panel-url.
If the panel is unreachable, the action warns and leaves the scan result, annotations, summary, and exit code unchanged.
Adapt it to your codebase
Add .logdrop.json at the repository root to teach the analyzer about project-specific sanitizers, field names, logging wrappers, and excluded paths. In a shared iOS/Android repository, keep platform-specific custom sinks in the configuration used by the matching analyzer; their rule ids are not interchangeable.
{
"sanitizers": { "makeSafe": ["user-input"], "maskEmail": ["pii"] },
"sources": { "nationalId": "pii", "customerEmail": "pii" },
"sensitiveNames": { "sifre": "pii", "kartNo": "pii" },
"sinks": { "secret": { "rule": "ANDROID-TAINT-PII-LOG", "accepts": ["pii"] } },
"passthrough": ["normalise"],
"exclude": ["vendor/", "generated/"]
}| Field | What it does |
|---|---|
sanitizers | Defines your own sanitising functions and the labels they remove. |
sources | Defines project-specific personal-data fields such as nationalId. |
sensitiveNames | Defines your own names for sensitive inputs, including non-English names. |
sinks | Maps your own wrapper, such as a logging class, to a LogDrop rule and accepted labels. |
passthrough | Lists helpers that transform a value while preserving its taint. |
exclude | Lists path fragments to skip, such as vendored or generated code. |
Valid labels are user-input, hardcoded-secret, pii, and credential.
An invalid configuration is never ignored silently. Unknown fields, rules, or labels stop the scan with exit code 3 and list the valid options.
Silence a reviewed finding
Sometimes a finding is real code but is not a problem in its specific context. Mark it as a false positive in the LogDrop panel, download .logdrop-suppressions.json, and commit the file at the repository root. LogDrop signs the file and the analyzer verifies it offline.
{
"version": 1,
"suppressions": [
{
"fingerprint": "a3f1c0d92b74e518",
"reason": "Test double; this password is not a real one",
"by": "ayse@example.com",
"at": "2026-08-26"
}
],
"signature": "…"
}Signed suppressions make each decision explicit and reviewable. The readable file remains in your repository so pull request reviewers can see what is being suppressed and why. Add or remove suppressions through the panel and download a fresh signed file instead of editing fingerprints or the signature by hand.
One file covers both platforms. If the repository also contains an iOS app, the same panel-signed file is accepted by both analyzers.
A suppressed finding is not deleted. It remains in SARIF with its reason and is displayed as closed by Code Scanning and the LogDrop panel:
LogDrop Taint: 4 finding(s) (1 suppressed) → logdrop-taint.sarifSuppressed findings do not fail the build. If a file cannot be verified—because it was edited, signed with another key, or expired—the file is ignored, all findings return, and the reason is printed.
A suppression follows the finding's code fingerprint rather than its line number. Moving the code normally preserves the decision; changing the relevant code makes the finding appear again for review.
Do not use exclude to clear an individual finding. It drops the whole path and can silently hide every future finding in that file. Reserve it for code you do not own, such as vendored dependencies.
Inputs
| Input | Default | Description |
|---|---|---|
license | — | Required licence key. Keep it in a secret. |
path | . | File or directory to scan. |
fail-on-findings | false | Fail the step when findings exist. |
annotations | true | Add inline annotations to the pull request. |
snippets | true | Include the offending line and ±2 context lines. With false, no code fragment leaves the runner. |
include-tests | false | Scan test code as well. |
upload-sarif | true | Attempt to upload the report to Code Scanning. |
sarif-file | logdrop-taint.sarif | SARIF output path. |
repo-root | github.workspace | Root used to make SARIF paths relative. |
panel-url | empty | Panel address. Empty means nothing is sent. |
bundle-id | empty | Registered application id; required when panel-url is set. |
analyzer-version | tested release | Analyzer version bundled and tested with the Action release. |
Outputs are findings (the count) and sarif-file (the report path).
Exit codes
| Code | Meaning | CI response |
|---|---|---|
0 | Clean | Continue. |
1 | Findings, when fail-on-findings is enabled | Fail the build and review the findings. |
2 | Licence missing, invalid, or expired | Fail, but do not report a source vulnerability. |
3 | Invalid arguments or .logdrop.json configuration | Fail and correct the setup. |
Keeping codes 1 and 2 separate matters: an expired licence is not evidence that the application has a vulnerability.
Requirements and licence
The analyzer requires JVM 17 or newer—the same runtime used by current Android Gradle Plugin versions. It needs no Android SDK, Gradle installation, or macOS host.
LogDrop Taint is commercial software distributed as a compiled analyzer. Its time-limited key is verified offline: the program contacts no licensing server, does not count usage, and reports to nobody. It warns 14 days before expiry.
To obtain a key, contact satis@initialcode.io.
Repository: initialcodess/logdrop-taint-android-action
.png)