Skip to content

Security and Privacy

Mikhail Deynekin edited this page Oct 16, 2026 · 1 revision

Security and Privacy

TrayIconGuard's security story is unusually simple for a shell-adjacent tool: it runs as a standard user, writes only to HKCU and the user's own AppData folders, sends nothing anyone did not ask for, and touches the network only to fetch an update feed and — after explicit confirmation — one pinned binary. This page states the threat model, the mechanisms that enforce it (with source references), the residual risks including the defects found during this wiki's audit, and the privacy policy in full.


Contents


Trust boundaries at a glance

flowchart TD
    U["user (standard)"] -->|explicit actions| G["TrayIconGuard.exe"]
    G -->|HKCU writes only| R["NotifyIconSettings / Run / Uninstall keys"]
    G -->|profile writes| F["AppData settings, logs, snapshots"]
    G -->|"HTTPS GET (opt-in)"| N["feed host / release CDN"]
    N -->|feed JSON + pinned binary| G
    X["other users / admins"] -.->|"no shared state"| G
Loading

Privilege and registry scope

  • Manifest requests asInvoker; no code path triggers a UAC prompt, and no feature requires administrator rights (resources/app.manifest, verified against the installer/uninstaller paths).
  • Write scope: Control Panel\NotifyIconSettings\{id}\IsPromoted; optional …\Explorer\EnableAutoTray; deletion-only touches of Policies\Explorer HideSCA values and TrayNotify caches; opt-in …\Run; install-time …\Uninstall\TrayIconGuard. All HKCU. Two HKLM reads (OS edition/display version) are the only machine-level access (src/core/os.cpp).
  • Consequence, stated plainly in the wiki and the docs: there is no machine-wide or Group-Policy mode, because per-icon promotion has no GPO. Deployment at scale means per-user configuration (logon scripts, GPP registry items targeting HKCU).

Update chain: integrity, not authentication

Mechanism (details and failure matrix in Self-Updater Guide): HTTPS-only feed and download URLs (loopback HTTP permitted for QA), mandatory 64-hex SHA-256 pin verified with BCrypt before apply, rename-chain self-replace with rollback, confirmation dialog in the GUI, opt-in balloon-only auto-check.

Residual risk, stated without euphemism: the pin and the feed arrive from the same TLS origin, so the chain guarantees the binary equals what the feed host serves — it cannot authenticate the publisher cryptographically (no signature, no signed manifest). A compromised feed origin can serve a self-consistent malicious pair. Mitigations available to operators: code-sign the executable yourself (documented signtool command; re-hash the feed afterwards), pin the feed URL to an internal mirror via updates.feedUrl, or deploy by your own package manager and leave the updater disabled (it is off by default).

The feedUrl override itself is a user-writable setting: any process running as the user can redirect update checks to an arbitrary HTTPS URL. This is standard for per-user configuration files and is why the confirmation step and the hash pin matter.

Input handling and file safety

  • Feed JSON parsed by the dependency-free parser: strict about structure (trailing garbage rejected, escapes validated including surrogate pairs), numbers via wcstod, no evaluation of any kind; file reads capped at 64 MB (src/util/fs.cpp).
  • Snapshot files: restore iterates the icons array and writes registry values; the delete path validates the file name against \, / and .. before composing a path — a deliberate traversal guard (src/core/backup.cpp).
  • Atomic writes: settings and snapshots go through tmp-file + MoveFileExW replace, so interrupted writes cannot corrupt prior state.
  • Exclusions/wildcards: an iterative matcher with * and ? only; no recursion, no regex engine, no code execution from patterns.
  • Explorer restart terminates only processes named explorer.exe enumerated from a toolhelp snapshot; it is always user- or policy-triggered (confirmation dialog by default) and cooldown-bounded when automatic.
  • IPC: WM_COPYDATA verbs are matched by exact string with a fixed constant; unknown verbs are ignored.

Concurrency defects found in audit

Found by source audit during this wiki's research phase; all are in the fixes bundle shipped with this wiki (fixes/), each with rationale:

  1. Guard wakeup defect (functional, high) — the worker waits on its stop event only; the armed registry-change event is never in the wait set, so default configurations (pollMs: 0) never reconcile after startup. Patch: wait on both events (WaitForMultipleObjects), preserving the poll timer as a bound.
  2. Handle lifetime edge (robustness, low) — Stop() closes the stop/registry events after a 4-second join budget even if the worker is still inside a pass; patch closes handles only after a successful join.
  3. Store snapshot race (robustness, low) — store::Current() is read by the UI thread while the guard thread replaces the vector in Rescan(); patch guards the cache with the existing SRWLOCK.
  4. Log retention skew (cosmetic, low) — the retention cutoff mixes a local-time-based FILETIME with UTC file timestamps, shifting the 14-day window by the UTC offset; patch uses system time for “now”.

None of these is remotely exploitable; they are correctness and robustness issues, listed here because a security page that hides known defects is worse than none.

Unsigned binary: SmartScreen and AV reality

The project publishes no signature. Downloaded copies carry Mark-of-the-Web and trigger SmartScreen once; the embedded installer's copy step drops the ADS, which is why the wizard install is the recommended first run. Statically linked mingw binaries occasionally draw generic antivirus heuristics; the repository's answer is transparency — small auditable source, documented write scope, reproducible build script, published SHA-256 sums — plus an exact signtool recipe for organizations that need a signature.

Privacy policy

The repository's docs/PRIVACY.md, summarized verbatim in intent: the app collects nothing and sends nothing. No telemetry, analytics, crash reporting or advertising identifiers. All data stays in %APPDATA%\TrayIconGuard and %LOCALAPPDATA%\TrayIconGuard. Network access occurs only on About → Check for updates or the opt-in automatic check: one HTTPS GET of the feed and, after confirmation, one HTTPS GET of the pinned binary. Uninstall removes program files; profile data remains until the user deletes it (the script uninstaller deletes it unless TRAYICONGUARD_KEEP_DATA is set).

Reporting issues

GitHub issues on the repository, or the author's address published in the About page and README (icons@Deynekin.com). For reports, use About → Copy diagnostics — OS caption, theme, resolved language, settings and log paths, last 25 log lines — and attach the relevant daily log file.


See also

Clone this wiki locally