From 97183cf867ee450abc7a026f282043ff53d26c0b Mon Sep 17 00:00:00 2001 From: joshrmcdaniel <80354972+joshrmcdaniel@users.noreply.github.com> Date: Sun, 4 Oct 2026 13:19:58 -0500 Subject: [PATCH 01/10] stats --- .gitignore | 5 +- CHANGELOG.md | 17 + Cargo.lock | 11 + README.md | 81 +++ privaxy/Cargo.toml | 1 + privaxy/src/server/configuration/mod.rs | 5 + .../src/server/configuration/statistics.rs | 62 ++ privaxy/src/server/lib.rs | 32 +- privaxy/src/server/proxy/html_rewriter.rs | 54 ++ privaxy/src/server/proxy/serve.rs | 29 +- privaxy/src/server/statistics.rs | 141 +++- privaxy/src/server/statistics/history.rs | 682 ++++++++++++++++++ .../src/server/statistics/history/tests.rs | 450 ++++++++++++ privaxy/src/server/web_gui/history.rs | 122 ++++ privaxy/src/server/web_gui/mod.rs | 17 + web_frontend/Cargo.toml | 1 + web_frontend/src/dashboard.rs | 3 +- web_frontend/src/history.rs | 281 ++++++++ web_frontend/src/main.rs | 9 + web_frontend/src/settings.rs | 8 + web_frontend/src/statistics_settings.rs | 319 ++++++++ web_frontend/tests/ui-regressions.cjs | 89 ++- 22 files changed, 2366 insertions(+), 53 deletions(-) create mode 100644 privaxy/src/server/configuration/statistics.rs create mode 100644 privaxy/src/server/statistics/history.rs create mode 100644 privaxy/src/server/statistics/history/tests.rs create mode 100644 privaxy/src/server/web_gui/history.rs create mode 100644 web_frontend/src/history.rs create mode 100644 web_frontend/src/statistics_settings.rs diff --git a/.gitignore b/.gitignore index 8ca8437..1da5405 100644 --- a/.gitignore +++ b/.gitignore @@ -7,4 +7,7 @@ filters/__pycache__ node_modules .vscode **/target/* -testing/ \ No newline at end of file +testing/ + +# Local opt-in request history can contain browsing metadata. +statistics.redb diff --git a/CHANGELOG.md b/CHANGELOG.md index c09ce61..5740921 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ ## Unreleased +- Added opt-in persistent statistics and individual request history using redb. + History includes time ranges, trends, top domains/clients, and pagination, with + privacy levels that remove identifiers before collection. URL paths, query + strings, headers, and bodies are never saved in statistics. +- Added Statistics settings for retention, a request-count limit, clearing saved + history, and excluding domain patterns from both statistics and the live + Requests log. Exclusions do not change filtering behavior and work even when + persistence is disabled. +- Bounded statistics buffering, batched transactional writes, visible storage + errors, and shutdown flushing keep storage failures off the proxy request path. + Existing installations retain memory-only statistics until explicitly enabled. +- Dashboard top blocked entries now use domains and select the top 50 after + sorting; client tracking is bounded. Dashboard counters remain per-process; + the new History page shows statistics retained across restarts. +- Count each modified HTML response once when both head scripts and cosmetic + styles are injected, instead of counting the two injection stages separately. + ## v0.8.0 - 2026-10-02 Release highlights and upgrade notes are in [RELEASE.md](RELEASE.md). diff --git a/Cargo.lock b/Cargo.lock index 0ffb544..c06596d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2192,6 +2192,7 @@ dependencies = [ "once_cell", "openssl", "portable-atomic", + "redb", "regex", "reqwest", "rustls", @@ -2393,6 +2394,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "redb" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb338a6c67830a61bed824b78c2bb034ab1ff401a20616bd7957524f4fdc2f22" +dependencies = [ + "libc", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -3657,6 +3667,7 @@ dependencies = [ "gloo-net 0.6.0", "gloo-timers 0.4.0", "gloo-utils 0.3.0", + "js-sys", "log", "num-format", "readonly", diff --git a/README.md b/README.md index 8b1315b..9f8c277 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ features, dependency updates, an improved UI, and server-friendly configuration - [Cert-pinned hosts (exclusions)](#4-cert-pinned-hosts-exclusions) - [Filter only selected hosts (inclusions)](#5-filter-only-selected-hosts-inclusions) - [HTTP CONNECT and proxy chaining](#6-http-connect-and-proxy-chaining) + - [Persistent statistics and privacy](#7-persistent-statistics-and-privacy) - [Screenshots](#screenshots) - [Acknowledgements](#acknowledgements) @@ -73,6 +74,7 @@ blocker more than it strictly replaces one. - Suppport for [Adblock Plus filters](https://adblockplus.org/filter-cheatsheet), such as [easylist](https://easylist.to/). - Web graphical user interface with a statistics display as well as a live request explorer. +- Opt-in persistent statistics and request history, with privacy levels, retention limits, and domain exclusions. - Support for uBlock origin's `js` syntax. - Support for uBlock origin's `redirect` syntax. - Support for uBlock origin's scriptlets. @@ -319,6 +321,85 @@ for those steps. > `password_hash` value from the config file and restart. The web UI will > force the setup flow again. +### 7. Persistent statistics and privacy + +Enable collection under **Settings → Statistics**, then open **History** for +saved totals, hourly/daily trends, top domains/clients, and a paginated request +log. The Dashboard continues to show the current process's live counters. +Persistence is **off by default**; enabling it starts collecting new activity, +without importing the live Requests feed. + +History records timestamps, destination hostnames, client IP addresses, and +whether a request was blocked or successfully forwarded. It never stores URL +paths, query strings, headers, cookies, or request/response bodies. Raw CONNECT +tunnels and requests that fail before being forwarded are not counted. Clients +behind another proxy share that proxy's client address. + +The privacy levels follow [Pi-hole's approach](https://docs.pi-hole.net/ftldns/privacylevels/): + +| Level | Saved identifiers | +| --- | --- | +| Show domains and clients | Hostname and client IP | +| Hide domains | Client IP only | +| Hide domains and clients (default) | Neither; request times and outcomes remain | +| Anonymous | No new history; session counters only | + +Hidden identifiers are removed **before queueing or storage**, without retaining +a reversible mapping or a hash. While collection is enabled, raising privacy +also redacts identifiers in existing history, including when entering anonymous +mode. Lowering privacy cannot restore them. These controls apply to statistics; +the separate live Requests feed and diagnostic logs can still contain full URLs. + +**Exclude domains from statistics and request logs** skips matching new traffic +in session counters, modified-response counts, saved history, and the live +Requests feed, even with persistence disabled. Filtering and interception still +apply. Use one case-insensitive hostname or wildcard pattern per line: +`example.com` matches exactly; `*.example.com` matches its subdomains. Do not +include schemes, ports, or paths. Existing history and diagnostic logs are +unaffected. + +Defaults are **7 days** and **100,000 saved requests**. The oldest records expire +when either limit is reached; valid limits are 1–365 days and 100–1,000,000 +requests. History totals and top lists describe retained requests, so they can +decrease as older entries expire. Modified-response totals are separate hourly +aggregates retained by age. Time windows include the current hour and preceding +hours. **Clear saved history** removes the database and starts fresh if enabled. +Turning collection off stops recording and closes the database; it retains the +existing file until cleared or collection is re-enabled and retention runs. + +The equivalent configuration section is: + +```toml +[statistics] +enabled = false +privacy = "hide_domains_and_clients" # full, hide_domains, hide_domains_and_clients, anonymous +retention_days = 7 +max_entries = 100000 +excluded_domains = ["example.com", "*.example.com"] +``` + +UI changes apply immediately; file edits also apply on SIGHUP. Data stays local +in `statistics.redb` under `PRIVAXY_BASE_PATH` (`/etc/privaxy` by default, `/conf` +in Docker). Keep `/conf` on a persistent Docker volume for history to survive +container replacement. No new ports or external database service are required. +On Unix, the database is restricted to its owner (`0600`). Storage is not +encrypted, and redaction/deletion is not secure erasure of filesystem snapshots, +backups, or recoverable disk pages. + +[redb](https://github.com/cberner/redb) transactions run on a dedicated worker, +with a bounded queue and batches flushed every five seconds or 512 events. +SIGTERM/SIGINT also flush accepted events; an abrupt kill can lose the pending +batch. Expired rows are pruned every minute and free space is compacted hourly; +the row limit bounds retained data, not an exact file size. Disk/write errors +pause recording and appear in Statistics settings without stopping the proxy. +Free space and save settings again to retry. Queue overflow drops statistics +events and reports their count instead of delaying proxy requests. + +The authenticated APIs are `GET/PUT /api/settings/statistics` and +`GET/DELETE /api/history`. History accepts `hours`, `limit` (1–200), `before` +(the previous response's `next_before` cursor), and `blocked=true|false`. +Pagination and the blocked filter affect request rows, not the window's totals. + ## Screenshots