Repository navigation
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.
- Trust boundaries at a glance
- Privilege and registry scope
- Update chain: integrity, not authentication
- Input handling and file safety
- Concurrency defects found in audit
- Unsigned binary: SmartScreen and AV reality
- Privacy policy
- Reporting issues
- See also
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
- 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 ofPolicies\ExplorerHideSCA values andTrayNotifycaches; opt-in…\Run; install-time…\Uninstall\TrayIconGuard. AllHKCU. TwoHKLMreads (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).
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.
-
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
iconsarray 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 +
MoveFileExWreplace, 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.exeenumerated from a toolhelp snapshot; it is always user- or policy-triggered (confirmation dialog by default) and cooldown-bounded when automatic. -
IPC:
WM_COPYDATAverbs are matched by exact string with a fixed constant; unknown verbs are ignored.
Found by source audit during this wiki's research phase; all are in the fixes bundle
shipped with this wiki (fixes/), each with rationale:
-
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. -
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. -
Store snapshot race (robustness, low) —
store::Current()is read by the UI thread while the guard thread replaces the vector inRescan(); patch guards the cache with the existing SRWLOCK. - 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.
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.
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).
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.
- Self-Updater Guide — feed contract and failure matrix
- Configuration Reference — every key an attacker-with-your-user-token could tweak
- The Guard Watcher — the wakeup defect in full
- Comparison with Alternatives — why no shell hooking is itself a security property
TrayIconGuard · feed 8.6.3 / source tree 8.6.3 (see Changelog and Versioning) · PolyForm Noncommercial 1.0.0 · Repository · Report an issue · Home · Glossary · FAQ · Troubleshooting
Every claim in this wiki is traced to a source line at commit 124e849 or reproduced by
executing the program (build + Wine battery, 2026-10-16); measured discrepancies between
documentation, source and shipped binaries are documented, not smoothed over.
Home Getting Started Features Overview Comparison with Alternatives
Installation and Deployment Configuration Reference Command-Line Interface
How Tray Visibility Works Legacy Script Editions The Guard Watcher
GUI Pages Guide Backups and Snapshots Localization and Themes Self-Updater Guide Security and Privacy Troubleshooting
Architecture Roadmap and Ideas Independent-Audits Testing and QA Building from Source Changelog and Versioning FAQ Glossary
8.6.3 feed / 8.6.3 source · PolyForm Noncommercial 1.0.0