xref: /plugin/hideip/README.md (revision e6a02230d1edb6feccdf1c861381fc2faf08118b)
1*e6a02230Stracker-user# Hide IP plugin for DokuWiki
2*e6a02230Stracker-user
3*e6a02230Stracker-userRemoves IP addresses from everywhere a DokuWiki admin might see them. Two parts that work together:
4*e6a02230Stracker-user
5*e6a02230Stracker-user- **Real-time anonymisation** — an action plugin that rewrites `$_SERVER['REMOTE_ADDR']` to `0.0.0.0` and clears every common forwarding header before any DokuWiki code reads them. From the moment the plugin is enabled, every new changelog entry, page metadata write, mailer header, and `$INFO['client']` value carries `0.0.0.0` instead of the real address.
6*e6a02230Stracker-user- **Historical scrub** — an admin page that walks the IP-bearing files DokuWiki already has on disk (changelogs and page metadata) and rewrites them. Authorship (user field) and modification timestamps are preserved; only the IP field changes.
7*e6a02230Stracker-user
8*e6a02230Stracker-userTested on DokuWiki `2025-05-14b "Librarian"`.
9*e6a02230Stracker-user
10*e6a02230Stracker-user## Why not the existing plugins?
11*e6a02230Stracker-user
12*e6a02230Stracker-userThere are three related plugins on dokuwiki.org. None of them quite fit this use case.
13*e6a02230Stracker-user
14*e6a02230Stracker-user**`anonip`** (Andreas Gohr, 2016, [dokuwiki.org/plugin:anonip](https://www.dokuwiki.org/plugin:anonip)) was the canonical answer. It generates a pseudo-IPv6 from `auth_browseruid()`. The problem, [as discussed on the DokuWiki forum](https://forum.dokuwiki.org/d/19953-don-t-store-ip-addresses), is that `auth_browseruid()` mixes in the first IPv4 octet of the client address, which makes the hash brute-forceable: an attacker iterating through ~256 octet values and a list of common User-Agent strings can recover the user's first IP octet and User-Agent. A community [fork by adakaleh](https://github.com/adakaleh/dokuwiki-plugin-anonip) swaps in `session_id()` instead — better, but adds session-stability assumptions and still produces a pseudo-IP for an admin to see when usernames already differentiate edits.
15*e6a02230Stracker-user
16*e6a02230Stracker-user**`gdpr`** (Michael Große, 2019, [dokuwiki.org/plugin:gdpr](https://www.dokuwiki.org/plugin:gdpr)) takes a different approach: it lets real IPs be recorded as users edit, then strips them from changelog entries older than `$conf['recent_days']` (default 90 days). This works for GDPR retention but doesn't fit a "no IPs anywhere, ever" policy, and it leaves IPs visible in the Recent Changes view for up to three months. It also touches only changelog files and skips the master changelog (`_dokuwiki.changes`) entirely, leaving IPs visible in Recent Changes after old per-page entries are cleaned.
17*e6a02230Stracker-user
18*e6a02230Stracker-user**This plugin** combines what works from both: anonip's real-time interception (simplified to a constant `0.0.0.0` since the wiki has named users) plus a one-shot historical scrub that does cover the master changelogs and page metadata.
19*e6a02230Stracker-user
20*e6a02230Stracker-user## What gets anonymised
21*e6a02230Stracker-user
22*e6a02230Stracker-user### In real time (action component)
23*e6a02230Stracker-user
24*e6a02230Stracker-user`$_SERVER['REMOTE_ADDR']` becomes `0.0.0.0`. These forwarding headers are removed: `HTTP_X_FORWARDED_FOR`, `HTTP_X_REAL_IP`, `HTTP_CLIENT_IP`, `HTTP_FORWARDED`, `HTTP_CF_CONNECTING_IP`, `HTTP_TRUE_CLIENT_IP`. The User-Agent is left alone (it's not an IP).
25*e6a02230Stracker-user
26*e6a02230Stracker-userThe hook fires on `INIT_LANG_LOAD`, after DokuWiki's `init.php` has applied any trusted-proxy `X-Forwarded-For` rewriting, but before any other code reads the IP. Every downstream consumer — `clientIP()`, `$INPUT->server->str('REMOTE_ADDR')`, changelog writers, metadata, mailer `X-Originating-IP`, AJAX page-info — sees the placeholder.
27*e6a02230Stracker-user
28*e6a02230Stracker-user### On disk (admin component, on demand)
29*e6a02230Stracker-user
30*e6a02230Stracker-user| File pattern | What it is | What changes |
31*e6a02230Stracker-user| --- | --- | --- |
32*e6a02230Stracker-user| `data/meta/**/*.changes` | Per-page change history; master `_dokuwiki.changes` | TSV field 2 (IP) rewritten to `0.0.0.0` |
33*e6a02230Stracker-user| `data/media_meta/**/*.changes` | Per-media change history; master | Same |
34*e6a02230Stracker-user| `data/meta/**/*.meta` | Page metadata (serialized) | `$meta[*]['last_change']['ip']` rewritten to `0.0.0.0` |
35*e6a02230Stracker-user
36*e6a02230Stracker-userTimestamps, page IDs, usernames, summaries, and size deltas are preserved verbatim. File mtimes are restored after each write.
37*e6a02230Stracker-user
38*e6a02230Stracker-user**Out of scope by design:**
39*e6a02230Stracker-user
40*e6a02230Stracker-user- `data/attic/` and `data/media_attic/` — historical gzip archives of page revisions. Admins generally don't view these; the project's filesystem owner has access to logs anyway. Rewriting them would be slow and require gzip handling for marginal benefit.
41*e6a02230Stracker-user- `data/cache/`, `data/tmp/`, `data/log/`, `data/locks/` — ephemeral or regenerated.
42*e6a02230Stracker-user
43*e6a02230Stracker-user## Page locking still works
44*e6a02230Stracker-user
45*e6a02230Stracker-userFor each lock, DokuWiki writes the username if one is set, otherwise IP and session-id. From `inc/common.php`:
46*e6a02230Stracker-user
47*e6a02230Stracker-user```php
48*e6a02230Stracker-userif ($INPUT->server->str('REMOTE_USER')) {
49*e6a02230Stracker-user    io_saveFile($lock, $INPUT->server->str('REMOTE_USER'));
50*e6a02230Stracker-user} else {
51*e6a02230Stracker-user    io_saveFile($lock, clientIP() . "\n" . session_id());
52*e6a02230Stracker-user}
53*e6a02230Stracker-user```
54*e6a02230Stracker-user
55*e6a02230Stracker-userIf your wiki has no anonymous edits (the use case this plugin is built for), every lock uses the username and IP isn't consulted. Even if you do allow anonymous edits, `unlock()` checks `session_id()` as a fallback, so a constant `0.0.0.0` still releases your own lock correctly.
56*e6a02230Stracker-user
57*e6a02230Stracker-user## Install
58*e6a02230Stracker-user
59*e6a02230Stracker-userIn your wiki:
60*e6a02230Stracker-user
61*e6a02230Stracker-user1. **Admin → Extension Manager → Manual Install**
62*e6a02230Stracker-user2. Upload `hideip.zip`, click **Install**
63*e6a02230Stracker-user3. Real-time anonymisation is now active. To scrub existing data: refresh the Admin page and open **Hide IP** under Additional Plugins.
64*e6a02230Stracker-user
65*e6a02230Stracker-userOr drop the directory into `lib/plugins/hideip/` directly.
66*e6a02230Stracker-user
67*e6a02230Stracker-user## Run the historical scrub
68*e6a02230Stracker-user
69*e6a02230Stracker-user1. **Admin → Hide IP**
70*e6a02230Stracker-user2. Click **Preview (count only)** to see how many files and IP slots would be rewritten.
71*e6a02230Stracker-user3. Click **Scrub now** to execute. The scrub is atomic per file (tmp file + rename) and preserves mtimes. It's also idempotent — running it again is a no-op once everything is at the placeholder.
72*e6a02230Stracker-user
73*e6a02230Stracker-userTake a backup with the Site Backup plugin first if you want a recovery point — the scrub is intentionally destructive.
74*e6a02230Stracker-user
75*e6a02230Stracker-user## Security model
76*e6a02230Stracker-user
77*e6a02230Stracker-user- **Admin-only.** `forAdminOnly() = true`, plus an explicit `auth_isadmin()` check inside the scrub method.
78*e6a02230Stracker-user- **CSRF-protected.** All actions go through `checkSecurityToken()`.
79*e6a02230Stracker-user- **POST-only scrub.** The scrub action rejects GET / HEAD so a stray link or prefetch can't trigger it.
80*e6a02230Stracker-user- **Atomic writes.** Every file write goes through a sibling `.hideip_tmp_<8 hex>` file and is `rename()`d into place. A concurrent reader sees either the old file or the new file, never a half-written state.
81*e6a02230Stracker-user- **File mtimes preserved.** The on-disk modification time of each file is restored after the scrub, so it doesn't look like everything was just edited.
82*e6a02230Stracker-user- **Idempotent.** Re-running scrub is safe — already-anonymised entries are skipped, no double-writes.
83*e6a02230Stracker-user
84*e6a02230Stracker-user## Compatibility
85*e6a02230Stracker-user
86*e6a02230Stracker-user- DokuWiki `2025-05-14b "Librarian"` and onwards (uses the modern namespaced `dokuwiki\Extension\AdminPlugin` / `ActionPlugin` base classes).
87*e6a02230Stracker-user- PHP 7.4+.
88*e6a02230Stracker-user- No external dependencies.
89*e6a02230Stracker-user
90*e6a02230Stracker-user## License
91*e6a02230Stracker-user
92*e6a02230Stracker-userGPL-2.0-or-later, matching DokuWiki itself.
93