Repository navigation
Contributing
The project is meant to be cloned, read and changed. Canonical source: CONTRIBUTING.md.
The value you accepted is not in the file you downloaded.
A tool that draws a black rectangle over live text is not a redaction tool. It is an expensive screenshot with a lie attached, and people have leaked real information believing otherwise.
A change that breaks one will not be merged, however good the rest of it is.
| # | Invariant |
|---|---|
| 1 | The source file is never mutated. |
| 2 | Only status === "accepted" reaches the exporter. |
| 3 | Redaction removes; it never covers. |
| 4 | Every export is verified before delivery. |
| 5 | A model's output is located, never trusted. |
| 6 | Nothing logs document content. |
| 7 | SHA-256 is not encryption. |
| 8 | Keys are random. |
Found a way to violate one that the tests do not catch? That is a security issue and a welcome contribution. Report it privately first (Security Model §13), ideally with a failing test.
No accounts, no auth. The repo is meant to run on your own machine against your own database and storage. The hosted demo is anonymous by design. For multi-user with real identities, fork it: ownership flows through one function (requireDocument), so swapping the identity source is a small change.
No telemetry that leaves the machine. Usage counters are rows in your own database.
No vendor lock-in that breaks local dev. Vercel Blob, Neon and the AI Gateway are the deployed defaults, not requirements. A clone runs against local Postgres, local RustFS, local Tesseract and a local model with no account anywhere. CI proves the first three on every pull request, and the model weekly. A code path that only works on Vercel is a bug. An OpenAI-compatible vendor is a row in lib/ai/providers/compatible.ts, not code.
pnpm setup # writes .env, generates secrets, can start things for you
docker compose up -d postgres rustfs rustfs-init
pnpm db:migrate
pnpm devOr docker compose up -d for everything in containers. pnpm setup --local --defaults --yes answers everything with defaults. A model is optional, which is a reasonable way to develop the UI and the way CI runs.
If you touch pnpm setup: every default it prints comes from the code that enforces it (defaultsFor, DEFAULT_EML_LIMITS, DEFAULT_EXPANSION_LIMITS, …), never copied into the script. If you add a limit, export its defaults and its envName and the script picks it up. A setup script that lies about the defaults is worse than none.
pnpm lint && pnpm typecheck && pnpm test && pnpm buildCI also runs Migrations, Database tests, OCR, the Compose stack (pnpm smoke for every format), and Changelog. See Testing and Benchmarks §5.
| If you touched… | Also run |
|---|---|
| OCR, image extraction, where image redactions land |
pnpm ocr:warm once, then pnpm test (runs the real-engine suites) |
| cleanup, quotas, rules, Hush tools, anything writing to the DB |
pnpm test:db against a throwaway TEST_DATABASE_URL
|
| anything the container assembles (native deps, workflow runtime, storage, Dockerfile) | docker compose up -d --build --wait && pnpm smoke && docker compose down -v |
| a parser or an exporter |
pnpm bench: a row suddenly ten times slower is the signal (quadratic range mapping and string edits do not show up in small unit fixtures) |
| the provider layer |
ANONIFY_OLLAMA_TESTS=1 with a pulled Qwen3-VL, or pnpm smoke --ai against a verified model |
Adding a format means an entry in lib/documents/formats.ts (typed against DocumentKind, so a half-added kind does not compile), a case in scripts/smoke.ts CASES, and an adversarial case (a guard test requires one).
Read the doc that covers your area first. Most decisions have a reason, and the reason is usually written down.
| Area | Doc | Wiki |
|---|---|---|
| The four layers, identity, concurrency | docs/architecture.md |
Architecture |
| The durable pipeline, batch export, expiry | docs/workflow.md |
Processing Workflow |
| Failure classification | docs/failure-codes.md |
Failure Codes |
| Detection order and cost | docs/ai-engine.md |
Redaction Engine |
| Providers, verification, prices |
docs/ai-providers.md, docs/connect-openai.md
|
AI Providers |
| Each format's pipeline, methods, vaults, mailbox rebuild | docs/pipelines.md |
Supported Formats · Export and Anonymisation · Batches and Mailboxes |
| Drivers, keys, encryption | docs/storage.md |
Storage and Encryption |
| Streaming readers | docs/streaming.md |
Streaming IO |
| The HTTP surface | docs/api.md |
API Reference |
| The schema | docs/data-model.md |
Data Model |
| Components, search, rules, Hush | docs/editor.md |
Review Workspace · Search and Rules · Hush Assistant |
| Store, undo, hooks | docs/client-state.md |
Review Workspace §7 |
| Tokens, report checks, presets, patterns, Hush | docs/security-internals.md |
Security Model |
| Presets | docs/presets.md |
Redaction Presets |
| Vercel | docs/deploy-vercel.md |
Deployment |
| MinIO → RustFS | docs/migrate-object-storage-to-rustfs.md |
Upgrading |
| Corpus and model benchmarks | benchmarks/README.md |
Testing and Benchmarks |
| The visual language | DESIGN.md |
- Comments explain why, not what. Several comments in this codebase exist because a test caught a real bug and the next person deserves to know.
- Match the surrounding code.
-
Keep processing logic out of React. Nothing in
lib/imports a component. - Prefer a simple implementation until a measurement says otherwise.
A change to redaction behaviour needs a test that reads the produced artifact, not one that asserts a function was called. Build a real file, redact it, then try to get the value back out (tests/security.test.ts is the pattern). Never commit real personal data.
- Explain why in the commit body. What changed is in the diff.
- One concern per PR.
- If you found a bug while doing something else, say so in the PR. That is the most valuable sentence in most changelogs.
- Issue templates: bug report · feature request. Community:
CODE_OF_CONDUCT.md.
.github/workflows/release.yml reads a merged PR's labels, moves the version in package.json, tags it and publishes a release. Nobody edits the version by hand.
| Bump | Labels |
|---|---|
minor (1.9.0 → 1.10.0) |
enhancement, formats
|
patch (1.9.2 → 1.9.3) |
bug, performance, testing, ci, infrastructure, ux, accessibility, security, benchmark
|
| nothing |
docs, documentation, and anything not listed |
- The highest bump wins.
docsnever vetoes, it only fails to raise. -
securityis a patch on purpose: a bypass fix is urgent, not incompatible. -
No label produces a major.
2.0.0is dispatched by hand by an admin. -
mainis protected, so the bump arrives as arelease/vX.Y.ZPR that the job opens, merges and tags. If that merge is refused, the PR is left open, and the version is delayed rather than lost. Requirements: Actions may open pull requests must be allowed (org and repo), and the bump PR is opened byGITHUB_TOKEN, so it starts no workflows. - The table lives in
scripts/release-labels.mjs, shared byrelease.ymlandci.yml.
A PR that moves the version must add a changelog fragment in the same PR. CI fails without one.
changelog.d/<issue-or-pull-request-number>.<section>.md
Sections: added, changed, deprecated, removed, fixed, security. One entry per file, no leading -, written for someone self-hosting Anonify who is deciding whether to pull: what changed in the output, what changed in configuration, what needs a migration. Not the refactors.
MBOX mailboxes are accepted as an upload, and open as a batch with one
document per messageFragments are separate files so that concurrent PRs never conflict. The release workflow assembles them into CHANGELOG.md, publishes that section as the GitHub release body, and deletes them in the same commit. Never edit CHANGELOG.md or the package.json version by hand. A PR that needs no entry (docs, a pure refactor) should carry no version-moving label. More: changelog.d/README.md.
Run the benchmark. The harness, the corpus and the scoring exist, and the pattern baseline is measured. What is missing is a published model run: #153. See Testing and Benchmarks §6.
Anonify: AI proposes, the application applies, and only what a person accepted is removed.
Repository · Report a bug · Request a feature · Security policy · Apache-2.0
This wiki describes v1.10.0 and summarises and links the in-repo documentation. Where the two disagree, the files under docs/, CHANGELOG.md and SECURITY.md are canonical.
Getting going
Using it
- Supported Formats
- Review Workspace
- Search and Rules
- Hush Assistant
- Redaction Presets
- Export and Anonymisation
- Batches and Mailboxes
How it works
- Architecture
- Processing Workflow
- Redaction Engine
- Storage and Encryption
- Streaming IO
- Data Model
- API Reference
- Security Model
When it breaks
Contributing
Repository · Issues · Releases · Changelog