Cross-device clipboard sync for macOS, Windows & Linux — end-to-end encrypted, LAN-only, no cloud. OCR, CLI and MCP server built in.
This report summarizes scanner evidence for CrossPaste/crosspaste-desktop, a Kotlin project with 2,683 GitHub stars. SynthScan examined 209,007 lines of code across 1538 source files, recording 146 pattern matches distributed across 13 syntactic categories. The legacy adjusted pattern signal is 1.7 (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? 75 finding(s).
Are imports declared and package/API names verified? 3 finding(s).
Are wrappers, guards, and repeated structures necessary? 80 finding(s).
Which changes need focused human review? 146 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 146 distinct pattern matches across 13 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 |
|---|---|---|---|---|
| MEDIUM | i18n_batch_update.sh | 3 | # ========================================== | COMMENT |
| MEDIUM | i18n_batch_update.sh | 5 | # ========================================== | COMMENT |
| MEDIUM | i18n_batch_update.sh | 16 | # ========================================== | COMMENT |
| MEDIUM | i18n_batch_update.sh | 18 | # ========================================== | COMMENT |
| MEDIUM | i18n_batch_update.sh | 37 | # ========================================== | COMMENT |
| MEDIUM | i18n_batch_update.sh | 39 | # ========================================== | COMMENT |
| MEDIUM | i18n_batch_update.sh | 95 | # ========================================== | COMMENT |
| MEDIUM | i18n_batch_update.sh | 97 | # ========================================== | COMMENT |
| MEDIUM | i18n_batch_update.sh | 116 | # ========================================== | COMMENT |
| MEDIUM | i18n_batch_update.sh | 118 | # ========================================== | COMMENT |
| MEDIUM | i18n_batch_update.sh | 157 | # ========================================== | COMMENT |
| MEDIUM | i18n_batch_update.sh | 159 | # ========================================== | COMMENT |
| MEDIUM⚡ | i18n_batch_update.sh | 229 | # ========================================== | COMMENT |
| MEDIUM⚡ | i18n_batch_update.sh | 231 | # ========================================== | COMMENT |
| MEDIUM⚡ | i18n_batch_update.sh | 238 | # ========================================== | COMMENT |
| MEDIUM⚡ | i18n_batch_update.sh | 240 | # ========================================== | COMMENT |
| MEDIUM | i18n_batch_update.sh | 302 | # ========================================== | COMMENT |
| MEDIUM | i18n_batch_update.sh | 305 | # ========================================== | COMMENT |
| MEDIUM | …/commonMain/kotlin/com/crosspaste/sync/SyncResolver.kt | 194 | // ────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | …/commonMain/kotlin/com/crosspaste/sync/SyncResolver.kt | 198 | // ────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | …/commonMain/kotlin/com/crosspaste/sync/SyncResolver.kt | 526 | // ────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | …/commonMain/kotlin/com/crosspaste/sync/SyncResolver.kt | 528 | // ────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | …/commonMain/kotlin/com/crosspaste/sync/SyncResolver.kt | 559 | // ────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | …/commonMain/kotlin/com/crosspaste/sync/SyncResolver.kt | 561 | // ────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | …/commonMain/kotlin/com/crosspaste/sync/SyncResolver.kt | 783 | // ────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | …/commonMain/kotlin/com/crosspaste/sync/SyncResolver.kt | 785 | // ────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/background/service-worker.ts | 48 | // ─── Per-device runtime facts (source of truth) ─────────────────────── | COMMENT |
| MEDIUM | web/src/background/service-worker.ts | 101 | // ─── WebSocket manager (initialized in initialize()) ──────────────────── | COMMENT |
| MEDIUM | web/src/background/service-worker.ts | 105 | // ─── Current connection attempt ───────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/background/service-worker.ts | 151 | // ─── Clipboard monitoring ─────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/background/service-worker.ts | 374 | // ─── Identity ─────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/background/service-worker.ts | 391 | // ─── Device status helpers ────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/background/service-worker.ts | 410 | // ─── Sync & heartbeat ────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/background/service-worker.ts | 525 | // ─── Migration from legacy single-device format ──────────────────────── | COMMENT |
| MEDIUM | web/src/background/service-worker.ts | 557 | // ─── Startup ──────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/background/service-worker.ts | 657 | // ─── Alarm handler ────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/background/service-worker.ts | 672 | // ─── Broadcast ────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/background/service-worker.ts | 712 | // ─── Message handling ─────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/shared/api/__tests__/sync-v2v3.test.ts | 10 | // ─── chrome.storage.local stub (KeyStore dependency) ──────────────────── | COMMENT |
| MEDIUM⚡ | web/src/shared/i18n/use-i18n.ts | 19 | // ─── Language detection ───────────────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | web/src/shared/i18n/use-i18n.ts | 29 | // ─── Context ──────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | web/src/shared/i18n/use-i18n.ts | 39 | // ─── Provider ─────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/shared/i18n/use-i18n.ts | 72 | // ─── Hooks ────────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/shared/paste/paste-type-detector.ts | 66 | // ─── URL detection ────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/shared/paste/paste-type-detector.ts | 84 | // ─── Color detection ──────────────────────────────────────────────────── | COMMENT |
| MEDIUM | …src/shared/paste/__tests__/paste-type-detector.test.ts | 5 | // ─── Helper: ARGB integer from components ───────────────────────────── | COMMENT |
| MEDIUM | …src/shared/paste/__tests__/paste-type-detector.test.ts | 10 | // ─── parseColor ─────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | …src/shared/paste/__tests__/paste-type-detector.test.ts | 159 | // ─── detectPasteType ────────────────────────────────────────────────── | COMMENT |
| MEDIUM | …hared/paste/__tests__/remove-html-image-plugin.test.ts | 8 | // ─── isSingleImgInBody ─────────────────────────────────────────────── | COMMENT |
| MEDIUM | …hared/paste/__tests__/remove-html-image-plugin.test.ts | 59 | // ─── RemoveHtmlImagePlugin.process ─────────────────────────────────── | COMMENT |
| MEDIUM | web/src/shared/ws/ws-manager.ts | 232 | // ─── Reconnection ────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/shared/ws/ws-client.ts | 403 | // ─── Frame parsing ────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/shared/ws/ws-client.ts | 526 | // ─── Heartbeat ────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/shared/ws/ws-client.ts | 559 | // ─── Send queue ───────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/shared/ws/ws-client.ts | 612 | // ─── Cleanup ──────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/components/settings/AboutView.tsx | 25 | // ─── Divider ──────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/components/settings/AboutView.tsx | 35 | // ─── Link Item ────────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/components/settings/AboutView.tsx | 78 | // ─── About View ───────────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/components/settings/SettingsView.tsx | 23 | // ─── Segmented Control ────────────────────────────────────────────────── | COMMENT |
| MEDIUM | web/src/components/settings/SettingsView.tsx | 61 | // ─── Language Dropdown ────────────────────────────────────────────────── | COMMENT |
| 7 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | …/com/crosspaste/ui/devices/PairingV3DialogStateTest.kt | 261 | // Step 1: Loading | COMMENT |
| LOW | …/com/crosspaste/ui/devices/PairingV3DialogStateTest.kt | 274 | // Step 2: Incorrect PIN feedback shown (isLoading remains true) | COMMENT |
| LOW | …/com/crosspaste/ui/devices/PairingV3DialogStateTest.kt | 289 | // Step 3: Error cleared while recover is executed | COMMENT |
| LOW | …/com/crosspaste/ui/devices/PairingV3DialogStateTest.kt | 300 | // Step 4: Recover finishes; inputResetGeneration incremented and loading ends in one atomic emission | COMMENT |
| LOW | …n/com/crosspaste/paste/PasteExportImportServiceTest.kt | 239 | // Step 1: Write paste.data file (simulating export) | COMMENT |
| LOW⚡ | …n/com/crosspaste/paste/PasteExportImportServiceTest.kt | 254 | // Step 2: Compress | COMMENT |
| LOW⚡ | …n/com/crosspaste/paste/PasteExportImportServiceTest.kt | 264 | // Step 3: Decompress (simulating import) | COMMENT |
| LOW⚡ | …n/com/crosspaste/paste/PasteExportImportServiceTest.kt | 272 | // Step 4: Read and verify paste.data | COMMENT |
| LOW | …/commonMain/kotlin/com/crosspaste/sync/SyncResolver.kt | 716 | // Step 1: Exchange keys. This exchange supersedes the dialog's | COMMENT |
| LOW | …/commonMain/kotlin/com/crosspaste/sync/SyncResolver.kt | 740 | // Step 2: Compute local SAS and compare with user-entered token | COMMENT |
| LOW | …/commonMain/kotlin/com/crosspaste/sync/SyncResolver.kt | 754 | // Step 3: Confirm trust | COMMENT |
| LOW | …/commonMain/kotlin/com/crosspaste/sync/SyncResolver.kt | 766 | // Step 4: Save remote key locally and send heartbeat. The confirm | COMMENT |
| LOW⚡ | …osspaste/image/coil/AppSourceIconCropTransformation.kt | 32 | // Step 1: normalize by symmetrically trimming transparent padding. | COMMENT |
| LOW⚡ | …osspaste/image/coil/AppSourceIconCropTransformation.kt | 38 | // Step 2: apply the fixed crop ratios relative to the trimmed content. | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | …/com/crosspaste/ui/devices/PairingV3DialogStateTest.kt | 261 | // Step 1: Loading | COMMENT |
| LOW | …/com/crosspaste/ui/devices/PairingV3DialogStateTest.kt | 274 | // Step 2: Incorrect PIN feedback shown (isLoading remains true) | COMMENT |
| LOW | …/com/crosspaste/ui/devices/PairingV3DialogStateTest.kt | 289 | // Step 3: Error cleared while recover is executed | COMMENT |
| LOW | …/com/crosspaste/ui/devices/PairingV3DialogStateTest.kt | 300 | // Step 4: Recover finishes; inputResetGeneration incremented and loading ends in one atomic emission | COMMENT |
| LOW | …n/com/crosspaste/paste/PasteExportImportServiceTest.kt | 239 | // Step 1: Write paste.data file (simulating export) | COMMENT |
| LOW⚡ | …n/com/crosspaste/paste/PasteExportImportServiceTest.kt | 254 | // Step 2: Compress | COMMENT |
| LOW⚡ | …n/com/crosspaste/paste/PasteExportImportServiceTest.kt | 264 | // Step 3: Decompress (simulating import) | COMMENT |
| LOW⚡ | …n/com/crosspaste/paste/PasteExportImportServiceTest.kt | 272 | // Step 4: Read and verify paste.data | COMMENT |
| LOW | …/commonMain/kotlin/com/crosspaste/sync/SyncResolver.kt | 716 | // Step 1: Exchange keys. This exchange supersedes the dialog's | COMMENT |
| LOW | …/commonMain/kotlin/com/crosspaste/sync/SyncResolver.kt | 740 | // Step 2: Compute local SAS and compare with user-entered token | COMMENT |
| LOW | …/commonMain/kotlin/com/crosspaste/sync/SyncResolver.kt | 754 | // Step 3: Confirm trust | COMMENT |
| LOW | …/commonMain/kotlin/com/crosspaste/sync/SyncResolver.kt | 766 | // Step 4: Save remote key locally and send heartbeat. The confirm | COMMENT |
| LOW⚡ | …osspaste/image/coil/AppSourceIconCropTransformation.kt | 32 | // Step 1: normalize by symmetrically trimming transparent padding. | COMMENT |
| LOW⚡ | …osspaste/image/coil/AppSourceIconCropTransformation.kt | 38 | // Step 2: apply the fixed crop ratios relative to the trimmed content. | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | smoke-test.sh | 1 | #!/usr/bin/env bash | COMMENT |
| LOW | app/openssl.yaml | 1 | # Pinned OpenSSL libcrypto binaries bundled with the desktop app for the | COMMENT |
| LOW | …ste/ui/paste/side/preview/SkiaAnimatedImageViewTest.kt | 101 | COMMENT | |
| LOW | …t/kotlin/com/crosspaste/sync/GeneralSyncHandlerTest.kt | 641 | // ========== X. callback wiring (polling vs force) ========== | COMMENT |
| LOW | …c/commonMain/kotlin/com/crosspaste/net/TelnetHelper.kt | 61 | // Last address set we successfully advertised to each peer (keyed by the probed host | COMMENT |
| LOW | …nMain/kotlin/com/crosspaste/net/routing/SyncRouting.kt | 241 | COMMENT | |
| LOW | …n/kotlin/com/crosspaste/DesktopPasteComponentModule.kt | 201 | // plugins and must keep these invariants): | COMMENT |
| LOW | …tlin/com/crosspaste/ui/paste/side/SidePasteItemView.kt | 161 | // layer is only used to provide a drag preview, and a drag | COMMENT |
| LOW | …sspaste/ui/paste/side/preview/SkiaAnimatedImageView.kt | 81 | COMMENT | |
| LOW | …topMain/kotlin/com/crosspaste/app/WindowsZipUpdater.kt | 281 | if (readReadyMarker()?.version == release.version && fileUtils.existFile(stagingDir())) { | COMMENT |
| LOW | …rc/desktopMain/kotlin/com/crosspaste/cli/CliRouting.kt | 1061 | // Sampling inside the collect instead would race — a paste created between | COMMENT |
| LOW | …rc/desktopMain/kotlin/com/crosspaste/cli/CliRouting.kt | 1081 | // query. Building a baseline from the latter would replay | COMMENT |
| LOW | web/src/vite-env.d.ts | 1 | /// <reference types="vite/client" /> | COMMENT |
| LOW | scripts/build-test-windows-zip.sh | 1 | #!/usr/bin/env bash | COMMENT |
| LOW | scripts/serve-fake-update.sh | 1 | #!/usr/bin/env bash | COMMENT |
| LOW | scripts/build-oss-test-update.sh | 1 | #!/usr/bin/env bash | COMMENT |
| LOW | scripts/publish-msstore.sh | 1 | #!/usr/bin/env bash | COMMENT |
| LOW | scripts/publish-msstore.sh | 21 | # the Store accepts or rejects it. | COMMENT |
| LOW | .github/workflows/build-beta-linux.yml | 1 | name: "Build Beta (Linux)" | COMMENT |
| LOW | .github/workflows/build-release.yml | 301 | # takes precedence over build.conveyor.conf's `revision = ${env.REVISION}` | COMMENT |
| LOW | .github/workflows/build-release.yml | 321 | # alias extension with both aliases and the windows.firewallRules | COMMENT |
| LOW | .github/workflows/build-release.yml | 361 | unzip -Z1 "${files[0]}" | grep -x 'bin/CrossPaste.exe' > /dev/null || { echo "Store MSIX does not contain bin/ | COMMENT |
| LOW | .github/workflows/build-release.yml | 381 | # and keep a backup of the secret key outside GitHub: rotating it | COMMENT |
| LOW | .github/scripts/package_appimage.sh | 1 | #!/bin/bash | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| CRITICAL | …t/kotlin/com/crosspaste/net/routing/PushRoutingTest.kt | 147 | private fun io.ktor.client.request.HttpRequestBuilder.pushHeaders( | CODE |
| CRITICAL | …pMain/kotlin/com/crosspaste/ui/ChangeLogContentView.kt | 344 | private fun androidx.compose.ui.text.AnnotatedString.Builder.appendLinkified( | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | web/src/background/service-worker.ts | 64 | function getOrCreateRuntime(targetId: string): RuntimeFacts { | CODE |
| LOW | web/src/background/service-worker.ts | 1103 | async function refreshOfferForNewGeneration( | CODE |
| LOW | web/src/shared/app/ui-support.ts | 20 | export async function openCrossPasteWebInBrowser(path = ""): Promise<void> { | CODE |
| LOW | web/src/shared/utils/html-color.ts | 93 | function parseBackgroundColorFromStyle(style: string): number | null { | CODE |
| LOW | web/src/shared/hooks/use-oversize-notice.ts | 20 | export function useOversizeNoticeListener(): void { | CODE |
| LOW | web/src/shared/api/__tests__/sync-v2v3.test.ts | 63 | async function fabricateExchangeResponse(server: Awaited<ReturnType<typeof generateServerKeys>>) { | CODE |
| LOW | web/src/shared/i18n/i18n-core.ts | 55 | export async function buildTranslatorFromStorage(): Promise<TranslateFn> { | CODE |
| LOW | web/src/shared/ws/ws-message-handler.ts | 383 | async function handlePasteRejectedOversize( | CODE |
| LOW | web/src/shared/ws/__tests__/ws-client-auth.test.ts | 35 | function gatedVerificationProcessor(): { | CODE |
| LOW | web/src/shared/clipboard/clipboard-sync-writer.ts | 22 | export async function writeRemotePasteToClipboard(pasteData: PasteData): Promise<boolean> { | CODE |
| LOW | .github/scripts/processJarFiles.js | 102 | function removeTesseractFilesFromJar(jarPath) { | CODE |
| LOW | .github/scripts/validateAndUpdateVersion.js | 7 | async function validateAndUpdateVersion() { | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM⚡ | app/script/mac_start.sh | 3 | # Define the application path relative to this script | COMMENT |
| MEDIUM⚡ | app/script/linux_start.sh | 3 | # Define the application path relative to this script | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW⚡ | app/script/mac_start.sh | 6 | # Check if a PID is provided and if it is a number | COMMENT |
| LOW⚡ | app/script/mac_start.sh | 10 | # Check if the process exists and wait for it to stop | COMMENT |
| LOW⚡ | app/script/linux_start.sh | 6 | # Check if a PID is provided and if it is a number | COMMENT |
| LOW⚡ | app/script/linux_start.sh | 10 | # Check if the process exists and wait for it to stop | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | …ommonMain/kotlin/com/crosspaste/ui/base/FontManager.kt | 100 | * This is for demonstration purposes of a font | COMMENT |
| MEDIUM | .github/scripts/processJarFiles.js | 94 | // You can modify this to extract specific parts using regex groups | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | …m/crosspaste/ui/paste/edit/PasteHtmlEditContentView.kt | 134 | // Track the annotated string at the last save point for robust change detection | COMMENT |
| MEDIUM | web/src/shared/utils/html-color.ts | 94 | // Use a temp element to leverage browser CSS parsing | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | scripts/publish-msstore.sh | 32 | # Usage: | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | …topTest/kotlin/com/crosspaste/db/paste/PasteDaoTest.kt | 390 | assertTrue(results.single().pasteSearchContent!!.contains("foo@bar.com")) | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | app/src/desktopMain/resources/plugins/translate/run.py | 31 | except Exception as e: | CODE |