LogDrop Taint for iOS
Taint data-flow analysis for iOS and Swift source code
LogDrop Taint tracks whether data that came from a user—or data meant to stay secret—travels through Swift code and reaches a dangerous destination without being sanitised.
It also checks for hardcoded credentials and unsafe App Transport Security configuration.
Your repository never leaves the runner. The scan runs locally. 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. If you optionally send the report to LogDrop, those snippets are sent with it; repository files and the complete source tree are never uploaded.
Set snippets: "false" if you want reports to contain only the rule and file:line. Not a single line of source code will leave the runner.
The Android counterpart is logdrop-taint-android-action. Both analyzers emit the same report shape and use the same configuration and signed suppression formats. Most .logdrop.json fields can be shared; custom sinks must use rule ids supported by the analyzer that reads them.
GitHub Actions
name: Security scan
on: [pull_request]
jobs:
taint:
runs-on: macos-15
permissions:
contents: read
security-events: write # only if you upload to Code Scanning
steps:
- uses: actions/checkout@v4
- uses: initialcodess/logdrop-taint-action@v1
with:
license: ${{ secrets.LOGDROP_LICENSE }}
path: . # scan the repository, including every app module
fail-on-findings: "true"macos-15 is required for Swift 6+. The analyzer is downloaded prebuilt; nothing is compiled in your project.
Run it without GitHub
The analyzer is a single executable and needs only macOS—no Xcode, Swift toolchain, or Homebrew.
# Download once; change the version when needed
V=v1.23.2
curl -fsSL -O "https://github.com/initialcodess/logdrop-taint-action/releases/download/$V/logdrop-taint-$V-macos-universal.tar.gz"
curl -fsSL -O "https://github.com/initialcodess/logdrop-taint-action/releases/download/$V/logdrop-taint-$V-macos-universal.tar.gz.sha256"
shasum -a 256 -c "logdrop-taint-$V-macos-universal.tar.gz.sha256"
tar -xzf "logdrop-taint-$V-macos-universal.tar.gz"
# Run it
export LOGDROP_LICENSE="LOGDROP...."
./logdrop-taint . --sarif report.sarif --verbose --fail-on-findingsThis is useful in several environments:
- Developer machines: scan your own source before pushing.
- Build servers: add the commands to Jenkins, TeamCity, Bitrise, or a Mac mini. Exit code
1means findings. - fastlane or Xcode: run the executable from a lane or Xcode Run Script phase.
- Self-hosted GitHub runners: use the Action as-is without GitHub macOS per-minute billing.
The --sarif output is standard SARIF 2.1.0. Open it in Xcode or a VS Code SARIF viewer, or send it to your own dashboard. The repository's examples/ directory contains recipes for CircleCI, GitLab CI, Jenkins, Bitrise, fastlane, an Xcode build phase, and local machines.
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 ones. Files under a Tests/ or UITests/ directory and files named *Tests.swift or *Spec.swift are excluded by default.
The exclusion is printed clearly:
Skipped 591 test file(s). Use --include-tests to scan them.A file named directly on the command line is always scanned, regardless of its name.
What it finds
| Scenario | CWE |
|---|---|
User, network, or deep-link data reaches WKWebView unsanitised | CWE-79 |
| An API key, token, or secret is written directly into source | CWE-798 |
| A key hardcoded in source reaches a cryptographic API | CWE-321 |
| Personal data—email, phone, password, card number, PIN, SSN, passport, or date of birth—is logged | CWE-532 |
| Personal data is stored in clear text in a local database, UserDefaults, or Core Data | CWE-312 |
| User or network data is interpolated into SQL instead of being bound | CWE-89 |
Untrusted data is built into an NSPredicate format string instead of being passed as an argument | CWE-943 |
| Personal data or credentials are copied to the system pasteboard | CWE-200 |
| App Transport Security is disabled globally | CWE-319 |
The analyzer also uses the name a value is read from. For example, cvvTextField.text is treated as a CVV, while searchTextField.text is ordinary user input and does not become personal data merely because it was typed by a user.
Flows are followed across functions. A value that passes through a recognised sanitizer such as escapeHTML(...) 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-action@v1
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 in the panel; 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, fastlane, 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 your own sanitizers, field names, logging wrappers, and code that should not be scanned. In a shared iOS/Android repository, platform-specific custom sinks must use rule ids supported by the matching analyzer.
{
"sanitizers": { "makeSafe": ["user-input"], "maskEmail": ["pii"] },
"sources": { "nationalId": "pii", "customerEmail": "pii" },
"sensitiveNames": { "sifre": "pii", "kartNo": "pii" },
"sinks": { "secret": { "rule": "SWIFT-TAINT-PII-LOG", "accepts": ["pii"] } },
"passthrough": ["normalise"],
"exclude": ["Pods/", "Generated/", "Tests/"]
}| 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 before analysis and the error lists 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 file stays readable and remains in the repository, so 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.
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. |
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 |
|---|---|
0 | Clean—no findings. |
1 | Findings, only when fail-on-findings: "true" is enabled. |
2 | Licence missing, invalid, or expired. |
3 | Invalid arguments or .logdrop.json configuration. |
Requirements and licence
The analyzer requires macOS 15 or newer. It links against Apple system libraries, so it runs wherever iOS source is normally built, but it needs no Xcode, Swift toolchain, Homebrew, or project compilation.
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-action
.png)