Latest AI audit result
Two AI models, Claude Opus 5.5 and GPT-6.1 Sol, checked the Tokrate companion’s source code against 16 privacy and security promises. Their results are published here unedited, so you can see what they found before you install or turn on sharing.
Released as 0.1.20 from commit cd39455: the reviewed code plus one fix for a finding below and a test-only change for Windows (see every changed line).
- Full commit
cf4b743319f665c04da574f674f7f9de8c15405b- Git ref
v0.1.20- Prompt
- On GitHub
An AI audit of the client source code. It is not a certification and not a professional audit; see what it does not cover below.
What each reviewer concluded
Claude Opus 5.5
Claude Code · claude-opus-5-5
Overall verdict: Pass with notes
“Both clients upload only the documented numbers after opt-in and stay read-only; a minor upload-timing edge case can reveal when a delayed sample became ready.”
- Verified
- 15
- Partial
- 1
- Contradicted
- 0
- Not verifiable
- 0
- Critical
- 0
- High
- 0
- Medium
- 0
- Low
- 1
- Info
- 6
GPT-6.1 Sol
Codex CLI 0.160.1 · reasoning effort high · gpt-6.1-sol
Overall verdict: Pass with notes
“Both clients protect upload privacy and consent, but the Mac client has a service-triggered crash and an updater-content caveat.”
- Verified
- 14
- Partial
- 0
- Contradicted
- 0
- Not verifiable
- 2
- Critical
- 0
- High
- 0
- Medium
- 1
- Low
- 1
- Info
- 0
Model, tool and reasoning effort are the run details we recorded ourselves. Each raw file also holds what the model reported about itself.
The 16 promises they checked
Each reviewer gave a separate verdict for the Mac app and for the Windows and Linux app. Open a row for the full promise, the reasoning and the code locations the reviewer cited.
- Verified. The code does what the promise says, on every path the reviewer traced.
- Partial. The promise holds in general, with a gap that a finding describes.
- Contradicted. The code does what the promise says it does not do.
- Not verifiable. The source code cannot settle the question.
- Not applicable. The promise does not apply to this client.
C01 Upload allowlistClaudeMac: VerifiedWindows and Linux: VerifiedCodexMac: VerifiedWindows and Linux: Verified
Each community upload is built from an explicit field allowlist and contains only the fields documented in README.md (section “Optional community sharing”) and docs/metrics-contract.md. It never contains prompts, responses, code, file or folder paths, session, turn or account identifiers, the local deduplication digest, raw originator or entrypoint strings, user names, host names or hardware identifiers.
Claude
Mac SharedSample encodes 24 fixed keys through a hand-written encode(to:) and SampleEnvelope adds schemaVersion/sentAt/samples; Rust SharedSample is a serde struct with the same 24 keys and a single envelope builder. Every value comes from allowlisted tuples (client/parser/metric), regex-checked model/clientVersion, allowlisted provider/effort/sourceKind, the surface enum, numbers and a fresh UUID; the local digest id is never copied. All keys are described in README/metrics contract. Desktop sends Grok's clientVersion where the Mac never does (F06, info).
- shared/Sources/TokrateCore/Sharing/SharedSample.swift:79-117Allowlist mapping from TurnMetric; id never copied, sampleId is a new UUID
- shared/Sources/TokrateCore/Sharing/SharedSample.swift:131-162Explicit encoder: exactly 24 sample keys
- shared/Sources/TokrateCore/Sharing/SharedSample.swift:170-186Envelope schemaVersion/sentAt/samples, sorted keys
- desktop/core/src/sharing.rs:35-70Rust SharedSample struct, same 24 camelCase keys
- desktop/core/src/sharing.rs:73-211from_metric: tuple allowlist, safe_identifier for model/clientVersion, provider allowlist
- desktop/core/src/sharing.rs:246-279Envelope built once, signed over exact bytes
Codex
Both serializers explicitly select 24 sample fields plus the three envelope keys. Traced tool metadata and numeric usage through normalized metrics, sanitization, serialization and signing. No raw content, paths, source identifiers or deduplication digest enters the payload. All transmitted body keys are documented; sampleId is a fresh upload UUID, not a source turn identifier.
- shared/Sources/TokrateCore/Sharing/SharedSample.swift:64-116Validated metric-to-allowlist conversion with bucketed time and sanitized identifiers.
- shared/Sources/TokrateCore/Sharing/SharedSample.swift:131-185Explicit sample keys and three-key upload envelope.
- desktop/core/src/sharing.rs:37-210Matching Rust fields, provenance checks and metadata normalization.
- desktop/core/src/sharing.rs:245-278Only the sample envelope is serialized and signed.
- README.md:48-56Target documentation lists the envelope, headers and sample fields.
C02 Content is not retainedClaudeMac: VerifiedWindows and Linux: VerifiedCodexMac: VerifiedWindows and Linux: Verified
Parsers keep only numeric usage, timestamps, model and provider identifiers, reasoning effort and the documented category fields. Prompt and response text, tool output, code and paths are not stored beyond parsing. Sources the documentation says are never read are really never opened or queried: Grok chat_history.jsonl and updates.jsonl; the OpenCode part table and full message.data; Antigravity step_payload, trajectory_metadata_blob, render_info, task_details, permissions, error_details and battle_mode_infos.
Claude
JSONL parsers parse each line transiently and keep only ids (hashed into digests), timestamps, token counts, regex-checked model/version, allowlisted effort and the surface category; text is only inspected for tool_result/interruption markers. Grok monitors only open events.jsonl, usage.json and summary.json. OpenCode SQL selects session(id,parent_id,version) and json_extract paths of message.data; part is never queried. Antigravity SQL selects only steps(idx,has_subtrajectory,metadata), executor_metadata.data, gen_metadata.data and parent_references existence.
- shared/Sources/TokrateCore/CodexEventParser.swift:103-124session_meta: only version, originator category, provider flag, ids for digests
- shared/Sources/TokrateCore/ClaudeTranscriptParser.swift:586-595Content inspected only for interruption prefix / tool_result
- shared/Sources/TokrateCore/GrokSessionParser.swift:421Watched names: events.jsonl, usage.json, summary.json only
- desktop/core/src/grok.rs:1072-1074Desktop Grok opens only events.jsonl/usage.json/summary.json
- shared/Sources/TokrateCore/OpenCode/OpenCodeDatabase.swift:67-112json_extract of fixed paths; full data never selected
- desktop/core/src/opencode_db.rs:251-295Rust message query selects bounded extracted columns only
- shared/Sources/TokrateCore/Antigravity/AntigravityDatabase.swift:80-138Only allowed columns selected
- desktop/core/src/antigravity_db.rs:148-237Rust: same columns; step_payload etc. never selected
Codex
Parsers discard parsed prompt/response/tool bodies, retaining measurements and allowed metadata. Grok opens only its three metadata/usage files. OpenCode SQL selects JSON properties, never the part table or full message.data. Antigravity queries omit every prohibited column. Incremental JSONL input may remain buffered until a line completes, with a 1 MiB cap.
- desktop/core/src/grok.rs:24-25Source file names exclude chat_history.jsonl and updates.jsonl.
- desktop/core/src/opencode_db.rs:111-138Projected and bounded JSON metadata, not complete message bodies.
- shared/Sources/TokrateCore/OpenCode/OpenCodeDatabase.swift:65-109Matching Swift metadata-only projection.
- desktop/core/src/antigravity_db.rs:163-196Selected steps/executor metadata excludes prohibited content.
- desktop/core/src/antigravity_db.rs:228-230Generation metadata query omits prohibited fields.
- shared/Sources/TokrateCore/Antigravity/AntigravityDatabase.swift:80-134Swift queries likewise omit prohibited fields.
- desktop/core/src/claude_parser.rs:69-174Retained parser state comprises metrics and turn bookkeeping.
- shared/Sources/TokrateCore/ClaudeTranscriptParser.swift:27-90Swift parser state does not retain parsed content.
- shared/Sources/TokrateCore/JSONLFileReader.swift:115-132Bounded unfinished-line buffering is discarded after parsing.
C03 Minimal local storageClaudeMac: VerifiedWindows and Linux: VerifiedCodexMac: VerifiedWindows and Linux: Verified
Local history stores only normalized metric records and checkpoints (with SHA-256 path digests, never raw paths or raw session or turn identifiers). Records are kept for 7 days and at most 50,000 turns while the app runs; expired records are removed within about an hour, also while monitoring is paused (nothing runs while the app is not running). Live per-response values are memory only.
Claude
History files hold schemaVersion, TurnMetric records (digest id, no paths or raw ids) and checkpoints with SHA-256 path digests, inode/identity, size, mtime, version key. Both cap at 50,000 and 7 days. Mac prunes every poll (saved within 10 s) and hourly while paused; desktop prunes at most every 10 min on a poll loop that keeps running while paused. Live responses stay in memory. Source-folder paths are kept in settings/preferences, not history (F04, info).
- shared/Sources/TokrateCore/MetricHistory.swift:5-467-day retention, 50,000 cap, prune
- macos/Sources/TokrateApp/Stores/HistoryStore.swift:335-366Prune each poll, throttled save
- macos/Sources/TokrateApp/Stores/HistoryStore.swift:464-478Hourly prune+save while paused
- shared/Sources/TokrateCore/SourceFileCheckpoint.swift:12-38Checkpoint = path digest, inode, size, mtime, version
- desktop/core/src/history.rs:25-95Rust history: retention, cap, checkpoints, private write
- desktop/src-tauri/src/runtime.rs:686-754Desktop prune at most every 10 min, incl. while paused
- desktop/core/src/monitor.rs:153-163Rust checkpoint stores SHA-256 path digest
Codex
History contains normalized records and digest-based checkpoints, capped at seven days and 50,000 turns. Swift has hourly paused maintenance; Rust checks pruning every ten minutes through the still-running polling task. Live response values stay in memory. History replacement is atomic and owner-only on Unix; Windows uses the user's application-data directory and inherited ACLs.
- shared/Sources/TokrateCore/MetricHistory.swift:3-46Seven-day and 50,000-record retention.
- shared/Sources/TokrateCore/SourceFileCheckpoint.swift:10-45Checkpoint identifiers are SHA-256 path digests.
- macos/Sources/TokrateApp/Stores/HistoryStore.swift:440-477Pausing monitoring starts periodic retention maintenance.
- desktop/core/src/history.rs:10-53Bounded normalized history representation.
- desktop/core/src/monitor.rs:147-162Rust checkpoints hash source paths.
- desktop/src-tauri/src/runtime.rs:641-701Pruning proceeds even without newly ingested records.
- desktop/src-tauri/src/runtime.rs:1011-1095Background polling remains active while monitoring is paused.
- desktop/core/src/model.rs:234-249Per-response values belong to the memory-only live model.
- desktop/core/src/private_file.rs:23-38Atomic private history-file replacement.
- shared/Sources/TokrateCore/PrivateFile.swift:3-29Swift writes temporary files with mode 0600 and atomic rename.
C04 Read-only access to coding-tool dataClaudeMac: VerifiedWindows and Linux: VerifiedCodexMac: VerifiedWindows and Linux: Verified
The app never writes, modifies, deletes or renames other tools’ session files or databases, and never takes an exclusive lock on them. SQLite databases are opened read-only (mode=ro, never immutable=1); SQLite’s normal shared lock for the duration of a short read transaction is expected and does not break this claim.
Claude
Source files are opened O_RDONLY|O_NONBLOCK (Mac) or OpenOptions read-only (Rust; Windows std share mode allows other writers) and never written, renamed or deleted; the only writes go to the app's own folder, and the Mac CLI export refuses destinations inside source roots. SQLite is opened with SQLITE_OPEN_READONLY and a file: URI with mode=ro, never immutable, inside one short read transaction. SQLite's own WAL handling may create -wal/-shm sidecars (F02, info).
- shared/Sources/TokrateCore/RegularFile.swift:20-46Read-only non-blocking open, regular-file check on descriptor
- desktop/core/src/reader.rs:41-66Rust read-only open, regular-file check, capped read
- shared/Sources/TokrateCore/SQLite/ReadOnlySQLiteDatabase.swift:91-137SQLITE_OPEN_READONLY, mode=ro URI, busy timeout, BEGIN/COMMIT
- desktop/core/src/sqlite_read.rs:18-66Rust mode=ro URI, read-only flags, busy timeout
- shared/Sources/TokrateCore/ExportDestination.swift:30-63CLI export never writes inside source folders
Codex
Coding-tool files are opened for reading only. SQLite uses mode=ro and read-only connection flags with short normal transactions and a 500 ms busy timeout; neither implementation sets immutable=1. Writes are confined to Tokrate state and explicit exports, with export destinations checked against coding-tool source roots.
- shared/Sources/TokrateCore/RegularFile.swift:20-34Read-only nonblocking open with regular-file verification.
- shared/Sources/TokrateCore/SQLite/ReadOnlySQLiteDatabase.swift:63-100Normal read transaction and read-only SQLite flags.
- shared/Sources/TokrateCore/SQLite/ReadOnlySQLiteDatabase.swift:122-136Percent-encoded mode=ro database URI.
- desktop/core/src/sqlite_read.rs:16-65Rust mode=ro, read-only flags and bounded lock wait.
- shared/Sources/TokrateCore/ExportDestination.swift:30-62Exports reject destinations inside source roots.
C05 Affirmative consentClaudeMac: VerifiedWindows and Linux: VerifiedCodexMac: VerifiedWindows and Linux: Verified
No sample is uploaded and no community request is made until the user explicitly opts in on the current notice version. “Only for local use” and a saved OFF stay OFF across launches and upgrades. Raising the notice version requires a new opt-in.
Claude
Mac: sharing is requested only when the saved choice is true AND the consent record is the current notice (4) with action contribute; otherwise activate() calls disable() and the Keychain is never touched. The toggle can only show the notice. Desktop: sharing_authorized requires sharing=true plus an Accepted consent with the exact SHARING_NOTICE_VERSION; a stale or corrupt setting is forced off, update() refuses sharing=true, and record_sharing_consent rejects a different notice version. Saved OFF/declined never prompts or enables.
- shared/Sources/TokrateCore/Sharing/SharingPreferences.swift:85-111Current-notice contribute consent required; else disable
- shared/Sources/TokrateCore/Sharing/SharingPreferences.swift:114-146Toggle only shows notice; consent recorded before enable
- desktop/src-tauri/src/runtime.rs:127-134sharing_authorized: current notice + Accepted
- desktop/src-tauri/src/runtime.rs:223-280Load: stale consent forced off, corrupt settings fail closed
- desktop/src-tauri/src/runtime.rs:462-466update() refuses sharing=true
- desktop/src-tauri/src/runtime.rs:524-563Consent must match current notice version
- desktop/src-tauri/src/runtime.rs:1098-1118restart_sharing starts nothing unless authorized
Codex
Saved OFF remains OFF. ON requires affirmative acceptance of the current versioned notice; old or invalid consent cannot enable requests. The Tauri settings patch cannot silently turn sharing on. Both implementations persist consent before enabling credentials and the upload loop, and fail closed on missing or invalid settings.
- shared/Sources/TokrateCore/Sharing/SharingPreferences.swift:77-150Versioned affirmative consent and explicit local-only/off paths.
- desktop/src-tauri/src/runtime.rs:128-132Authorization requires ON and valid current-version consent.
- desktop/src-tauri/src/runtime.rs:223-279Settings load preserves OFF and rejects obsolete or invalid ON.
- desktop/src-tauri/src/runtime.rs:462-465Ordinary settings patches cannot enable sharing.
- desktop/src-tauri/src/runtime.rs:524-561Current notice acceptance is persisted before activation.
C06 Consent screen is accurateClaudeMac: VerifiedWindows and Linux: VerifiedCodexMac: VerifiedWindows and Linux: Verified
The sample payload and field description the consent screen shows match the structure and fields that are actually uploaded.
Claude
Both consent screens render the example by running fake values through the real allowlist and envelope encoder (Mac SamplePayload uses SharedSample+SampleEnvelope; desktop sent_example calls example_request_json, which uses from_metric and envelope_value, the same path as signed_request). Field descriptions name the same categories, the 5-minute rounding, the random sample ID and the public-key pseudonym. The coding-tool version is shown in the example though only implicitly in the prose.
- macos/Sources/TokrateApp/Support/SamplePayload.swift:8-43Example built via SharedSample and SampleEnvelope
- macos/Sources/TokrateApp/Views/ConsentDisclosureView.swift:35-84Field description and example disclosure
- desktop/core/src/sharing.rs:284-314example_request_json uses real from_metric + envelope_value
- desktop/src-tauri/src/main.rs:66-70sent_example command returns serializer output
- desktop/ui/components/SharingChoice.tsx:22-51Desktop field list and example display
Codex
Both consent examples use the production sample and envelope serializers, rather than a separately invented structure. The disclosures describe the numeric fields, categories, five-minute timestamps and pseudonymous signing identity that are actually sent.
- macos/Sources/TokrateApp/Support/SamplePayload.swift:8-42Mac example passes synthetic metrics through the real encoder.
- macos/Sources/TokrateApp/Views/ConsentDisclosureView.swift:35-69Field description and rendered production-format example.
- desktop/core/src/sharing.rs:273-313Rust example uses the same upload envelope construction.
- desktop/ui/components/SharingChoice.tsx:22-49Frontend field disclosure and escaped payload presentation.
- desktop/ui/components/SharingChoice.tsx:107-122Consent view explains the public signing identity.
C07 Sharing OFF stops network activityClaudeMac: VerifiedWindows and Linux: VerifiedCodexMac: VerifiedWindows and Linux: Verified
Switching sharing off cancels the upload loop, clears pending uploads and stops community statistics and alert fetches. While sharing is off, no request is made to the community endpoints.
Claude
Mac disable() bumps the generation, cancels the loop task, clears queue/seen/board/key and every await in refresh() is followed by an isEnabled+generation check before upload or board fetch. Desktop withdraw_sharing/stop_sharing run before any fallible save, abort the network task, disable the queue and clear the board; every request in sharing_loop is created only under s.valid(generation). Board fetches exist only inside these sharing loops.
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:81-90disable() cancels loop, clears queue and board
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:111-164Generation/isEnabled checks around each request
- desktop/src-tauri/src/runtime.rs:444-455withdraw_sharing stops sharing before saving
- desktop/src-tauri/src/runtime.rs:564-573stop_sharing aborts task, clears queue/board
- desktop/src-tauri/src/runtime.rs:1128-1245Every request gated by valid(generation)
Codex
OFF cancels the active network task, invalidates its generation, clears pending uploads and board state, and disables future community polling. Every upload and board path checks current authorization. A request already delivered while ON cannot be recalled; neither client starts a new community request while OFF.
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:81-89Disable cancels task and clears queue, identity and board state.
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:111-163Board requests and responses are guarded by enabled state/generation.
- desktop/src-tauri/src/runtime.rs:444-455Withdrawal stops sharing before persistence can fail.
- desktop/src-tauri/src/runtime.rs:564-573Generation change, task abort and queue/board clearing.
- desktop/src-tauri/src/runtime.rs:1171-1189Upload loop checks authorization.
- desktop/src-tauri/src/runtime.rs:1213-1243Community board fetches check authorization.
C08 No backfillClaudeMac: VerifiedWindows and Linux: VerifiedCodexMac: VerifiedWindows and Linux: Verified
Only turns completed after the current launch or the latest switch-on are eligible for upload; historical turns are never uploaded.
Claude
Mac enable() sets consentStartedAt=now at launch activation or switch-on, and enqueue() drops any metric with completedAt before it (or in the future); HistoryStore only passes records new to history and final. Desktop SharingQueue.enable records enabled_since when the sharing loop starts (after launch/consent/retry), disable() clears it, and enqueue() skips completed_at < enabled_since; ingest() enqueues only while sharing_active. Replayed history therefore never qualifies.
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:62-69consentStartedAt = now on enable
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:92-106completedAt >= consentStartedAt filter
- macos/Sources/TokrateApp/Stores/HistoryStore.swift:221-225Activation at launch time
- desktop/core/src/sharing.rs:378-415enabled_since filter, primary turns only when final
- desktop/src-tauri/src/runtime.rs:1146-1154Queue enabled when the authorized loop starts
Codex
Eligibility begins at activation in the current process and resets on every re-enable. Queue insertion requires completion at or after that boundary and rejects future timestamps. Loaded historical records are not fed into sharing; newly discovered records still undergo the boundary check.
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:62-69Fresh activation establishes the completion-time boundary.
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:92-105Enqueue rejects older and future completions.
- macos/Sources/TokrateApp/Stores/HistoryStore.swift:335-336Only newly finalized records are offered to sharing.
- desktop/core/src/sharing.rs:378-405Rust activation and enqueue completion-time checks.
- desktop/src-tauri/src/runtime.rs:1098-1153Runtime activation creates a fresh queue boundary.
C09 Pseudonymous identityClaudeMac: VerifiedWindows and Linux: VerifiedCodexMac: VerifiedWindows and Linux: Verified
The installation identity is a random Ed25519 key stored in the OS credential store (macOS Keychain, Windows Credential Manager, Linux Secret Service). Only the public key and signatures are sent; the private key never leaves the store or the process. No user name, host name, hardware or device identifier, MAC address or serial number is collected or sent.
Claude
Mac creates a CryptoKit Curve25519 signing key and stores it as a generic password (AfterFirstUnlockThisDeviceOnly). Desktop draws 32 bytes from OsRng and stores them via keyring (Windows native, macOS native, Linux Secret Service), held in Zeroizing memory. Requests carry only the base64 public key and signature headers; no code reads user, host, hardware or MAC identifiers, and the ephemeral/cookieless clients add none. Sparkle system profiling is off.
- macos/Sources/TokrateApp/Services/KeychainIdentity.swift:6-31Random Ed25519 key in Keychain
- shared/Sources/TokrateCore/Sharing/SharingTransport.swift:63-75Only public key and signature headers
- desktop/src-tauri/src/runtime.rs:981-1001OsRng key in OS credential store, Zeroizing
- desktop/src-tauri/src/runtime.rs:1170-1181Upload headers: key, signature, content type
- desktop/src-tauri/Cargo.toml:25-30keyring backends per platform
Codex
Mac generates an Ed25519 key and stores it in Keychain; Rust uses OS randomness and native keyring backends for macOS, Windows or Linux. Only base64 public keys and signatures enter HTTP headers. Private key material is held inside native process memory for signing and never exposed through IPC. No username, hostname, hardware identifier, MAC address or serial collection path was found.
- macos/Sources/TokrateApp/Services/KeychainIdentity.swift:8-27Random Ed25519 identity and Keychain storage.
- shared/Sources/TokrateCore/Sharing/SharingTransport.swift:64-74Only public key and body signature are attached to requests.
- desktop/src-tauri/src/runtime.rs:981-999Native keyring lookup and OS-random key generation.
- desktop/src-tauri/Cargo.toml:25-30Native credential-store backend selection.
- desktop/core/src/sharing.rs:245-268Signed request contains body, public key and signature only.
C10 Known network destinations onlyClaudeMac: VerifiedWindows and Linux: VerifiedCodexMac: Not verifiableWindows and Linux: Verified
All network requests go to tokrate.dev (sample upload, community statistics, update feeds) or GitHub (update packages), over HTTPS. Update packages are accepted only from this repository’s GitHub release assets; update requests may follow ordinary HTTP redirects (GitHub serves release assets from its own download hosts), which is documented and expected, provided packages are installed only after signature verification. Sharing requests reject redirects and use no cookies or persistent cache. No other host is contacted, and local-only features (history export, tokrate inspect) make no network requests.
Claude
The only request code targets https://tokrate.dev/api/public/v1/{samples,board}, the fixed update feeds on tokrate.dev, and package URLs that both clients pin to https://github.com/mattivilola/tokrate-clients/releases/download/. Sharing clients reject redirects and use no cookies or cache (Mac ephemeral session with nil cookie storage/cache; reqwest without cookie feature, https_only, redirect none). tokrate inspect and export-history make no requests. Sparkle release-notes links are not pinned (F03, info).
- shared/Sources/TokrateCore/Sharing/SharingTransport.swift:26-61Ephemeral session, no cookies/cache, redirects refused
- desktop/src-tauri/src/runtime.rs:1121-1127reqwest: https_only, no redirects, fixed User-Agent
- macos/Sources/TokrateApp/Services/AppUpdates.swift:20-62Package URLs pinned to the GitHub release path
- desktop/src-tauri/src/updater_state.rs:7-35Desktop download URL pinned to the GitHub release path
- desktop/src-tauri/tauri.conf.json:40-45Fixed alpha feed endpoint
- shared/Sources/tokrate/main.swift:45-65inspect reads a file only
Codex
Community requests use fixed HTTPS endpoints, no redirects, cookies or persistent cache. Update feeds are fixed; package URLs are checked against this repository's GitHub release assets. Inspect/export are local. Mac package restrictions do not constrain Sparkle release-note URLs/content: the standard driver is used and the published appcast is unavailable offline, leaving possible additional destinations unsettled.
- shared/Sources/TokrateCore/Sharing/SharingTransport.swift:31-60Ephemeral cookie/cache-free session rejects redirects.
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:56-56Fixed community API origin.
- desktop/src-tauri/src/runtime.rs:1121-1126HTTPS-only, redirect-rejecting sharing client.
- macos/Sources/TokrateApp/Services/AppUpdates.swift:29-61Update package and delta URLs are restricted to release assets.
- macos/Sources/TokrateApp/Services/AppUpdates.swift:80-88Standard Sparkle driver has no repository release-note restriction.
- desktop/src-tauri/tauri.conf.json:40-44Fixed Tauri feed and embedded updater public key.
- desktop/src-tauri/src/main.rs:162-180Package URL checked again immediately before download.
- shared/Sources/tokrate/main.swift:45-64CLI inspect reads and prints local metrics only.
C11 No tracking SDKsClaudeMac: VerifiedWindows and Linux: VerifiedCodexMac: VerifiedWindows and Linux: Verified
No analytics, advertising, crash-reporting or telemetry SDK is included in any dependency set, and no separate install, launch or usage event is sent.
Claude
Package.resolved pins only Sparkle 2.10.0. The Rust lock files contain Tauri, reqwest, rusqlite, keyring, notify, rfd and their transitive crates; log/tracing are facades with no subscriber or exporter crate present. package-lock.json holds React, lucide-react, @tauri-apps/api and build/test tooling. No analytics, advertising, crash-reporting or telemetry package was found, and no install/launch/usage event is sent by app code.
- Package.resolved:1-15Single dependency: Sparkle 2.10.0
- desktop/core/Cargo.toml:8-21Core dependencies
- desktop/src-tauri/Cargo.toml:9-30Host dependencies
- desktop/package.json:12-27UI dependencies
Codex
Read every lock file and direct dependency manifest. No analytics, advertising, crash-reporting or telemetry SDK, or independent install/launch/usage beacon, was found. Networking dependencies serve sharing and signed updates. log/tracing dependencies provide local logging infrastructure, with no telemetry exporter configured.
- Package.swift:13-13Swift's external dependency is Sparkle.
- Package.resolved:5-10Sparkle dependency is pinned.
- desktop/core/Cargo.toml:8-21Core dependency set covers parsing, metrics, SQLite and crypto.
- desktop/src-tauri/Cargo.toml:9-30Native dependencies provide Tauri, updates, HTTP, watching and credentials.
- desktop/src-tauri/Cargo.lock:4713-4742Pinned updater and networking dependencies.
- desktop/core/Cargo.lock:714-728Resolved core dependency graph.
- desktop/package-lock.json:1-25Frontend root dependencies; complete lock entries were inspected.
C12 Update integrity and privacyClaudeMac: VerifiedWindows and Linux: VerifiedCodexMac: VerifiedWindows and Linux: Verified
Updates are verified against public keys embedded in the app before installation (Sparkle EdDSA on Mac, Tauri updater minisign on Windows and Linux), and installation requires a user action. Update checks send no contribution key, measurements or coding-tool content. Sparkle system-profile reporting is disabled. The automatic-check switch is respected.
Claude
Mac release bundles embed SUPublicEDKey, require a signed feed, verify before extraction, disable automatic install and system profiling (also set in code), and the Settings switch drives automaticallyChecksForUpdates. Desktop embeds a minisign pubkey (build.rs asserts it matches the repo key), checks only for NSIS/AppImage, gates automatic checks on the saved switch with a 24 h throttle, and installs only from the install_update command bound to a button; the download URL is checked before storing and before installing. Neither path sends the contribution key.
- macos/script/package_app.sh:89-100Feed, signed feed, no auto-install, no profiling, EdDSA key
- macos/Sources/TokrateApp/Services/AppUpdates.swift:74-110sendsSystemProfile=false, switch, manual check
- macos/Sources/TokrateApp/Views/UpdateSettingsView.swift:17-21Toggle sets automatic checks
- desktop/src-tauri/build.rs:28-44Embedded pubkey must equal repo key
- desktop/src-tauri/src/updater_state.rs:155-182Automatic checks honour switch and throttle
- desktop/src-tauri/src/main.rs:148-186Install only on command; URL rechecked
- desktop/ui/components/Updates.tsx:70-77Install triggered by a button
Codex
Release builds embed updater public keys. Sparkle requires a signed feed and signature verification before extraction; Tauri verifies minisign through its updater plugin. Both download/install paths require user action. Automatic-check preferences are respected, and Mac system-profile reporting is disabled. Update requests use a separate updater client with no contribution identity, measurements or coding-tool content.
- macos/script/package_app.sh:89-99Signed feed, pre-extraction verification and automatic installation disabled.
- macos/Sources/TokrateApp/Services/AppUpdates.swift:80-109Profile reporting disabled; manual and automatic check controls.
- desktop/src-tauri/tauri.conf.json:40-44Tauri minisign public key and fixed update feed.
- desktop/src-tauri/src/main.rs:149-186Explicit native install command delegates signature verification.
- desktop/src-tauri/src/updater_state.rs:155-181Automatic check switch and cadence are enforced.
- desktop/ui/components/Updates.tsx:121-125Installation requires an explicit UI action.
- .github/workflows/desktop.yml:55-76Release signing uses CI secrets, not committed private keys.
C13 Bounded memory-only queueClaudeMac: VerifiedWindows and Linux: VerifiedCodexMac: VerifiedWindows and Linux: Verified
Unsent uploads are held only in memory, capped at 1,000 samples and 24 hours, and are lost on quit. Nothing pending is written to disk.
Claude
Mac SharingSession keeps Pending samples in an in-memory array, drops the oldest at 1,000 on every insert and prunes samples whose observedAt is over 24 h old; nothing in it is persisted. Desktop SharingQueue is a VecDeque capped at 1,000 with the same 24 h prune; Runtime snapshots expose only the count and history saves only records/checkpoints.
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:92-106Cap enforced per insert
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:219-22324 h prune
- desktop/core/src/sharing.rs:415-431Cap enforced per insert
- desktop/core/src/sharing.rs:497-50524 h prune
Codex
Both upload queues are process-memory collections, capped at 1,000 samples and pruned after 24 hours. OFF clears them and quit loses them. Persistence paths write normalized local history/preferences/checkpoints, never pending upload envelopes or retry state.
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:23-49Mac queue constants and in-memory pending representation.
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:92-105Insertion enforces the queue cap.
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:219-222Twenty-four-hour queue pruning.
- desktop/core/src/sharing.rs:20-23Rust queue limits.
- desktop/core/src/sharing.rs:327-350Queue state is an in-memory collection.
- desktop/core/src/sharing.rs:392-439Insertion caps samples and handles memory-only bookkeeping.
- desktop/core/src/sharing.rs:497-504Rust expiry pruning.
C14 No sensitive loggingClaudeMac: VerifiedWindows and Linux: VerifiedCodexMac: VerifiedWindows and Linux: Verified
No prompts, responses, paths, session identifiers, keys or signatures are written to logs, the console, the OS log or crash output.
Claude
No print/NSLog/os_log/Logger in Swift app or core; the CLI writes only usage, fixed errors and export counts by client/model to stderr. Rust has a single eprintln naming the source id and an error kind, no path; no console.* in the UI; no log subscriber is installed. Panic/expect messages are static strings.
- shared/Sources/tokrate/main.swift:15-27CLI stderr messages are fixed text
- desktop/src-tauri/src/watcher.rs:313-319Only eprintln: source id and error kind
Codex
Searched all tracked production Swift/Rust/frontend sources for console, file and OS logging sinks and traced their arguments. Native watcher messages contain fixed source labels and categorized errors; CLI output is normalized metrics and generic errors. No coding content, source paths, source identifiers, private/public keys or signatures are passed to logging sinks.
- desktop/src-tauri/src/watcher.rs:314-318Watcher logging uses a fixed source category and sanitized error reason.
- shared/Sources/tokrate/main.swift:45-64Inspect emits normalized records and generic parse errors.
- shared/Sources/TokrateCore/ExportDestination.swift:15-23Export errors use fixed descriptions instead of source paths.
- macos/Sources/TokrateApp/Stores/HistoryStore.swift:530-532History failures report a generic status.
- desktop/ui/components/ErrorBoundary.tsx:19-20Error boundary retains only failure state, not error content.
C15 No remote code or contentClaudeMac: VerifiedWindows and Linux: VerifiedCodexMac: Not verifiableWindows and Linux: Verified
The app UI loads no remote web content and executes no downloaded code other than signed updates. On Windows and Linux the Tauri content security policy and capabilities limit the frontend to what it needs, and the IPC commands it exposes cannot read arbitrary files or reach arbitrary network destinations (fixed-purpose commands such as an update check or switching sharing on, which reach only the endpoints of C10, are expected).
Claude
The Mac UI is SwiftUI with no web view in app code; links open the system browser. Desktop windows load the bundled index.html; the CSP limits scripts to 'self' and connections to IPC; capabilities grant only the 19 app commands (no core/plugin permissions), enforced by a unit test. Commands take enumerated strings or booleans; folder roots come only from the native picker or defaults, snapshot returns parsed records, and network commands reach only the C10 endpoints.
- desktop/src-tauri/tauri.conf.json:27-29CSP
- desktop/src-tauri/capabilities/default.json:1-29Only app commands permitted
- desktop/src-tauri/src/main.rs:204-258Folder picker, enumerated website pages
- desktop/src-tauri/src/runtime.rs:135-145SettingsPatch has no path fields
- desktop/src-tauri/src/main.rs:462-483Test ties handler, manifest and capability lists
Codex
Tauri ships a local frontend, restrictive CSP and explicit capabilities; IPC exposes fixed-purpose actions, normalized snapshots and a native folder picker, not arbitrary file/network primitives. Main Mac UI is native SwiftUI. Sparkle's standard update UI can display appcast-supplied release notes, and repository configuration does not constrain or disable that remote content, so the absolute Mac promise cannot be settled.
- desktop/src-tauri/tauri.conf.json:6-9Production frontend is bundled locally.
- desktop/src-tauri/tauri.conf.json:27-28CSP restricts scripts and network access.
- desktop/src-tauri/capabilities/default.json:8-27Explicit scoped application capabilities.
- desktop/src-tauri/build.rs:6-48Exposed command list is generated from fixed app commands.
- desktop/src-tauri/src/main.rs:204-228Folder selection is a native picker, not arbitrary file-reading IPC.
- desktop/src-tauri/src/main.rs:249-257External URLs come from a fixed page allowlist.
- macos/Sources/TokrateApp/Services/AppUpdates.swift:80-84Standard Sparkle driver is selected without a release-note policy.
C16 Upload timingClaudeMac: PartialWindows and Linux: PartialCodexMac: VerifiedWindows and Linux: Verified
A sample is uploaded only after its five-minute observedAt period has ended, after a random delay, so neither the request’s sentAt nor its sending time places a turn more precisely than its five-minute period. Community statistics fetches do not reveal when turns completed.
Claude
Both assign each sample the first 5-minute boundary at or after max(queue time, observedAt+600 s) plus one OsRng/SystemRandom jitter per slot, so normally settled turns leave at slot+jitter and sentAt is the send time; board fetches follow their own 30 s cadence. But enqueue never wakes the sharing loop: a sample queued less than ~30 s before its slot time leaves at the next board wake, together with the board fetch, which reveals its queue time to about 30 s (F01). Turn completion stays within its period.
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:175-205Slot+jitter and loop sleep computation
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:71-77Loop sleeps; enqueue does not wake it
- desktop/core/src/sharing.rs:531-549upload_slot
- desktop/src-tauri/src/runtime.rs:1234-1243Sleep until next eligible or board
- desktop/src-tauri/src/schedule.rs:50-65sharing_delay
Codex
Uploads wait until at least observedAt+600 seconds and a slot-wide random delay, rather than a delay tied to precise completion time. Later queue additions are rounded to a five-minute sending slot. Retries preserve eligibility. Community board polling uses its independent cadence and is not triggered by turn completion.
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:168-204Random slot jitter and bucket-based earliest upload time.
- desktop/core/src/sharing.rs:392-418Rust samples share per-slot random jitter.
- desktop/core/src/sharing.rs:535-548Eligibility is bucket-based and queued time is rounded up.
- desktop/src-tauri/src/schedule.rs:44-65Board refresh cadence is independent of completed turns.
What leaves your computer
This is what the reviewers found in the code, listed by each of them and merged here. Uploads happen only after you opt in to sharing. See one contribution, field by field, and the privacy policy for how the server treats them.
Fields in an upload (27)
Documented means the reviewer found the field named in the README or the metrics contract. “Can hold user content” is the reviewers’ answer to whether the value could contain something the user wrote or a file path.
| Field | Type | Where the value comes from | Documented | Can hold user content |
|---|---|---|---|---|
schemaVersion | integer | Envelope constant 1 | Yes | No |
sentAt | string | Envelope: wall-clock time the request is built (ISO 8601, seconds) | Yes | No |
samples | array | Envelope: 1-50 SharedSample objects | Yes | No |
sampleId | string | Fresh random UUID per sample; kept across retries | Yes | No |
observedAt | string | completedAt floored to a 5-minute UTC bucket | Yes | No |
client | string | Coding-tool id from the supported client/parser/metric tuple allowlist | Yes | No |
clientVersion | string | Coding tool's own version if it matches ^[A-Za-z0-9.+_-]{1,40}$, else unknown | Yes | No |
appVersion | string | Constant 0.1.20 | Yes | No |
parserVersion | string | From the allowlisted tuple | Yes | No |
metricVersion | string | From the allowlisted tuple | Yes | No |
model | string | Model id as logged if ^[A-Za-z0-9._-]{1,80}$, else unknown (Claude Bedrock/Vertex ids normalized) | Yes | No |
provider | string | Allowlisted provider (openai, anthropic, xai, google, amazon-bedrock, google-vertex) or unknown | Yes | No |
reasoningEffort | string | Allowlisted effort value or unknown | Yes | No |
sourceKind | string | primary, subagent or unknown | Yes | No |
outputTokens | integer | Turn output token count (0-10,000,000) | Yes | No |
reasoningOutputTokens | integer|null | Reasoning tokens if within 0..outputTokens | Yes | No |
durationMs | number | Whole-turn duration in ms (1 ms - 24 h) | Yes | No |
ttftMs | number|null | Codex-reported time to first token, Codex only | Yes | No |
responseOutputTokens | integer|null | Sum over qualifying API responses, null unless plausible | Yes | No |
responseDurationMs | number|null | Summed response durations in ms | Yes | No |
responseCount | integer|null | Number of qualifying responses | Yes | No |
providerRegion | string|null | Bedrock inference-profile region from allowlist, null for other providers | Yes | No |
delegatedOutputTokens | integer|null | Subagent output attributed to a primary turn; null for subagent records | Yes | No |
surface | string|null | Category cli/desktop/ide/sdk/other derived from originator/entrypoint/folder | Yes | No |
inputTokens | integer|null | Turn input tokens incl. cached, null when not reported | Yes | No |
cacheReadInputTokens | integer|null | Cached input tokens read, <= inputTokens | Yes | No |
cacheWriteInputTokens | integer|null | Cache-write tokens (Claude Code, OpenCode anthropic) else null | Yes | No |
Where reviewers worded a value’s source differently, the table shows the first reviewer’s wording. Every wording is in the raw files.
Network requests (12)
POST
https://tokrate.dev/api/public/v1/samplesMac, Windows and Linux · listed by Claude, Codex
- Purpose
- Upload signed batches of allowlisted turn measurements
- When
- Only while sharing is authorized on the current notice; at 5-minute slots plus jitter, retries at most every 30 s
- Sends
- Envelope body; X-Tokrate-Key (stable public key), X-Tokrate-Signature, Content-Type, User-Agent Tokrate/0.1.20 (Mac also Accept, Accept-Language: en); source IP
What each reviewer wroteTheir own words, with the code they cited
Claude
Purpose. Upload signed batches of allowlisted turn measurements
When. Only while sharing is authorized on the current notice; at 5-minute slots plus jitter, retries at most every 30 s
Sends. Envelope body; X-Tokrate-Key (stable public key), X-Tokrate-Signature, Content-Type, User-Agent Tokrate/0.1.20 (Mac also Accept, Accept-Language: en); source IP
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:121-127Mac upload
- shared/Sources/TokrateCore/Sharing/SharingTransport.swift:39-75Headers and signing
- desktop/src-tauri/src/runtime.rs:1169-1186Desktop upload
Codex
Purpose. Upload signed community metric batches.
When. Only with current affirmative consent, after bucket-based eligibility and random delay; cancelled on OFF.
Sends. JSON envelope and 24 allowlisted sample keys; X-Tokrate-Key, X-Tokrate-Signature, Content-Type, fixed app-version User-Agent and Accept headers.
- shared/Sources/TokrateCore/Sharing/SharingTransport.swift:64-74Signed POST body and public-key/signature headers.
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:56-56Fixed production API origin.
- desktop/src-tauri/src/runtime.rs:1171-1188Guarded Rust POST to the same fixed endpoint.
GET
https://tokrate.dev/api/public/v1/boardMac, Windows and Linux · listed by Claude, Codex
- Purpose
- Fetch community statistics and alerts (max 1 MiB)
- When
- Only while sharing is authorized; about every 30 s
- Sends
- No key or body; User-Agent Tokrate/0.1.20 (Mac also Accept, Accept-Language: en); source IP and request cadence
What each reviewer wroteTheir own words, with the code they cited
Claude
Purpose. Fetch community statistics and alerts (max 1 MiB)
When. Only while sharing is authorized; about every 30 s
Sends. No key or body; User-Agent Tokrate/0.1.20 (Mac also Accept, Accept-Language: en); source IP and request cadence
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:147-158Mac board fetch
- desktop/src-tauri/src/runtime.rs:1246-1266Desktop board fetch
Codex
Purpose. Fetch community statistics and alerts.
When. Independent periodic cadence while sharing is authorized; cancelled and disabled on OFF.
Sends. No body, contribution key or signature. Fixed app-version User-Agent and normal Accept metadata; Mac uses fixed Accept-Language en.
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:145-158Guarded board GET.
- desktop/src-tauri/src/runtime.rs:1246-1265Fixed board endpoint and bounded response parsing.
- shared/Sources/TokrateCore/Sharing/SharingTransport.swift:40-43Fixed common HTTP metadata.
GET
https://tokrate.dev/updates/macos/stable.xmlMac · listed by Claude, Codex
- Purpose
- Sparkle signed appcast check
- When
- Sparkle's schedule when automatic checks are on (default), or Check for Updates; independent of sharing
- Sends
- Sparkle default request (app name/version, Sparkle UA); system profile disabled; no contribution key
What each reviewer wroteTheir own words, with the code they cited
Claude
Purpose. Sparkle signed appcast check
When. Sparkle's schedule when automatic checks are on (default), or Check for Updates; independent of sharing
Sends. Sparkle default request (app name/version, Sparkle UA); system profile disabled; no contribution key
- macos/Sources/TokrateApp/Services/AppUpdates.swift:5-18Fixed feed URL
- macos/script/package_app.sh:89-95Info.plist updater keys
Codex
Purpose. Check signed Mac update feed.
When. Automatic checks when enabled, or explicit manual check; independent of community sharing.
Sends. Standard Sparkle HTTP metadata such as app/updater version; no contribution key, metrics or coding content; system-profile reporting disabled.
- macos/script/package_app.sh:89-95Fixed feed and signed-feed requirement.
- macos/Sources/TokrateApp/Services/AppUpdates.swift:88-109Profile reporting disabled and check controls.
GET
https://tokrate.dev/updates/desktop/alpha.jsonWindows and Linux · listed by Claude, Codex
- Purpose
- Tauri updater feed check (NSIS and AppImage only)
- When
- On UI start and periodically if the automatic switch is on (24 h throttle), or a manual check; independent of sharing
- Sends
- Tauri updater plugin default request; no contribution key or measurements
What each reviewer wroteTheir own words, with the code they cited
Claude
Purpose. Tauri updater feed check (NSIS and AppImage only)
When. On UI start and periodically if the automatic switch is on (24 h throttle), or a manual check; independent of sharing
Sends. Tauri updater plugin default request; no contribution key or measurements
- desktop/src-tauri/tauri.conf.json:40-45Feed and pubkey
- desktop/src-tauri/src/main.rs:93-126Check command
- desktop/src-tauri/src/updater_state.rs:155-182Automatic-check gating
Codex
Purpose. Check Windows/Linux update metadata.
When. Enabled automatic checks or manual user check on installations supporting the native updater; independent of sharing.
Sends. Standard updater HTTP metadata; no contribution key, measurements or coding-tool content.
- desktop/src-tauri/tauri.conf.json:40-44Fixed HTTPS update endpoint.
- desktop/src-tauri/src/main.rs:93-117Native updater check.
- desktop/src-tauri/src/updater_state.rs:155-181Automatic-check gating.
GET
https://github.com/mattivilola/tokrate-clients/releases/download/{tag}/{asset}Mac, Windows and Linux · listed by Claude, Codex
- Purpose
- Download a signed update package (may redirect to GitHub asset hosts)
- When
- Only after the user chooses to install an available update
- Sends
- Ordinary HTTP request from the updater; no contribution key or measurements
What each reviewer wroteTheir own words, with the code they cited
Claude
Purpose. Download a signed update package (may redirect to GitHub asset hosts)
When. Only after the user chooses to install an available update
Sends. Ordinary HTTP request from the updater; no contribution key or measurements
- macos/Sources/TokrateApp/Services/AppUpdates.swift:23-62Mac package URL policy
- desktop/src-tauri/src/main.rs:148-186Desktop install with URL recheck
Codex
Purpose. Download a signed update package, including a permitted Mac delta.
When. After the user chooses an offered update; initial package URL must pass the release-repository allowlist.
Sends. Updater download HTTP metadata, without contribution identity or coding-tool metrics/content.
- macos/Sources/TokrateApp/Services/AppUpdates.swift:29-61Mac package and delta URL allowlist.
- macos/script/package_app.sh:92-95Automatic installation disabled; signature required.
- desktop/src-tauri/src/main.rs:149-180User-triggered download with a second URL-policy check.
other
https://tokrate.dev/{,privacy,terms,download}Mac, Windows and Linux · listed by Claude
- Purpose
- Website links opened in the system browser, not by the app's own HTTP stack
- When
- Only when the user clicks a link (works with sharing off)
- Sends
- Whatever the user's browser sends; nothing from the app
What each reviewer wroteTheir own words, with the code they cited
Claude
Purpose. Website links opened in the system browser, not by the app's own HTTP stack
When. Only when the user clicks a link (works with sharing off)
Sends. Whatever the user's browser sends; nothing from the app
- macos/Sources/TokrateApp/Views/ConsentDisclosureView.swift:112-117Mac Link views
- desktop/src-tauri/src/main.rs:248-258Desktop enumerated pages via open::that
GET
https://{github-download-host}/{redirect-path}Mac, Windows and Linux · listed by Codex
- Purpose
- Follow ordinary update-package download redirects.
- When
- When GitHub redirects a permitted release-asset download; signature verification precedes installation.
- Sends
- Standard updater download headers; no contribution headers or measurement body.
What each reviewer wroteTheir own words, with the code they cited
Codex
Purpose. Follow ordinary update-package download redirects.
When. When GitHub redirects a permitted release-asset download; signature verification precedes installation.
Sends. Standard updater download headers; no contribution headers or measurement body.
- macos/Sources/TokrateApp/Services/AppUpdates.swift:80-84Sparkle performs package downloads through its standard updater.
- desktop/src-tauri/src/main.rs:168-180Tauri updater performs the permitted package download.
GET
{scheme}://{appcast-release-notes-host}/{path}Mac · listed by Codex
- Purpose
- Potential Sparkle release-note loading; actual URL is unknown.
- When
- Conditional on signed appcast release-note content and standard updater UI; no repository policy constrains this path.
- Sends
- Standard release-note/webview request metadata; no app code attaches contribution keys or metric bodies.
What each reviewer wroteTheir own words, with the code they cited
Codex
Purpose. Potential Sparkle release-note loading; actual URL is unknown.
When. Conditional on signed appcast release-note content and standard updater UI; no repository policy constrains this path.
Sends. Standard release-note/webview request metadata; no app code attaches contribution keys or metric bodies.
- macos/Sources/TokrateApp/Services/AppUpdates.swift:80-84Standard updater UI is used without a release-note delegate policy.
- macos/Sources/TokrateApp/Services/AppUpdates.swift:39-61Appcast filtering inspects package URLs, not release-note destinations.
GET
https://tokrate.dev/Mac, Windows and Linux · listed by Codex
- Purpose
- Open the public website in the external browser.
- When
- Explicit click; independent of sharing and updater preferences.
- Sends
- Browser-controlled headers/cookies; the app does not attach its key, signature or metrics.
What each reviewer wroteTheir own words, with the code they cited
Codex
Purpose. Open the public website in the external browser.
When. Explicit click; independent of sharing and updater preferences.
Sends. Browser-controlled headers/cookies; the app does not attach its key, signature or metrics.
- macos/Sources/TokrateApp/Views/WebsiteLinkView.swift:8-31Deliberate external-browser link.
- desktop/src-tauri/src/main.rs:249-257Fixed home URL passed to the external browser.
GET
https://tokrate.dev/privacyMac, Windows and Linux · listed by Codex
- Purpose
- Open the privacy policy in the external browser.
- When
- Explicit click on Privacy, including during consent.
- Sends
- Browser-controlled headers/cookies; no app contribution identity or measurement payload.
What each reviewer wroteTheir own words, with the code they cited
Codex
Purpose. Open the privacy policy in the external browser.
When. Explicit click on Privacy, including during consent.
Sends. Browser-controlled headers/cookies; no app contribution identity or measurement payload.
- macos/Sources/TokrateApp/Views/ConsentDisclosureView.swift:111-116Fixed privacy-policy link.
- desktop/src-tauri/src/main.rs:249-257Fixed privacy URL passed to the external browser.
GET
https://tokrate.dev/termsMac, Windows and Linux · listed by Codex
- Purpose
- Open the terms in the external browser.
- When
- Explicit click on Terms, including during consent.
- Sends
- Browser-controlled headers/cookies; no app contribution identity or measurement payload.
What each reviewer wroteTheir own words, with the code they cited
Codex
Purpose. Open the terms in the external browser.
When. Explicit click on Terms, including during consent.
Sends. Browser-controlled headers/cookies; no app contribution identity or measurement payload.
- macos/Sources/TokrateApp/Views/ConsentDisclosureView.swift:111-116Fixed terms link.
- desktop/src-tauri/src/main.rs:249-257Fixed terms URL passed to the external browser.
GET
https://tokrate.dev/downloadWindows and Linux · listed by Codex
- Purpose
- Open desktop download information in the external browser.
- When
- Explicit desktop-downloads action on installations using manual updates.
- Sends
- Browser-controlled headers/cookies; no app contribution identity or measurement payload.
What each reviewer wroteTheir own words, with the code they cited
Codex
Purpose. Open desktop download information in the external browser.
When. Explicit desktop-downloads action on installations using manual updates.
Sends. Browser-controlled headers/cookies; no app contribution identity or measurement payload.
- desktop/src-tauri/src/main.rs:249-257Fixed download URL passed to the external browser.
Findings and our response
9 findings from the two reviewers: 1 fixed, 4 accepted and 4 open. A fixed finding links the change in the clients repository. An accepted finding was read and its behaviour or wording was left as it is, with the reason given. An open finding is not finished yet, and its response says what is planned.
Medium (1)
Codex F01: A community response can crash the Mac dashboard
- What the reviewer found
- The board decoder accepts a finite positive medianThroughput without a minimum. With a matching cohort, a 24h or 15m window, and at least three local turns, CommunityLine.make converts the computed percentage directly to Int. A service response with medianThroughput=1e-20 and a local median of 20 produces about 2e23 percent, outside Int's range, and Swift traps when the menu is rendered.
- What it could mean for you
- The community service can terminate the Mac app while sharing is on, interrupting monitoring and losing its memory-only upload queue.
- The reviewer’s recommendation
- Validate the ratio after arithmetic and use a checked integer conversion such as Int(exactly:), returning an unavailable comparison when it is non-finite or out of range.
- Affects
- Mac
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:155-158Untrusted board JSON is decoded and stored after only the schema check.
- shared/Sources/TokrateCore/Sharing/GlobalBoard.swift:26-41medianThroughput is an unrestricted optional Double.
- macos/Sources/TokrateApp/Support/Presentation.swift:302-315A positive finite denominator passes; the unchecked Int conversion can trap.
- macos/Sources/TokrateApp/Views/MenuBarView.swift:252-257The visible dashboard invokes this comparison while sharing is enabled.
Our response Fixed
Fixed in 0.1.20 after the review: a community median above the speed the clients upload themselves is ignored, and the percentage is never forced into an integer.
Low (2)
Claude F01: A sample queued just before its slot leaves on the board cadence, timing its readiness to ~30 s
- What the reviewer found
- Both clients compute the sharing loop's sleep right after each pass: until the board is next due (30 s) or the earliest queued sample's slot+jitter. enqueue() (Mac SharingSession.enqueue; desktop SharingQueue::enqueue from Runtime::ingest) never wakes the loop. A sample whose slot is set by its queue time (a primary turn that waited for background subagents, or turns read after a resume) and that is queued less than ~30 s before slot+jitter is sent at the next board wake, in the same pass as the board GET, not at slot+jitter. Board GETs are visible every ~30 s, so a POST coinciding with one shows the sample was queued in the preceding ~30 s.
- What it could mean for you
- The service or a network observer can sometimes learn, to about 30 s, when a delayed sample became ready (e.g. when background subagent work finished or monitoring resumed), finer than the five-minute period the README promises. The turn's completion time is not revealed more precisely.
- The reviewer’s recommendation
- Wake the sharing loop when enqueue adds a sample whose eligible time is earlier than the current sleep target, or always sleep until the exact earliest slot+jitter; alternatively, if a sample is found past its eligible time at a board wake, postpone it to the next slot with fresh jitter.
- Affects
- Mac, Windows and Linux · promises C16
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:71-77Loop: refresh, then sleep the computed delay
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:92-106enqueue does not signal the loop
- shared/Sources/TokrateCore/Sharing/SharingSession.swift:175-182Sleep target fixed at the end of each pass
- desktop/src-tauri/src/runtime.rs:1161-1243Upload then board in one pass; sleep computed once
- desktop/src-tauri/src/schedule.rs:54-65sharing_delay capped at board interval
Our response Open
Planned for 0.1.21: the upload loop will wake exactly at each slot instead of at the next statistics fetch. A turn’s completion is still never placed more precisely than its five-minute period.
Codex F02: The inspect CLI buffers an unbounded number of turns
- What the reviewer found
- tokrate inspect repeatedly reads one-MiB batches until no bytes remain, appends every metric to one records array, then sorts and encodes the entire array into another in-memory buffer. The reader's line and per-poll byte limits do not bound this accumulated output. An arbitrarily large valid session file can exhaust memory; this requires a large source file and the user invoking inspect.
- What it could mean for you
- A very large local session can hang or terminate the CLI. The path does not upload content or alter the source file.
- The reviewer’s recommendation
- Stream inspection output or enforce a documented total record/byte limit and report truncation.
- Affects
- Mac
- shared/Sources/tokrate/main.swift:45-60All parsed turns and the final encoded JSON are held in memory.
- shared/Sources/TokrateCore/JSONLFileReader.swift:54-55The byte cap applies to each poll, not the total inspection.
Our response Open
Planned for 0.1.21: tokrate inspect will stream records instead of holding them all. It is a local command you run yourself.
Info (6)
Claude F02: Read-only SQLite opens may still create -wal/-shm sidecar files in the tool's folder
- What the reviewer found
- Antigravity and OpenCode databases are opened with SQLITE_OPEN_READONLY and file:...?mode=ro, without immutable or readonly_shm. Per SQLite's documented WAL behaviour, a read-only connection to a WAL database opens the -shm wal-index read/write and can create missing -wal/-shm files when the directory is writable; it cannot checkpoint or delete them on close. The database contents are never modified by Tokrate's code.
- What it could mean for you
- Possibly empty -wal/-shm files left next to a closed tool's database. No data change; SQLite treats them as normal sidecars.
- The reviewer’s recommendation
- Document that SQLite may create its own coordination files, or skip a read when the database is in WAL mode and neither -wal nor -shm exists (the tool is not running and the main file is complete).
- Affects
- Mac, Windows and Linux · promises C04
- shared/Sources/TokrateCore/SQLite/ReadOnlySQLiteDatabase.swift:91-129mode=ro URI, read-only flags
- desktop/core/src/sqlite_read.rs:52-66Rust read-only open
Our response Accepted
SQLite creates its own coordination files for databases in WAL mode. Tokrate never writes the database contents.
Claude F03: Sparkle release-notes and info links are not pinned like package URLs
- What the reviewer found
- UpdateDownloadPolicy restricts each appcast item's package and delta URLs to the project's GitHub release path, but not releaseNotesURL/fullReleaseNotesURL/infoURL. Sparkle fetches release notes from whatever host the item names and renders them in its own web view. The appcast must be signed (SURequireSignedFeed) and generate_appcast.sh publishes no notes link, so only the release key holder could add one.
- What it could mean for you
- No current defect. A future signed appcast with a notes link would make the Mac app contact a host outside C10's list when showing an update.
- The reviewer’s recommendation
- Ignore or reject items whose release-notes link is outside tokrate.dev or the GitHub release path, or embed notes inline in the signed appcast only.
- Affects
- Mac · promises C10, C15
- macos/Sources/TokrateApp/Services/AppUpdates.swift:39-62Only fileURL and deltas checked
- macos/script/package_app.sh:94-95Signed feed required
- macos/script/generate_appcast.sh:30-35Only the archive is staged
Our response Accepted
Our signed update feed carries no release-notes link. Pinning that link as well is planned.
Claude F04: Source-folder paths are persisted in settings, outside the history file
- What the reviewer found
- The desktop settings.json stores all five source roots, including defaults under the user's home folder (which contain the account name), and the Mac stores a chosen folder's path and security-scoped bookmark in UserDefaults. History and checkpoints contain no paths, so C03 holds, but the contract's wording that source paths are never persisted could be read more broadly.
- What it could mean for you
- Local configuration only (owner-only file on desktop); nothing is uploaded.
- The reviewer’s recommendation
- Persist only non-default roots on desktop and clarify in the docs that chosen source folders are kept in app preferences.
- Affects
- Mac, Windows and Linux · promises C03
- desktop/src-tauri/src/runtime.rs:79-126Settings include all roots
- desktop/src-tauri/src/runtime.rs:435-439Settings written to settings.json
- macos/Sources/TokrateApp/Stores/HistoryStore.swift:236-246Chosen folder path and bookmark in UserDefaults
Our response Accepted
Chosen source folders are kept only in the app’s own owner-only settings and are never uploaded. The documentation will say so.
Claude F05: CI exposes the updater signing key to the full Tauri build
- What the reviewer found
- On pushes to main and manual runs, TAURI_SIGNING_PRIVATE_KEY and its password are in the environment of the step that runs `tauri build`, which compiles every third-party crate and runs their build scripts and the npm build. The Rust toolchain is 'stable' rather than a fixed version. Actions are pinned by SHA and secrets are not given to pull requests.
- What it could mean for you
- A compromised dependency build script in that step could read the updater signing key, which signs Windows/Linux updates.
- The reviewer’s recommendation
- Build unsigned artifacts first, then sign them in a separate step or job that runs only the signer with the key; pin the Rust toolchain version.
- Affects
- Windows and Linux · promises C12
- .github/workflows/desktop.yml:55-70Key in env of the build step
- .github/workflows/desktop.yml:35-37Unpinned stable toolchain
Our response Open
Planned: build Windows and Linux installers unsigned and sign updates in a separate CI step, with a pinned Rust toolchain.
Claude F06: Desktop shares Grok Build's clientVersion that the Mac rules exclude
- What the reviewer found
- The Mac SharedSample refuses Grok Build records that carry a client version (grokClientVersion) and its Grok parser never sets one. The desktop Grok parser reads clientVersion from usage.json and from_metric sends it. The value is regex-bounded and the field is documented, so this is not a privacy defect, but the clients disagree; if the service rejects such samples, the desktop acks and drops the whole batch on 400/422.
- What it could mean for you
- Possible loss of other samples in the same batch; no extra personal data.
- The reviewer’s recommendation
- Apply one Grok clientVersion rule in both clients (and the contract), and consider rejecting single samples rather than whole batches.
- Affects
- Windows and Linux · promises C01
- shared/Sources/TokrateCore/Sharing/SharedSample.swift:64-77Mac rejection rules incl. grokClientVersion
- desktop/core/src/grok.rs:461-466Desktop reads Grok clientVersion
- desktop/core/src/sharing.rs:173-178clientVersion sent when safe
- desktop/src-tauri/src/runtime.rs:1204-1207Whole batch dropped on 400/413/422
Our response Open
Planned for 0.1.21: one Grok Build client-version rule in both clients.
Claude F07: Signatures cover only the body; replay protection is left to the server
- What the reviewer found
- Both clients sign the exact body bytes with Ed25519 and send the public key; the signature does not bind the method, path or host, and there is no nonce. Freshness and duplicate detection depend on the server checking sentAt and sampleId. Only a party that can read the TLS stream (the service or a TLS-terminating proxy) could replay a body.
- What it could mean for you
- No client-side defect; replays can only inflate data if the server does not deduplicate.
- The reviewer’s recommendation
- Include the endpoint in the signed material and document the server's sentAt window and sampleId deduplication.
- Affects
- Mac, Windows and Linux
- shared/Sources/TokrateCore/Sharing/SharingTransport.swift:64-75Mac signs body only
- desktop/core/src/sharing.rs:246-269Rust signs body only
Our response Accepted
The server accepts a request only within five minutes of its sentAt and deduplicates samples by installation and sample ID.
Run it yourself
Get the code at the reviewed commit.
git clone https://github.com/mattivilola/tokrate-clients cd tokrate-clients git checkout cf4b743319f665c04da574f674f7f9de8c15405b
Open that folder in your coding agent in read-only mode: it may read files and run read-only commands, but not edit anything.
Paste the prompt below. It asks for one JSON object in the same format as the published files.
Compare the result with the published files. Models, and runs of the same model, word and weigh things differently, so check the code locations an answer cites rather than the verdict alone.
The prompt
The reviewers of this round received exactly this text. It begins with two git checks that stop the review if the code differs from the commit. The same prompt is in the repository: docs/security-review/PROMPT.md.
You are an independent security and privacy reviewer. Review the source code of the Tokrate desktop clients and decide whether the code keeps the privacy and security promises listed below. You are not the author and you have no reason to be generous: report what the code actually does.
## Target
- Repository: https://github.com/mattivilola/tokrate-clients
- Ref: Tokrate 0.1.20 (Mac `v0.1.20` and Windows/Linux `v0.1.20-desktop-alpha.1`, both from this commit)
- Commit: `cf4b743319f665c04da574f674f7f9de8c15405b`
Before starting, run these in the working directory:
```sh
git cat-file -e cf4b743319f665c04da574f674f7f9de8c15405b^{commit}
git diff --quiet cf4b743319f665c04da574f674f7f9de8c15405b -- . ':(exclude)*.md'
```
The first confirms the commit exists; the second confirms that every tracked non-Markdown file in the working directory, including uncommitted changes, is identical to the target commit. If either exits non-zero, stop and return only `{"error": "code does not match target", "head": "<output of git rev-parse HEAD>"}`.
Only files tracked at the target commit are in scope (`git ls-tree -r --name-only cf4b743319f665c04da574f674f7f9de8c15405b`). Ignore untracked and gitignored files, including build output and installed dependencies (`.build/`, `desktop/**/target/`, `node_modules/`, `dist/`, `.local/`); review dependencies through the lock files instead. For Markdown files, read the version at the target commit (`git show cf4b743319f665c04da574f674f7f9de8c15405b:README.md`).
## What Tokrate is
Tokrate is a menu-bar / tray app that reads the local session logs of AI coding tools (Codex, Claude Code, Grok Build, Antigravity, OpenCode), measures how fast the model answered, and, only if the user opts in, uploads the numbers (model, token counts, timings) to `tokrate.dev` for a public speed board.
Two implementations are in scope. Check every claim against both:
- **macos**: the Swift app. `shared/` (core, parsers, sharing, CLI `tokrate`) and `macos/` (SwiftUI app, packaging scripts).
- **windowsLinux**: the Tauri app. `desktop/core` (Rust core), `desktop/src-tauri` (native host), `desktop/ui` and `desktop/*` frontend and build config.
Also in scope: `Package.swift`, `Package.resolved`, `desktop/**/Cargo.toml`, `desktop/**/Cargo.lock`, `desktop/package*.json`, `.github/workflows/`, the updater public keys, and packaging/signing scripts (look for secrets or unsafe build steps).
Out of scope: the tokrate.dev backend, which is not in this repository. Server-side promises (retention, IP handling, continent derivation) cannot be checked here; do not mark them as failures.
## Rules
1. **Code is the only evidence.** README, `docs/`, comments, identifiers and test names describe intent; they are not proof. Use them to find what to check, then confirm in the code that runs. Tests may support a verdict but never replace reading the implementation.
2. **Trace data end to end.** For uploads, follow a value from the file or database it is read from, through parsing, storage and serialization, to the network call. For each claim, look for any code path that breaks it, not only the main path.
3. **Find every network call, file write and log call yourself.** Search for networking APIs (for example `URLSession`, `URLRequest`, `reqwest`, `ureq`, `hyper`, `fetch`, `XMLHttpRequest`, Tauri HTTP/updater plugins, Sparkle), file writes, process launches, and logging (`print`, `NSLog`, `os_log`, `Logger`, `println!`, `eprintln!`, `log::`, `tracing::`, `console.*`).
4. **Check dependencies.** Read `Package.resolved`, `Cargo.lock` files and `desktop/package-lock.json` for analytics, crash-reporting, advertising or telemetry packages, and check what any network-capable dependency is used for.
Third-party dependency source code (Sparkle, Tauri and its updater plugin, reqwest, SQLite, notify and so on) is not in this repository. Judge how the app configures and calls a dependency, and take a well-established dependency's documented behaviour as given (for example that Sparkle verifies the EdDSA signature against `SUPublicEDKey` before installing, or that the Tauri updater verifies the minisign signature against the configured `pubkey`). Do not mark a claim `NOT_VERIFIABLE` only because that code is not in the repository; list the dependency behaviour you relied on in `scope.limitations`. A claim is `NOT_VERIFIABLE` only when this repository's own code or configuration leaves the outcome open.
5. **Cite exact locations.** Every evidence item needs a repository-relative path and the line numbers at the target commit. Do not cite files you did not open. Never invent a path or line.
6. **No speculation as findings.** A finding needs a concrete code path. Hardening ideas without a current defect are `info`.
7. **Read-only.** Do not modify, build-install, or run the apps. Running the test suites (`swift test`, `cargo test`) is allowed but optional. Do not contact tokrate.dev or any other service, and do not use web search.
8. **Be complete before concluding.** If you could not examine something, list it in `scope.notExamined`; do not guess its verdict.
## Claims to check
Check each claim, use its exact ID, and keep the order.
- **C01 Upload allowlist.** Each community upload is built from an explicit field allowlist and contains only the fields documented in `README.md` (section "Optional community sharing") and `docs/metrics-contract.md`. It never contains prompts, responses, code, file or folder paths, session/turn/account identifiers, the local deduplication digest, raw originator/entrypoint strings, user names, host names or hardware identifiers. List every key actually sent in `uploadedFields`, and report any key the documentation does not mention.
- **C02 Content is not retained.** Parsers keep only numeric usage, timestamps, model/provider identifiers, reasoning effort and the documented category fields. Prompt and response text, tool output, code and paths are not stored beyond parsing. Sources the documentation says are never read are really never opened or queried: Grok `chat_history.jsonl` and `updates.jsonl`; the OpenCode `part` table and full `message.data`; Antigravity `step_payload`, `trajectory_metadata_blob`, `render_info`, `task_details`, `permissions`, `error_details`, `battle_mode_infos`.
- **C03 Minimal local storage.** Local history stores only normalized metric records and checkpoints (with SHA-256 path digests, never raw paths or raw session/turn identifiers). Records are kept for 7 days and at most 50,000 turns while the app runs; expired records are removed within about an hour, also while monitoring is paused (nothing runs while the app is not running). Live per-response values are memory only.
- **C04 Read-only access to coding-tool data.** The app never writes, modifies, deletes or renames other tools' session files or databases, and never takes an exclusive lock on them. SQLite databases are opened read-only (`mode=ro`, never `immutable=1`); SQLite's normal shared lock for the duration of a short read transaction is expected and does not break this claim.
- **C05 Affirmative consent.** No sample is uploaded and no community request is made until the user explicitly opts in on the current notice version. "Only for local use" and a saved OFF stay OFF across launches and upgrades. Raising the notice version requires a new opt-in.
- **C06 Consent screen is accurate.** The sample payload and field description the consent screen shows match the structure and fields that are actually uploaded.
- **C07 Sharing OFF stops network activity.** Switching sharing off cancels the upload loop, clears pending uploads and stops community statistics/alert fetches. While sharing is off, no request is made to the community endpoints.
- **C08 No backfill.** Only turns completed after the current launch or the latest switch-on are eligible for upload; historical turns are never uploaded.
- **C09 Pseudonymous identity.** The installation identity is a random Ed25519 key stored in the OS credential store (macOS Keychain, Windows Credential Manager, Linux Secret Service). Only the public key and signatures are sent; the private key never leaves the store or the process. No user name, host name, hardware/device identifier, MAC address or serial number is collected or sent.
- **C10 Known network destinations only.** All network requests go to `tokrate.dev` (sample upload, community statistics, update feeds) or GitHub (update packages), over HTTPS. Update packages are accepted only from this repository's GitHub release assets; update requests may follow ordinary HTTP redirects (GitHub serves release assets from its own download hosts), which is documented and expected, provided packages are installed only after signature verification. Sharing requests reject redirects and use no cookies or persistent cache. No other host is contacted, and local-only features (history export, `tokrate inspect`) make no network requests. List every request in `networkEndpoints`.
- **C11 No tracking SDKs.** No analytics, advertising, crash-reporting or telemetry SDK is included in any dependency set, and no separate install, launch or usage event is sent.
- **C12 Update integrity and privacy.** Updates are verified against public keys embedded in the app before installation (Sparkle EdDSA on Mac, Tauri updater minisign on Windows/Linux), and installation requires a user action. Update checks send no contribution key, measurements or coding-tool content. Sparkle system-profile reporting is disabled. The automatic-check switch is respected.
- **C13 Bounded memory-only queue.** Unsent uploads are held only in memory, capped at 1,000 samples and 24 hours, and are lost on quit. Nothing pending is written to disk.
- **C14 No sensitive logging.** No prompts, responses, paths, session identifiers, keys or signatures are written to logs, the console, the OS log or crash output.
- **C15 No remote code or content.** The app UI loads no remote web content and executes no downloaded code other than signed updates. On Windows/Linux the Tauri content security policy and capabilities limit the frontend to what it needs, and the IPC commands it exposes cannot read arbitrary files or reach arbitrary network destinations (fixed-purpose commands such as an update check or switching sharing on, which reach only the endpoints of C10, are expected).
- **C16 Upload timing.** A sample is uploaded only after its five-minute `observedAt` period has ended, after a random delay, so neither the request's `sentAt` nor its sending time places a turn more precisely than its five-minute period. Community statistics fetches do not reveal when turns completed.
## Threat model
Use this to judge who can trigger a defect, and set severity accordingly:
- **The tokrate.dev service and anyone on the network path** see every request. What can they learn about the user beyond the documented fields? Could they make the app do something harmful (responses, update feeds)?
- **Content inside the coding tools' logs and databases** (prompt, response and tool-output text) can be influenced by third parties, for example a malicious repository, web page or model output. A defect triggerable by such content is realistic.
- **File structure** (file types such as FIFOs or symlinks, numeric fields, timestamps, JSON shape, file sizes, paths) is written by the coding tools themselves. Crafting it requires write access to the user's home folder; such an attacker can already do far more than disturb Tokrate.
- **Other local users** on the same machine, through file permissions.
## General security review
Beyond the claims, report concrete defects in any area, for example:
- Handling of untrusted input: the session files and databases Tokrate reads are written by other programs and could be crafted. Look for crashes, unbounded memory or CPU use, path traversal, symlink following outside source roots, SQL injection, and unsafe deserialization.
- Request signing and replay: signature construction, timestamp handling, key generation randomness.
- Local file permissions and locations of the history file and any other written file.
- Tauri IPC surface, webview configuration, CSP gaps.
- Secrets, private keys or credentials committed to the repository; unsafe CI or packaging steps.
- Places where the documentation promises something the code does not do (severity by user impact).
## Severity
- `critical`: prompts, responses, code, paths or the private key can leave the device, or remote code execution is possible.
- `high`: a claim is contradicted in normal use; data leaves without consent; update verification can be bypassed.
- `medium`: a claim fails only in an edge case or on one platform; a defect exploitable by the service, a network observer, another local user, or content inside the coding tools' logs; documentation materially misstates what is sent or stored.
- `low`: a defense-in-depth gap with limited impact; a crash, hang or resource exhaustion that needs crafted file structure (write access to the user's home folder, see Threat model); a minor documentation inaccuracy.
- `info`: an observation or hardening suggestion with no current defect.
## Verdicts
Per claim and per implementation:
- `VERIFIED`: the code implements the claim on every path you traced, with evidence cited.
- `PARTIAL`: the claim holds in general but has a gap (an edge case, one path, a documentation mismatch). Must reference at least one finding.
- `CONTRADICTED`: the code does what the claim says it does not do. Must reference a finding of severity `high` or `critical`.
- `NOT_VERIFIABLE`: the source cannot settle it; explain why in `rationale`.
- `NOT_APPLICABLE`: the claim does not apply to this implementation (explain why).
The claim's overall `verdict` is the worse of its two implementation verdicts, in this order from worst: `CONTRADICTED`, `PARTIAL`, `NOT_VERIFIABLE`, `VERIFIED`. `NOT_APPLICABLE` is ignored unless both are `NOT_APPLICABLE`.
`summary.overallVerdict`:
- `FAIL` if any claim is `CONTRADICTED` or any finding is `critical` or `high`;
- otherwise `PASS_WITH_NOTES` if any claim is `PARTIAL` or `NOT_VERIFIABLE`, or any finding is `medium`;
- otherwise `PASS`.
## Output format
Your final answer must be exactly one JSON object that matches the schema below: no Markdown fences, no text before or after it. Use only the enum values given. Keep string lengths within the stated limits. Use `[]` for empty lists and `null` for unknown optional values. Number findings `F01`, `F02`, … from most to least severe.
```jsonc
{
"schemaVersion": "tokrate-client-review/1",
"reviewer": {
"model": "string — exact model name and version you are",
"tool": "string — agent or app you ran in, e.g. Codex CLI, Claude Code, Grok",
"reviewedAt": "YYYY-MM-DD"
},
"target": {
"repository": "https://github.com/mattivilola/tokrate-clients",
"ref": "v0.1.20",
"commit": "the 40-character target commit SHA you verified"
},
"scope": {
"filesExamined": 0, // integer: files you actually opened
"testsRun": "none | swift | cargo | swift+cargo",
"notExamined": ["string ≤ 160 chars"],
"limitations": ["string ≤ 200 chars"]
},
"summary": {
"overallVerdict": "PASS | PASS_WITH_NOTES | FAIL",
"headline": "string ≤ 160 chars — one plain-language sentence for a website visitor",
"claimCounts": { "VERIFIED": 0, "PARTIAL": 0, "CONTRADICTED": 0, "NOT_VERIFIABLE": 0, "NOT_APPLICABLE": 0 },
"findingCounts": { "critical": 0, "high": 0, "medium": 0, "low": 0, "info": 0 }
},
"claims": [ // exactly 16 entries, C01 … C16 in order
{
"id": "C01",
"verdict": "VERIFIED | PARTIAL | CONTRADICTED | NOT_VERIFIABLE | NOT_APPLICABLE",
"byImplementation": {
"macos": "VERIFIED | PARTIAL | CONTRADICTED | NOT_VERIFIABLE | NOT_APPLICABLE",
"windowsLinux": "VERIFIED | PARTIAL | CONTRADICTED | NOT_VERIFIABLE | NOT_APPLICABLE"
},
"confidence": "high | medium | low",
"rationale": "string ≤ 600 chars — what you traced and why the verdict follows",
"evidence": [ { "file": "repo/relative/path", "lines": "120-148", "note": "string ≤ 160 chars" } ],
"findings": ["F01"]
}
],
"uploadedFields": [ // every JSON key in an upload request body, both implementations
{
"field": "string — JSON key as sent",
"type": "string | integer | number | boolean | null-able variants, e.g. integer|null",
"valueSource": "string ≤ 160 chars — where the value comes from",
"implementations": ["macos", "windowsLinux"],
"documented": true,
"containsUserContent": false
}
],
"networkEndpoints": [ // every outbound request either app can make
{
"url": "string — scheme, host and path (use {placeholder} for variable parts)",
"method": "GET | POST | PUT | other",
"purpose": "string ≤ 120 chars",
"when": "string ≤ 160 chars — what triggers it and whether sharing/update switches gate it",
"sends": "string ≤ 200 chars — headers and body contents that could identify the user or install",
"implementations": ["macos", "windowsLinux"],
"evidence": [ { "file": "repo/relative/path", "lines": "10-42", "note": "string ≤ 160 chars" } ]
}
],
"findings": [
{
"id": "F01",
"severity": "critical | high | medium | low | info",
"category": "privacy-leak | consent | network | local-storage | input-handling | resource-exhaustion | crypto | update-integrity | supply-chain | ipc-webview | logging | secrets | documentation | other",
"title": "string ≤ 100 chars",
"description": "string ≤ 800 chars — the defect and the concrete code path",
"impact": "string ≤ 300 chars — what can actually happen to a user",
"recommendation": "string ≤ 400 chars",
"implementations": ["macos", "windowsLinux"],
"claims": ["C05"],
"evidence": [ { "file": "repo/relative/path", "lines": "55-61", "note": "string ≤ 160 chars" } ]
}
]
}
```
The reviewers’ raw results
The two JSON files exactly as the reviewers returned them.
What this AI audit does not cover
- An AI audit is not a professional audit. AI reviewers can miss problems and can be wrong. This is not a certification and not a professional security audit. It is two independent readings of the same code, published so you can judge the results yourself.
- The server is not in the code they read. The tokrate.dev backend is not part of the open-source repository, so nobody checked what the server does with an upload. See the privacy policy for what it keeps and for how long.
- They read source code, not the apps you download. The link between the two is the release itself: every release publishes SHA-256 checksums, the Mac app is signed with an Apple Developer ID and notarized by Apple, and in-app updates are signature-verified. The download page has the details.
- Dependencies were taken as documented. Sparkle, Tauri, SQLite and the other libraries the apps use were not reviewed line by line, only how the apps use them and what their lock files list.
- Each reviewer lists what it did not read. The scope and limits sections in the raw files name the files and areas a reviewer only sampled.
Privacy and sharing choicesDownload TokrateRead the measurement definitions