LogDrop
Taint Analysis

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

.github/workflows/security.yml
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, verify, and run
# 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-findings

Use 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 1 means 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:

  1. Pull request annotation: the finding appears above the relevant line in Files changed.
  2. Job summary: the run page shows a location, rule, and finding table.
  3. 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

ScenarioCWE
User, network, or Intent data reaches a WebView unsanitisedCWE-79
User or network data is built into a shell commandCWE-78
User or network data is concatenated into a SQL queryCWE-89
Personal data is written to the logCWE-532
Personal data or credentials are stored locally in clear text in SharedPreferences, DataStore, Room, SQLite, or a fileCWE-312
An API key, token, or secret is written directly into sourceCWE-798
A key hardcoded in source reaches SecretKeySpecCWE-321
Personal data or credentials are copied to the clipboardCWE-200
Untrusted data is built into a selection clause instead of using selectionArgsCWE-943
The manifest permits cleartext HTTP for every host without a network security configurationCWE-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.

Optional panel upload
- uses: initialcodess/logdrop-taint-android-action@v0
  with:
    license: ${{ secrets.LOGDROP_LICENSE }}
    path: .
    bundle-id: com.company.app
    panel-url: https://analyze.logdrop.io

Sending 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.

.logdrop.json
{
  "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/"]
}
FieldWhat it does
sanitizersDefines your own sanitising functions and the labels they remove.
sourcesDefines project-specific personal-data fields such as nationalId.
sensitiveNamesDefines your own names for sensitive inputs, including non-English names.
sinksMaps your own wrapper, such as a logging class, to a LogDrop rule and accepted labels.
passthroughLists helpers that transform a value while preserving its taint.
excludeLists 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.

.logdrop-suppressions.json
{
  "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.sarif

Suppressed 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

InputDefaultDescription
license—Required licence key. Keep it in a secret.
path.File or directory to scan.
fail-on-findingsfalseFail the step when findings exist.
annotationstrueAdd inline annotations to the pull request.
snippetstrueInclude the offending line and ±2 context lines. With false, no code fragment leaves the runner.
include-testsfalseScan test code as well.
upload-sariftrueAttempt to upload the report to Code Scanning.
sarif-filelogdrop-taint.sarifSARIF output path.
repo-rootgithub.workspaceRoot used to make SARIF paths relative.
panel-urlemptyPanel address. Empty means nothing is sent.
bundle-idemptyRegistered application id; required when panel-url is set.
analyzer-versiontested releaseAnalyzer version bundled and tested with the Action release.

Outputs are findings (the count) and sarif-file (the report path).

Exit codes

CodeMeaningCI response
0CleanContinue.
1Findings, when fail-on-findings is enabledFail the build and review the findings.
2Licence missing, invalid, or expiredFail, but do not report a source vulnerability.
3Invalid arguments or .logdrop.json configurationFail 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

On this page