Self-hosting
The safest place for personal data is your own network. The complete service (web tool, API, LLM gateway and the browser extension files) runs on your server, so no text ever reaches Kwizmo.
The self-hosted package is available to business customers. Ask for it or see the plans.
Requirements
- PHP 7.4, 8.0, 8.1, 8.2, 8.3 or 8.4 with the standard extensions
json,pcre(with Unicode support) andhash.mbstringandintlare used when present but not required. - For the admin page:
pdo_sqlite(Debian and Ubuntu:php-sqlite3). Without it the API, the gateway and the command line still work. - Apache 2.4 (the included
.htaccessdoes the rest) or nginx with PHP-FPM. - A writable data directory, ideally outside the web root.
- For the LLM gateway:
curl, orallow_url_fopenwithopenssl. - About 3 MB of disk space and 32 MB of memory per request.
Docker
The package contains a Dockerfile, a Compose file and docker/README.md with keys, HTTPS, updates and troubleshooting. All settings can be given as environment variables named PII_ plus the setting name in capitals, for example PII_APP_SECRET or PII_GATEWAY_UPSTREAMS (as JSON).
cd pii-anonymizer
cp docker/.env.example docker/.env # then edit docker/.env
docker compose -f docker/docker-compose.yml up -d --build
curl http://localhost:8080/api.php?action=healthAny PHP web server
# 1. upload the folder to the web server, e.g. /var/www/pii
# 2. create a data directory outside the web root
sudo mkdir -p /var/lib/pii && sudo chown www-data: /var/lib/pii && sudo chmod 700 /var/lib/pii
# 3. generate a secret for app/conf.php ('app_secret')
php -r "echo bin2hex(random_bytes(32)), PHP_EOL;"
# 4. check the installation
curl https://pii.example.internal/api.php?action=healthThen edit app/conf.php. The service refuses to start until the secret and the privacy contact are set.
'app_secret' => 'paste the generated secret here',
'data_dir' => '/var/lib/pii',
'keys_file' => '/var/lib/pii/keys.json',
'operator_name' => 'Example d.o.o.',
'operator_email' => 'privacy@example.si',
'api_key_required' => true, // only your applications may use the API
'force_https' => true,
'rate_limit_per_minute' => 60,nginx
Apache reads the rules from .htaccess. For nginx, use the equivalent:
server {
listen 443 ssl;
server_name pii.example.internal;
root /var/www/pii;
index index.php;
client_max_body_size 8m;
# never serve code, configuration, data or tooling
location ~ ^/(app|data|bin|bench|training|tests|extension|docker|docs)(/|$) { return 404; }
location ~ \.(md|log|txt|json|lock|dist|sqlite|sqlite3|db)$ { return 404; }
# LLM gateway: /v1/... → gateway.php/v1/...
# stylesheets and scripts are linked with ?v=<mtime>, so they can be cached hard
location ~ \.(css|js)$ { expires 1y; add_header Cache-Control "public, immutable"; }
location /v1/ { rewrite ^/v1/(.*)$ /gateway.php/v1/$1 last; }
location ~ ^(.+\.php)(/.*)?$ {
fastcgi_split_path_info ^(.+\.php)(/.*)$;
try_files $fastcgi_script_name =404;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param HTTP_AUTHORIZATION $http_authorization;
fastcgi_buffering off; # streamed gateway answers
fastcgi_read_timeout 180s;
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
}
}Admin page
Open admin.php and sign in with admin / admin. The first thing it asks for is a new password; nothing else can be done until that is set. From there you create API keys, change their limits, disable or delete them, watch usage per customer and month, and download a CSV for invoicing.
Administrator accounts, sessions and an audit log are kept in a small SQLite database beside your other data, so the page needs the PHP extension pdo_sqlite (Debian and Ubuntu: php-sqlite3). Keys and usage counters remain plain files, so the API and the gateway keep working even without it.
The page is never linked from the public pages. On a server reachable from the internet, add your own addresses to admin_ip_allowlist, or set 'admin_enabled' => false and manage keys only on the command line.
A forgotten password is reset on the server, which is also how you add a second administrator:
echo 'a-new-long-password' | sudo -u www-data php bin/admin.php password admin
sudo -u www-data php bin/admin.php add darko --password='another-long-password'
sudo -u www-data php bin/admin.php listInternal use: keys and gateway
With 'api_key_required' => true only applications with a key can use the API. Create keys on the server:
# run as the web server user (Docker: docker compose -f docker/docker-compose.yml exec -u www-data pii php bin/keys.php …)
sudo -u www-data php bin/keys.php create "CRM integration" --scopes=api,gateway --per-minute=300 --quota=0
sudo -u www-data php bin/keys.php list
sudo -u www-data php bin/keys.php disable 3f9a1c0b7d2eThe LLM gateway can send protected prompts to a cloud provider, or to a model in your own network so that nothing leaves it at all:
'gateway_enabled' => true,
'gateway_upstreams' => [
// a model on your own server: nothing leaves the network at all
['name' => 'ollama', 'base_url' => 'http://10.0.0.20:11434/v1', 'no_key' => true, 'models' => ['*']],
],Browser extension for employees
Build the extension with php bin/build-extension.php. The files appear in downloads/ and on the extension page of your installation. Distribute it with your browser management and preset names and settings through policies. The extension itself never connects to your server or anywhere else.
Check the privacy claims yourself
You have the full source code. These commands show what is stored and where connections are made:
# what the application wrote (only rate-limit counters, logs "time IP [key=id]", usage counters, key hashes)
find /var/lib/pii -type f | head
cat /var/lib/pii/logs/access-*.log | head -3
# the code that writes files: every call is in app/bootstrap.php and app/business.php
grep -rn "file_put_contents\|fwrite" --include=*.php .
# the only outgoing connections are made by the gateway (app/gateway.php, function gw_http)
grep -rn "curl_init\|fopen(\$url" --include=*.php .
# accuracy on the included test texts
php bin/bench.phpUpdates
Replace all files except app/conf.php and your data directory. Settings added in a new version have safe defaults. After an update, php bin/bench.php shows the accuracy of the new version on the included test texts, and api.php?action=health confirms that everything works.
