Thunderbird for Android – Open Source Email App for Android (fka K-9 Mail)
This report summarizes scanner evidence for thunderbird/thunderbird-android, a Kotlin project with 14,166 GitHub stars. SynthScan examined 508,833 lines of code across 7988 source files, recording 221 pattern matches distributed across 15 syntactic categories. The legacy adjusted pattern signal is 0.6 (Low pattern signal); it is not an authorship estimate.
Use the findings to ask whether maintainers can explain intent, verify dependencies, simplify unnecessary scaffolding, and focus review effort. Risk Payload v1 adds ownership, supply-chain, over-engineering, and review-effort vectors when available. These are prompts for human verification, not probabilities, security findings, or measured maintenance costs.
Use each vector to guide manual verification of the underlying findings.
Payload schema: 1.0.0 · Engine: 2.0.0 · Scoring: heuristic-density-v1 · Scan date: 2026-10-31.
Can maintainers explain intent, assumptions, and trade-offs? 43 finding(s).
Are imports declared and package/API names verified? 172 finding(s).
Are wrappers, guards, and repeated structures necessary? 12 finding(s).
Which changes need focused human review? 221 finding(s).
This chart tracks the legacy adjusted pattern score across scan runs. Changes can reflect source changes, scan coverage, or detector/scoring versions; they do not measure AI adoption or prove that a team refactored code. Compare runs only when their scoring versions are compatible. The dashed line tracks the raw pattern-hit count.
Groups findings by configured severity weight. Severity is not a confidence estimate, security rating, or proof that a package is missing. Verify imports against the project's manifests and inspect each finding in its local design context.
This horizontal bar chart shows where configured pattern scores accumulate across directories. Treat the bars as locations to inspect, not as evidence of authorship or code quality. Use the file-level findings to decide whether a directory needs a focused ownership, dependency, complexity, or review check.
The scanner identified 221 distinct pattern matches across 15 syntactic categories. Each entry below represents a discrete location in the source code where the engine recorded a detector match to inspect in context. Expand any category row to inspect the individual file paths, line numbers, code snippets, and the lexical context (CODE, COMMENT, or STRING) in which each match was detected.
Reading the findings table: The Severity column indicates the configured finding weight (CRITICAL / HIGH / MEDIUM / LOW). The Context column identifies whether the match occurred inside executable code, an inline comment, or a string literal — legacy comment-context matches receive a ×1.5 score weight. The ⚡ bolt icon marks clustered matches: three or more patterns within a 10-line window, each receiving an additional ×1.5 density multiplier under the legacy scoring model.
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | …p/k9mail/core/android/common/contact/ContactFixture.kt | 9 | val CONTACT_EMAIL_ADDRESS = "user@example.com".toEmailAddressOrThrow() | CODE |
| LOW | quality/e2e/README.md | 28 | ./launch_tfa_debug.sh --real --email "user@example.com" --account-name "Work" --user-name "User42" test tests/init_with_ | CODE |
| LOW | quality/e2e/tests/mail_compose_simple_message.yml | 17 | BODY: "Lorem ipsum dolor sit amet." | CODE |
| LOW | quality/e2e/tests/mail_compose_simple_message.yml | 17 | BODY: "Lorem ipsum dolor sit amet." | CODE |
| LOW⚡ | …l/common/src/test/java/com/fsck/k9/mail/AddressTest.kt | 122 | "\"()<>[]:,;@\\\\\\\"!#$%&'*+-/=?^_`{}| ~.a\"@example.org", | CODE |
| LOW⚡ | …l/common/src/test/java/com/fsck/k9/mail/AddressTest.kt | 123 | "\" \"@example.org", | CODE |
| LOW⚡ | …l/common/src/test/java/com/fsck/k9/mail/AddressTest.kt | 126 | "#!$%&'*+-/=?^_`{}|~@example.org", | CODE |
| LOW | …l/common/src/test/java/com/fsck/k9/mail/AddressTest.kt | 228 | val address = "alice@example.org" | CODE |
| LOW | …l/common/src/test/java/com/fsck/k9/mail/AddressTest.kt | 241 | val address = parse("alice@example.org")[0] | CODE |
| LOW | …l/common/src/test/java/com/fsck/k9/mail/AddressTest.kt | 242 | val address2: Address = parse("alice@example.org")[0] | CODE |
| LOW | …l/common/src/test/java/com/fsck/k9/mail/AddressTest.kt | 255 | val address = parse("alice@example.org")[0] | CODE |
| LOW | …va/com/fsck/k9/mail/internet/MimeMessageParseTest.java | 45 | checkAddresses(msg.getFrom(), "adam@example.org"); | CODE |
| LOW | …va/com/fsck/k9/mail/internet/MimeMessageParseTest.java | 46 | checkAddresses(msg.getRecipients(RecipientType.TO), "eva@example.org"); | CODE |
| LOW | …va/com/fsck/k9/mail/internet/MimeMessageParseTest.java | 74 | checkAddresses(msg.getFrom(), "adam@example.org"); | CODE |
| LOW | …va/com/fsck/k9/mail/internet/MimeMessageParseTest.java | 75 | checkAddresses(msg.getRecipients(RecipientType.TO), "eva@example.org"); | CODE |
| LOW | …va/com/fsck/k9/mail/internet/MimeMessageParseTest.java | 93 | checkAddresses(msg.getFrom(), "adam@example.org"); | CODE |
| LOW | …va/com/fsck/k9/mail/internet/MimeMessageParseTest.java | 94 | checkAddresses(msg.getRecipients(RecipientType.TO), "eva@example.org"); | CODE |
| LOW | …va/com/fsck/k9/mail/internet/MimeMessageParseTest.java | 122 | checkAddresses(msg.getFrom(), "x@example.org"); | CODE |
| LOW | …va/com/fsck/k9/mail/internet/MimeMessageParseTest.java | 123 | checkAddresses(msg.getRecipients(RecipientType.TO), "y@example.org"); | CODE |
| LOW | …va/com/fsck/k9/mail/internet/MimeMessageParseTest.java | 177 | checkAddresses(msg.getFrom(), "x@example.org"); | CODE |
| LOW | …va/com/fsck/k9/mail/internet/MimeMessageParseTest.java | 178 | checkAddresses(msg.getRecipients(RecipientType.TO), "y@example.org"); | CODE |
| LOW | …va/com/fsck/k9/mail/internet/MimeMessageParseTest.java | 221 | checkAddresses(msg.getFrom(), "x@example.org"); | CODE |
| LOW | …va/com/fsck/k9/mail/internet/MimeMessageParseTest.java | 222 | checkAddresses(msg.getRecipients(RecipientType.TO), "y@example.org"); | CODE |
| LOW | …va/com/fsck/k9/mail/internet/MessageIdGeneratorTest.kt | 18 | setFrom(Address("alice@example.org")) | CODE |
| LOW⚡ | …in/net/thunderbird/app/common/feature/mail/FakeData.kt | 18 | name = "user@example.com", | CODE |
| LOW⚡ | …in/net/thunderbird/app/common/feature/mail/FakeData.kt | 21 | name = "user@example.com", | CODE |
| LOW⚡ | …in/net/thunderbird/app/common/feature/mail/FakeData.kt | 31 | identities = listOf(Identity(email = "user@example.com")), | CODE |
| LOW⚡ | …in/net/thunderbird/app/common/feature/mail/FakeData.kt | 32 | email = "user@example.com", | CODE |
| LOW | …t/thunderbird/app/common/account/AccountCreatorTest.kt | 78 | emailAddress = "user@example.com", | CODE |
| LOW | …t/thunderbird/app/common/account/AccountCreatorTest.kt | 99 | assertThat(result.email).isEqualTo("user@example.com") | CODE |
| LOW | …t/thunderbird/app/common/account/AccountCreatorTest.kt | 108 | assertThat(result.identities.single().email).isEqualTo("user@example.com") | CODE |
| LOW | …t/thunderbird/app/common/account/AccountCreatorTest.kt | 127 | emailAddress = "user@example.com", | CODE |
| LOW | …t/thunderbird/app/common/account/AccountCreatorTest.kt | 158 | emailAddress = "user@example.com", | CODE |
| LOW | …p/common/account/DefaultAccountDefaultsProviderTest.kt | 257 | email = "user@example.com", | CODE |
| LOW | …ccount/data/LegacyAccountLocalDataSourceStorageTest.kt | 136 | email = "user@example.com", | CODE |
| LOW | …common/account/data/DefaultLegacyAccountManagerTest.kt | 382 | email = "user@example.com", | CODE |
| LOW | …ommon/account/data/LegacyAccountLocalDataSourceTest.kt | 144 | email = "user@example.com", | CODE |
| LOW | …/common/account/data/LegacyInMemoryAccountCacheTest.kt | 120 | email = "user@example.com", | CODE |
| LOW | …ava/com/fsck/k9/ui/crypto/MessageCryptoHelperTest.java | 216 | assertEquals("test@example.org", capturedApiIntent.getStringExtra(OpenPgpApi.EXTRA_SENDER_ADDRESS)); | CODE |
| LOW | …ava/com/fsck/k9/ui/crypto/MessageCryptoHelperTest.java | 284 | assertEquals("test@example.org", capturedApiIntent.getStringExtra(OpenPgpApi.EXTRA_SENDER_ADDRESS)); | CODE |
| LOW | …fsck/k9/ui/messagedetails/AddToContactsLauncherTest.kt | 19 | val result = testSubject.launch(context, name = "John Doe", email = "john@example.com") | CODE |
| LOW | …test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt | 458 | recipientAddresses = listOf("alice@example.org", "bob@example.org"), | CODE |
| LOW⚡ | …test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt | 475 | verify(autocryptOpenPgpApiInteractor).getKeyMaterialForUserId(same(openPgpApi), eq("alice@example.org")) | CODE |
| LOW⚡ | …test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt | 476 | verify(autocryptOpenPgpApiInteractor).getKeyMaterialForUserId(same(openPgpApi), eq("bob@example.org")) | CODE |
| LOW⚡ | …test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt | 484 | recipientAddresses = listOf("alice@example.org", "bob@example.org", "carol@example.org"), | CODE |
| LOW⚡ | …test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt | 487 | pgpMessageBuilder.setBcc(listOf(Address("carol@example.org"))) | CODE |
| LOW⚡ | …test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt | 507 | verify(autocryptOpenPgpApiInteractor).getKeyMaterialForUserId(same(openPgpApi), eq("alice@example.org")) | CODE |
| LOW⚡ | …test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt | 508 | verify(autocryptOpenPgpApiInteractor).getKeyMaterialForUserId(same(openPgpApi), eq("bob@example.org")) | CODE |
| LOW⚡ | …test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt | 517 | recipientAddresses = listOf("alice@example.org", "carol@example.org"), | CODE |
| LOW⚡ | …test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt | 520 | pgpMessageBuilder.setBcc(listOf(Address("carol@example.org"))) | CODE |
| LOW | …test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt | 548 | recipientAddresses = listOf("test@example.org"), | CODE |
| LOW | …test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt | 610 | recipientAddresses = listOf("test@example.org"), | CODE |
| LOW | …test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt | 658 | recipientAddresses = listOf("test@example.org"), | CODE |
| LOW | …test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt | 733 | val cryptoStatus = defaultCryptoStatus.copy(recipientAddresses = listOf("test@example.org")) | CODE |
| LOW | …test/java/com/fsck/k9/message/PgpMessageBuilderTest.kt | 789 | private const val SENDER_EMAIL = "test@example.org" | CODE |
| LOW | …ui/legacy/src/main/res/layout/recipient_token_item.xml | 34 | tools:text="Jane Doe" | CODE |
| LOW | …cy/ui/legacy/src/main/res/layout/account_list_item.xml | 63 | tools:text="test@example.org" | CODE |
| LOW | …ui/legacy/src/main/res/layout/account_spinner_item.xml | 30 | tools:text="test@example.org" | CODE |
| LOW | …/src/main/res/layout/account_spinner_dropdown_item.xml | 33 | tools:text="test@example.org" | CODE |
| LOW | …ck/k9/activity/setup/AccountSetupCompositionPreview.kt | 16 | senderName = "John Doe", | CODE |
| 108 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| CRITICAL | …c/main/java/com/fsck/k9/mail/internet/EncoderUtil.java | 84 | return prefix + org.apache.james.mime4j.codec.EncoderUtil.encodeB(bytes) + ENC_WORD_SUFFIX; | CODE |
| CRITICAL | …c/main/java/com/fsck/k9/mail/internet/EncoderUtil.java | 114 | return prefix + org.apache.james.mime4j.codec.EncoderUtil.encodeQ(bytes, org.apache.james.mime4j.codec.Encod | CODE |
| CRITICAL | …mpl/ui/sendingMail/SendingMailSettingsViewModelTest.kt | 100 | profile = net.thunderbird.feature.account.storage.profile.ProfileDto( | CODE |
| CRITICAL | …mpl/ui/sendingMail/SendingMailSettingsViewModelTest.kt | 104 | avatar = net.thunderbird.feature.account.storage.profile.AvatarDto( | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | docs/developer/db-migration-checklist.md | 91 | #### Step 1: Renumber Your Migration | COMMENT |
| LOW | docs/developer/db-migration-checklist.md | 101 | #### Step 2: Verify Your Renumbered Migration | COMMENT |
| LOW | …cs/user-guide/troubleshooting/collecting-debug-logs.md | 14 | ## Step 1: Enable debug logging | COMMENT |
| LOW | …cs/user-guide/troubleshooting/collecting-debug-logs.md | 23 | ## Step 2: Reproduce the problem | COMMENT |
| LOW | …cs/user-guide/troubleshooting/collecting-debug-logs.md | 34 | ## Step 3: Collect the debug log | COMMENT |
| LOW | …cs/user-guide/troubleshooting/collecting-debug-logs.md | 116 | ## Step 4: Check Logs for Sensitive Information | COMMENT |
| LOW | …cs/user-guide/troubleshooting/collecting-debug-logs.md | 136 | ## Step 5: Report the Issue and Attach Logs | COMMENT |
| LOW | feature/notification/README.md | 12 | ### Step 1: Inject the `NotificationSender` | COMMENT |
| LOW | feature/notification/README.md | 33 | ### Step 2: Create a Notification instance | COMMENT |
| LOW | feature/notification/README.md | 47 | ### Step 3: Send and handle the outcome | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | …k/k9/preferences/migration/StorageMigrationTo25Test.kt | 0 | { "type": "smtp", "host": "irrelevant.invalid", "port": 465, "connectionsecurity": "starttls_required", "authenticationt | STRING |
| HIGH | …k/k9/preferences/migration/StorageMigrationTo24Test.kt | 0 | { "type": "smtp", "host": "irrelevant.invalid", "port": 465, "connectionsecurity": "starttls_required", "authenticationt | STRING |
| HIGH | …k/k9/preferences/migration/StorageMigrationTo22Test.kt | 0 | { "type": "smtp", "host": "irrelevant.invalid", "port": 465, "connectionsecurity": "starttls_required", "authenticationt | STRING |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | …Main/kotlin/net/thunderbird/core/common/mail/Tokens.kt | 21 | get() = this in '0'..'9' | COMMENT |
| LOW | quality/e2e/launch_tfa_debug.sh | 1 | #!/usr/bin/env bash | COMMENT |
| LOW | quality/e2e/tests/mail_compose.yml | 1 | appId: ${MAESTRO_APP_ID} | COMMENT |
| LOW | config/detekt/detekt.yml | 1 | config: | COMMENT |
| LOW | …java/com/fsck/k9/ui/messagelist/MessageListFragment.kt | 1361 | } | COMMENT |
| LOW | gradle/libs.versions.toml | 1 | # Judging the impact of newer library versions on the app requires being intimately familiar with the code base. | COMMENT |
| LOW | scripts/extract-eml.sh | 1 | #!/usr/bin/env bash | COMMENT |
| LOW | scripts/merged-pr-report.sh | 1 | #!/usr/bin/env bash | COMMENT |
| LOW | scripts/merged-pr-report.sh | 21 | # | COMMENT |
| LOW | scripts/requirements.txt | 1 | # Python dependencies for scripts in this directory | COMMENT |
| LOW | scripts/changelog/migrate_changelog_to_json.py | 1 | #!/usr/bin/env python3 | COMMENT |
| LOW | scripts/changelog/generate_changelog_json.py | 1 | #!/usr/bin/env python3 | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | scripts/extract-eml.sh | 16 | # Usage: | COMMENT |
| LOW | scripts/merged-pr-report.sh | 8 | # Usage: | COMMENT |
| LOW | scripts/changelog/migrate_changelog_to_json.py | 12 | # Usage: | COMMENT |
| LOW | scripts/changelog/generate_changelog_json.py | 16 | # Usage: | COMMENT |
| LOW | scripts/ci/validate-pr-report-labels.sh | 8 | # Usage: | COMMENT |
| LOW | scripts/ci/manage-pr-comment.sh | 6 | # Usage: | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | scripts/app_cycler.sh | 3 | # ============================================================================== | COMMENT |
| MEDIUM | scripts/app_cycler.sh | 6 | # ============================================================================== | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | scripts/changelog/test_changelog.py | 15 | def test_migration_preserves_beta_history_and_normalizes_date(self): | CODE |
| LOW | scripts/changelog/test_changelog.py | 47 | def test_generation_orders_versions_not_dates(self): | CODE |
| LOW | scripts/changelog/test_changelog.py | 58 | def test_generation_places_final_after_release_candidates_and_orders_beta_numbers(self): | CODE |
| LOW | scripts/changelog/test_changelog.py | 66 | def test_beta_generation_filters_groups_and_preserves_other_index_entries(self): | CODE |
| LOW | scripts/changelog/test_changelog.py | 107 | def test_main_generates_only_requested_beta_release(self): | CODE |
| LOW | scripts/ci/contributor_list.py | 101 | def generate_contributor_list( | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | scripts/ci/pr-sentinel/lib.sh | 4 | # Shared PR Sentinel helpers for comment + label + close orchestration. | COMMENT |
| MEDIUM | …ification/api/ui/layout/InAppNotificationHostLayout.kt | 64 | // Measuring Body content, applying scaffold paddings | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | docs/install.sh | 19 | # Check if the script was run with "force" argument | COMMENT |
| LOW | scripts/test_python_scripts.sh | 32 | # Check if Python is available | COMMENT |
| LOW | scripts/ci/run-fluidattacks-scanners.sh | 8 | # Check if tools are installed | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | …ava/com/fsck/k9/ui/messageview/ViewIntentFinderTest.kt | 43 | // set. The most robust way to accomplish this is to properly initialize the content provider. | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | …p/k9mail/ui/utils/itemtouchhelper/ItemTouchHelper.java | 1294 | * {@link #makeMovementFlags(int, int)} to easily construct it. Alternatively, you can use {@link SimpleCallback}. | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | scripts/ci/render-notes.py | 58 | print("Error: Unsupported applicationid") | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | …/java/com/fsck/k9/mail/transport/smtp/SmtpTransport.kt | 91 | // TODO: Fix tests to not use open() directly | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | scripts/ci/render-notes.py | 27 | CODE |