tsf-anonymizer exists to keep customer identity out of the files people share for troubleshooting. A defect in it is therefore a security matter twice over: the tool itself handles un-anonymized archives, and its output is trusted to contain nothing identifying. This page says what the project considers a vulnerability, how to report one, and what is deliberately out of scope.
Do not open a public issue for a security problem. Use GitHub's private vulnerability reporting:
https://github.com/tbortolossi/tsf-anonymizer/security/advisories/new
You will get an acknowledgement within 7 days and a fix or a decision within 30 days for confirmed reports (this is a volunteer-maintained project; the timeline is a commitment, not an SLA). Credit is given in the release notes unless you ask otherwise.
Never attach a real tech support file, a mapping, or an excerpt of one.
Genericize: the shape of what leaked (a hostname glued to an underscore inside a nginx log line) is what fixes the bug, never the value. The
tsf-anonymizer mock-tsf archive is the reproducer to build on — extend it
with a synthetic line that shows the pattern.
- An identifier class that survives anonymization in a case the tool claims to handle (see the table in the README and the invariants in .claude/rules/anonymizer-invariants.md): an IP, hostname, FQDN, e-mail, username, serial or named object left in clear, or replaced by a pseudonym that reveals it (a chained or truncated replacement, a fake that collides with an original without being reported as a collision).
- A verification that lies: the compare mode reporting OK on an archive where a mapped value survives, where a change is not explained by the mapping, or where structure was lost.
- A mapping that travels with the output: any path by which the
*.mapping.jsonsidecar — the key that reverses every pseudonym — ends up inside or next to the anonymized archive without the operator asking. - Web UI: authentication bypass on any route (every route is behind
HTTP Basic auth as soon as
TSF_PASSWORDis set — no exemptions, health probe and static files included), path traversal through job ids, file names or diff paths, a way to read another job's input or mapping, or a TLS fallback to plain HTTP that the operator did not opt into. - Archive handling: a crafted
.tgzescaping the extraction directory, or making the service write outsideTSF_DATA_DIR.
The README's Known limitations and the Known limitations section of
.claude/rules/anonymizer-invariants.md list what the tool does not do, on purpose or for lack of a
reliable heuristic — hostnames that appear only in a log line no config
declares, IPv6, organisation names in certificate subjects, identifiers
embedded in binary files (flagged by the compare, redactable with
--redact-binaries), and free-text fields when a run opts out of removing
them with --keep-free-text. A report
about one of these is a welcome feature request, not a security advisory.
The web UI's threat model is also documented and deliberate:
- One shared account, LAN-grade. HTTP Basic auth with a single username/password, no rate limiting on failed attempts, no session revocation, no per-user audit trail. Whoever has the password can download the un-anonymized archive and its mapping. Anything wider than a trusted LAN belongs behind a reverse proxy that adds what is missing.
- The container is published on loopback by default, TLS mandatory as
soon as a certificate is configured and fail-closed when it is missing.
TSF_BIND_ADDRandTSF_TLS_CERT=(empty) are explicit opt-outs. The baretsf-anonymizer servealso listens on127.0.0.1by default (it warns when--hostis used to expose it), so the loopback default holds whether the CLI runs directly or behind the container. - The self-signed CA is trusted only where you imported it. A browser warning clicked through proves nothing about who answered.
Only the latest release on the 0.x line receives fixes. There is no
long-term support branch: upgrade to the newest version before reporting.
| version | supported |
|---|---|
latest 0.x release |
yes |
| anything older | no |
Whatever the tool's correctness, the operator's practices decide whether a customer's identity is protected:
- Keep the
*.mapping.jsonwith the customer's material, never with the anonymized archive. - Run a raw
grepof the anonymized tree for the customer's name, domains and site names before sharing it: the compare mode proves every change is mapping-driven, it cannot know that a string it never mapped is a name. - Leave delete the original after a clean verification on, or delete
data/jobs/<id>/inputyourself once the job is done. - Use a dedicated data directory on an encrypted volume;
./datais a bind mount, and the container runs as your user so you can wipe it withoutsudo.