Skip to content

Add opt-in anonymous usage analytics - #6

Merged
Xenne93 merged 1 commit into
mainfrom
feature/anonymous-analytics
Aug 14, 2026
Merged

Add opt-in anonymous usage analytics#6
Xenne93 merged 1 commit into
mainfrom
feature/anonymous-analytics

Conversation

@Xenne93

@Xenne93 Xenne93 commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Adds an opt-in analytics toggle (default off) so installations can share anonymous usage statistics (install check-ins, server/player/user counts - no identifiable data) via a self-hosted Plausible instance.

  • Asked once during initial setup, unchecked by default.
  • Adjustable any time from Panel Settings.
  • Public /api/panelsettings/analytics-status endpoint lets the frontend decide whether to load the script on every page load, so toggling takes effect immediately.
  • Custom event with aggregate counts sent once per app load, only when enabled.

Verified end-to-end in a throwaway Docker stack (status defaults false, flips on/off via setup and the toggle endpoint, snapshot counts correct, served index.html includes the conditional script block).

Adds an opt-in analytics toggle (PanelSettings.AnalyticsEnabled, default off) so
installations can optionally share anonymous usage statistics - install
check-ins, server/player/user counts - via a self-hosted Plausible instance.
No SteamIDs, emails, or other identifiable data is ever sent.

- Asked once during initial setup (Setup.razor), unchecked by default.
- Adjustable at any time from Panel Settings > Anonymous Usage Statistics.
- GET /api/panelsettings/analytics-status is a public, unauthenticated endpoint
  the frontend checks on every page load (including pre-login) to decide
  whether to load the Plausible script - toggling the setting takes effect
  immediately, no rebuild/restart needed.
- GET /api/panelsettings/analytics-snapshot (authenticated) returns aggregate
  server/player/user counts, sent as a single custom Plausible event once per
  app load from MainLayout.razor, only when analytics is enabled.

Verified end-to-end in a throwaway Docker stack: analytics-status defaults to
false pre-setup, flips to true after opting in during setup, the toggle
endpoint flips it back, and analytics-snapshot returns correct counts for a
fresh install. Confirmed the served index.html includes the conditional
script-loading block.
@Xenne93
Xenne93 merged commit 9e0c6fc into main Aug 14, 2026
1 check passed
@Xenne93
Xenne93 deleted the feature/anonymous-analytics branch August 14, 2026 14:08
Xenne93 added a commit that referenced this pull request Aug 16, 2026
* Fix GitHub Code Scanning alerts (Security and quality)

- Add explicit permissions blocks to build-check.yml and
  remove-old-packages.yml workflows (actions/missing-workflow-permissions)
- Add missing admin check to PanelSettingsController.PurgeData, which
  previously let any authenticated user (including moderators) purge all
  logged data including audit logs
- Add path-containment validation to MapStorageService (GetServerImageFilePath)
  using Path.GetFullPath + base-directory prefix check, closing an
  unauthenticated arbitrary-file-read via PublicImagesController's
  fast disk-read path using an unvalidated instanceHash route parameter
  (cs/path-injection)
- Harden Discord webhook URL validation in ServerWebhookController.TestWebhook
  to check uri.Host/AbsolutePath explicitly instead of a raw substring
  .Contains() match
- Clean up RconController.DeleteServer to return NotFound on a missing/
  non-owned server instead of relying on a NullReferenceException falling
  through to a broad catch block
- Add a shared LogSanitizer helper and use it to strip CR/LF from every
  user-controlled string value before it reaches a logger call across 18
  files (cs/log-forging), converting any remaining string-interpolated log
  calls to structured logging with named placeholders along the way
- Replace User.GetEmail() with a non-PII user identifier (resolved user
  object's Id, or the NameIdentifier claim) in every log call that
  previously logged the user's email address (cs/exposure-of-sensitive-information)
- Mask the recipient address in EmailService's log calls instead of logging
  it in full
- Dismiss 4 alerts confirmed as false positives via the Code Scanning API,
  each with a documented reason (cs/cleartext-storage-of-sensitive-information
  #7, cs/user-controlled-bypass #4, #5, #6)

* test: inline Replace() sanitizer instead of helper method to validate CodeQL barrier recognition

* Replace LogSanitizer helper with inline .Replace() calls

CodeQL's cs/log-forging barrier recognition requires the sanitizing
.Replace() call to appear directly in the tainted expression, not
routed through a custom static helper method - validated empirically
via a live re-scan (helper-based fix cleared only 1/65 alerts, one
inlined call site cleared immediately). Converted every remaining
call site accordingly and removed the now-dead LogSanitizer helper.

* Fix remaining 4 alerts: sanitize steamId logging, drop email from EmailService diagnostics

- PanelSettingsController: sanitize steamId in VAC-ban override log calls (cs/log-forging)
- EmailService: stop logging even a masked form of the recipient address, since
  CodeQL treats any value derived from the tainted parameter as still-sensitive
  regardless of transformation (cs/exposure-of-sensitive-information); removed
  the now-unused MaskEmail helper
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant