| Collected | Not collected |
|---|---|
| Anonymous random client id (SHA-256 hashed, one-way) | Real machine id, hardware serial |
Event name (run, command:build) |
Raw command arguments |
| Package name + version | File paths, project names, git remotes |
Node.js version (v22.0.0) |
Environment variables, secrets, API keys |
Platform (darwin, linux, win32) |
Username, hostname |
| CI flag (true / false) | User-generated content, logs, stack traces |
| Timestamp (server-side) | Client's local timezone / clock |
| Stored | Stripped before storage |
|---|---|
| Package name + version ✅ | IP address ❌ |
| Event name ✅ | Oversized fields (truncated at 128 chars) ✅ |
| Hashed client id ✅ | Arbitrary payload fields ❌ |
- Access IPs in the payload
- Access logs (by default — your provider may still log)
- Secrets or credentials
pinglet uses the industry-standard opt-out model (like Next.js, VS Code, Homebrew).
- Tracking is ON by default at level 1 (basic: all event names, no properties)
- No prompts during
npm install— no postinstall script - One-time notice on first
track()call (TTY only) - Opt-out overrides:
PINGLET_OPT_OUT=1,DO_NOT_TRACK=1, or--no-telemetry
Choose from levels:
| Level | What | Example events |
|---|---|---|
| 0 | Off — no telemetry | — |
| 1 | Basic (default) | all event names, no properties |
| 2 | Standard | event names + properties stripped |
| 3 | Extended | + non-PII metadata like mode |
Full documentation: docs/telemetry.md.
The client id is:
- Random 32 bytes, never derived from hardware or user data
- Hashed with SHA-256
- Only 32 hex chars stored
- Not reversible
If the state file is deleted, a new random id is generated. There is no way to link old and new ids.
| Endpoint | Auth |
|---|---|
POST /ping |
Public (optionally protected by PINGLET_INGEST_TOKEN) |
GET /stats?pkg= |
Admin: Basic Auth or Bearer token |
GET /packages |
Admin: Basic Auth or Bearer token |
POST /auth/login |
Admin: Basic Auth → returns 30-day token |
GET /health |
Public |
- 200 pings per minute per IP address
- Returns HTTP 429 with
{"error":"Rate limit exceeded"}
- NDJSON files in
PINGLET_DATA_DIR - No database needed
- File permissions:
0o600for config files,0o700for directories - Admin sessions stored as SHA-256 hashed tokens in
.pinglet-admin-sessions.json
- Use HTTPS in production. Railway provides it automatically.
- The server does not enforce HTTPS — configure it at the proxy/reverse-proxy level.
- Set a strong
PINGLET_ADMIN_PASSWORD(generate:openssl rand -base64 32) - Mount a persistent volume at
/data - Set
PINGLET_DATA_DIR=/data - Railway TLS is automatic
docker build -t pinglet .
docker run -p 3456:3456 \
-v pinglet-data:/data \
-e PINGLET_DATA_DIR=/data \
-e PINGLET_ADMIN_USER=admin \
-e PINGLET_ADMIN_PASSWORD=<password> \
pingletPORT=3456 \
PINGLET_DATA_DIR=/var/lib/pinglet \
PINGLET_ADMIN_USER=admin \
PINGLET_ADMIN_PASSWORD=<password> \
npx -p @black-knight.dev/pinglet pinglet-serverBehind nginx or Caddy for TLS termination.
- No personal data collected by the pinglet server (no IPs, no usernames, no email)
- Anonymous client ids are random, non-reversible, not linkable to individuals
- Users can opt out at any time
- Data is stored on servers the package author controls
- No third-party analytics services involved
A disclosure to add to your package README is in maintainer-guide.md.
Security issues: open a GitHub issue or email the maintainer.