Skip to main content

Omna — mask secrets and PII before your prompt leaves the machine

A small local proxy for AI coding tools. Point Claude Code, aider, Codex CLI or any Anthropic/OpenAI SDK app at http://127.0.0.1:7788 and every request is masked on the way out, sent to the provider with your own key or login, and un-masked on the way back. The tool never notices. The model never sees the real values.

you type:     "fix the config, SUPPORT_EMAIL=jane.doe@example.com AWS_ACCESS_KEY_ID=AKIA…"
model sees:   "fix the config, SUPPORT_EMAIL=[EMAIL_1] AWS_ACCESS_KEY_ID=[SECRET_GENERIC_SECRET_1]"
you get back: "…set SUPPORT_EMAIL to jane.doe@example.com…"   ← restored, streaming, inside tool calls too
  • Secrets (240 rules: AWS, GitHub, Stripe, OpenAI, Anthropic, database URLs, JWTs, private keys, …) never reach the provider. They become numbered tokens like [SECRET_AWS_KEY_1] that live only in the proxy's memory, never on disk, and are put back only in what comes back from the model, so when Claude edits the line that holds a key, the edit still matches your file. --no-restore-secrets redacts them for good instead.
  • Personal data (emails, phones, SSNs and 50 international ID formats with real checksum validation, cards, IBANs, IPs, names) becomes stable tokens: [EMAIL_1] is the same address in every request, so prompt caching keeps working.
  • Nothing is sent to Omna. The only network destination is the provider you were already using. A local receipt log (counts only, hash-chained) shows what was caught.
  • 16 ms to mask a 68 KB request — measured, not estimated, on an M5 MacBook Air, and it is on top of a call that already takes seconds. You will not feel it. (How it was measured.)

Same compiled engine as the Omna Mac app, browser extension and the omna Python library.

Install (macOS Apple Silicon, Linux x86_64/aarch64)

curl -fsSL https://omna.dev/cli/install.sh | bash

That installs the omna command (via uv), wires Claude Code, and starts the proxy.

Installing without piping to a shell

Piping a script from the internet straight into a shell means running it before you have read it. If that bothers you — and for a privacy tool it reasonably might — you never have to do it. Every step the installer takes, you can take yourself:

# 1. Read the script first, if you want to see what the one-liner does.
curl -fsSL https://omna.dev/cli/install.sh -o install.sh && less install.sh

# 2. Or skip it entirely — this is all it really does:
uv tool install --python 3.12 git+https://github.com/gaurjin/omna-plugin@v0.6.0
omna init            # Claude Code: ANTHROPIC_BASE_URL in ~/.claude/settings.json + a SessionStart hook
omna start -d        # background proxy on 127.0.0.1:7788
omna status

The installer has no privileged step of its own: it installs uv if you do not have it, runs the uv tool install above, and calls omna init. omna init is the only part that asks for sudo, it prints the exact commands before it runs them, and omna uninstall reverses all of it.

A brew install is tracked in docs/homebrew.md, with an honest account of what it is blocked on — the engine ships as a binary wheel, which Homebrew's source-build model does not fit cleanly.

omna init also wires aider, Codex CLI, VS Code (Copilot Chat and similar chat extensions), and Continue automatically if each is already installed/configured — nothing is added for a tool you don't have. Wire one later, or a tool you didn't have yet: omna enable aider / codex / continue; omna disable reverts it. VS Code is Mac-only for now (omna init's Mac step); see omna.dev/help/plugin-supported-tools for the full list, including the tools that only offer an in-app setting (Cursor, Cline, Windsurf — one line, typed once, inside the tool) and the ones not supported yet, with the real reason why.

On a Mac, omna init also does one more thing: it trusts a local certificate ("Omna Local Certificate Authority") and points the system proxy at Omna via a PAC file that names only AI hostnames, so apps that don't use ANTHROPIC_BASE_URL/OPENAI_BASE_URL (a desktop chat app, a browser) are masked too — not just Claude Code. That's one sudo password prompt, printed before it runs. Pass --no-system to skip it and wire Claude Code only. omna uninstall reverts it.

On a Mac, omna init also starts a menu-bar icon (omna menubar) showing live status — secrets kept off the wire, personal values tokenised, requests masked, what's covered, and masking overhead in ms, each on its own line. Its top line is the toggle itself (click, or the native checkmark, to pause/resume masking), and it's the only thing Omna adds to System Settings → Login Items: the masking proxy has no login item of its own, the menu-bar process supervises it. Quitting the icon only stops that one process: relaunch it any time from Spotlight/Launchpad as Omna Plugin (a small .app at /Applications/Omna Plugin.app, kept distinct from the native Omna Mac app so the two never collide — though it now shares that app's icon, so look for the name if you have both installed), or turn on its Launch at Login toggle so it comes back on its own after every reboot or log-out. Its Keep Local Reports toggle turns the receipt log off entirely (not just hides it) for anyone who doesn't want even anonymous counts kept on their machine — omna status/omna report/omna log still run, they just have nothing new to show while it's off.

Cursor and Cline only offer an in-app setting (paste http://127.0.0.1:7788 / /v1 — see the docs link above for the exact menu path). Any custom SDK app uses the same address, by hand:

export ANTHROPIC_BASE_URL=http://127.0.0.1:7788   # Anthropic SDK
export OPENAI_BASE_URL=http://127.0.0.1:7788/v1   # OpenAI SDK, Cursor BYOK

What is covered

Surface How Turn it off
Claude Code, aider, Codex CLI, VS Code, Continue wired automatically by omna init (each only if already installed) omna disable claude-code / aider / codex / continue
Cursor, Cline, Windsurf one in-app setting, typed once — full instructions unset it in the tool
Custom SDK apps ANTHROPIC_BASE_URL/OPENAI_BASE_URL set by hand unset the env var
Browsers (claude.ai, chatgpt.com, gemini, … — a named list, not every site; add your own with omna hosts add SITE) the system proxy (PAC + a local certificate), set up by omna init on a Mac omna hosts remove SITE for one site, omna init --no-system or omna uninstall for all of them
Desktop AI apps that honour the system proxy same system proxy as browsers same as above
Desktop apps that ignore the system proxy omna capture app NAME (Stage 3, per app, its own signed network extension) omna bypass app NAME (stops masking; there's no command yet to release the app from capture itself)
Any specific app, at any layer omna bypass app NAME — tunnelled through untouched, still receipted omna mask app NAME

Try it in 30 seconds

omna mask "email jane.doe@example.com, key AKIAIOSFODNN7EXAMPLE, phone 212-555-0199"
# email [EMAIL_1], key [SECRET_AWS_KEY_1], phone [PHONE_1]

omna log         # one line per request: time, route, status, ms, what was masked
omna log --verify

Commands

Command What it does
omna start [-d] [--smart] [--no-restore-secrets] Run the proxy (foreground, or -d in the background). --smart adds the on-device Contextual model for prose names (809 MB download once, slower).
omna stop / omna ensure Stop the background proxy / start it if it is not running (the Claude Code hook calls this).
omna status Running? Claude Code wired? Receipts today.
omna menubar Mac status icon (starts automatically): click the top line to pause/resume, see live counts (secrets kept off the wire, personal values tokenised, requests masked, coverage, masking overhead), toggle Launch at Login, Keep Local Reports or Restore Real Values in Browser, or uninstall.
omna log [-n 20] [--verify] [--json] Local receipts. --verify checks the hash chain.
omna report [--days 7] [--json] [--html FILE] Weekly summary from the receipts: requests enabled, distinct secrets kept off the wire, PII tokenised, destinations, chain status, masking cost.
omna report --export FILE Write a counts-only copy of that report, safe to hand to a company admin — numbers plus your org/department tags. No prompt text, no real values, no usernames, no file paths, no app names.
omna report --merge FILE... Add up exported files into one company view plus a per-department view. A machine whose file is collected twice is counted once.
omna enroll [--org NAME] [--dept NAME] [--forget] Tag this machine so its exports can be grouped. With no arguments, shows the current tags. Nothing is sent anywhere — see below.
omna mask [TEXT or -] Mask a string or stdin.
omna allow VALUE Never mask this exact value again (false positive).
omna forget Wipe the token registry and its encryption key (tokens renumber).
omna crash [--show N] [--send] [--clear] What broke on this machine. Masked by Omna's own engine before it is written to disk, and never sent anywhere — --send opens a GitHub issue pre-filled with the report you just read, and you submit it or close the tab.
omna verify-model Re-hash the on-device Contextual model and compare it to the hash this engine was built against. Does the full check every time, never a cached answer.
omna dashboard Open the live dashboard. It is protected by a local token (~/.omna/dashboard.token, owner-only), so another program on your machine cannot read your numbers over HTTP.
omna init [--project] [--no-system] / omna uninstall Wire / un-wire Claude Code, aider, Codex CLI, VS Code and Continue (each only if already installed), and (on a Mac, unless --no-system) the system proxy + certificate. init backs up every settings file it touches first and removes only its own keys on uninstall.
omna tools / omna enable TOOL / omna disable TOOL Show, or turn on/off, which tools Omna covers. enable/disable claude-code, aider, codex, or continue also wire/unwire it (same as init/uninstall); other tool names (Cursor) just record the policy today.
omna apps / omna bypass app NAME / omna mask app NAME Show, or set, what happens to an app's traffic through the system proxy: mask (default) tokenises it like everything else, bypass tunnels it through untouched (still receipted, so you can see what wasn't masked).
omna capture app NAME Stage 3: deep-capture an app that ignores the system proxy, via its own signed network extension.
omna hosts / omna hosts add HOST / omna hosts remove HOST Show, or add/remove, the hostnames the system proxy treats as AI traffic.

Speed

Measured on a MacBook Air M5 with Claude Code 2.1.274, a 4-request task (read a file, write a file, answer):

direct through omna
wall time 8.8 s 9.8 s
fast masking, 68 KB request body 16 ms

Fast masking (rules + checksums, the default) is the 16 ms. Smart masking (--smart, which adds the on-device Contextual model for prose names) is slower and off by default — it is a deliberate choice you make, not a surprise you discover.

omna status reports the real average for your machine and your traffic (avg_mask_ms_this_run), so you never have to take this table's word for it.

The proxy relays the stream as it arrives (pings included), forwards anthropic-beta, anthropic-version, cache_control and the system array untouched, and forwards provider errors unmodified, per Claude Code's gateway compatibility guide.

What is stored on your machine

File (~/.omna/) Contents Permissions
registry.json token → real value for reversible PII (needed to restore replies). Secrets are never in it; they live in the running proxy's memory only. 0600 + encrypted
receipts.jsonl one hash-chained line per request: route, status, ms, counts per entity. No values. 0600
ruleset.json your allowlist and custom patterns ({"allowlist": [...], "custom_rules": [{"label": "CUSTOMER_ID", "pattern": "ACME-\\d{6}"}]}). Created with loopback addresses (127.0.0.1, 0.0.0.0, ::1, localhost) allowlisted.
proxy.log, omna.pid background-process housekeeping
policy.json which tools/apps/hosts are covered, and — only if you ran omna enroll — an organisation name, a department name, and a random device id. 0600
crashes.jsonl the last 50 crashes: error type, message, stack frames (filenames only), versions. Masked by the engine before it is written, so a value that leaked into an error message is a token here too. Never sent. 0600
dashboard.token the local secret the dashboard asks for. 0600

What Omna sends, and to whom

Nothing, to us. The plugin makes exactly one kind of outbound connection: the request you were already making, to the AI provider you were already using, with your own key.

There is no analytics call, no crash reporter phoning home, no licence check, no update ping. When something crashes, it is written to ~/.omna/crashes.jsonl on your machine and stays there — omna crash shows you the exact bytes, and omna crash --send opens a pre-filled GitHub issue that you submit, or close. That is a deliberate difference from tools that ship an opt-in crash reporter: an opt-in promise is something you have to trust, and a file on your disk is something you can read.

Where the real values live, and what protects them

registry.json is the only file Omna writes that contains real values, so it is the only one that needs more than file permissions.

It is sealed with AES-256-GCM. The key is 32 random bytes kept in your macOS Keychain — never on disk next to the file it opens, and never sent anywhere. GCM also authenticates, so a file that someone edited fails to open instead of quietly handing back changed mappings.

  • omna status always tells you which state you are in, in plain words.
  • If your machine has no Keychain (Linux, CI, a headless box), the registry stays as it was before: plaintext at 0600. Omna says so loudly rather than refusing to run — and on that kind of machine, turn on full-disk encryption.
  • OMNA_REGISTRY_ENCRYPTION=off opts out entirely and touches the Keychain not at all.
  • If the key is ever lost, Omna keeps masking but stops writing, so it can never overwrite mappings it cannot read. omna forget starts cleanly from scratch.
  • omna forget and omna uninstall remove the key along with the file.

Company reports, without a server

A company with 100 laptops does not want 100 separate reports, and Omna does not want their prompts. So the rollup is done with files, not a backend:

  1. IT tags each machine at install time (or later with omna enroll): curl -fsSL https://omna.dev/cli/install.sh | sh -s -- --org "Acme Inc" --dept engineering
  2. Each machine writes a counts-only file: omna report --export machine.json
  3. Whoever is collecting them runs omna report --merge *.json and gets a company total plus a per-department breakdown.

Nothing is uploaded. There is no Omna server in this picture at all — the files move however that company already moves files. The export is an allowlist of number fields, so a new field added to the report later cannot quietly start riding along. It deliberately leaves out your home path (which carries your username) and which apps you run. The device id is random and local; it exists so two files can be told apart and a duplicate can be spotted, and omna enroll --forget deletes it.

Reading browser replies: real values or labels

Restore Real Values in Browser (menu bar, on by default) decides what you read back in a browser reply: the real name, or the [EMAIL_1] label that was actually sent. Masking is not affected by it and is never optional — this only changes what comes back.

It is scoped to the browser on purpose. The coding tools always get real values put back, because there a label reaching the tool breaks it: Claude Code would write [SECRET_AWS_KEY_1] into your file instead of editing the real line.

If you also run the Chrome extension, the plugin steps aside and lets the extension handle the browser reply. It has to: both halves label things [EMAIL_1], [PERSON_1] … and number them independently, so whoever restores second could swap in a different person's value. The extension tags the requests it handles so the plugin knows, per request, to leave that reply alone — masking still happens either way.

What it refuses

A request to an inference path (/v1/messages, /v1/chat/completions, /v1/responses, …) whose body cannot be parsed as JSON (compressed, malformed, wrong content type) is answered with 400 omna_refused and never forwarded. Non-inference uploads (/v1/files, audio) pass through unmasked and are marked passthrough in the receipt. If the proxy is not running, the tool's requests fail to connect: nothing leaves unmasked.

Honest limits (v1)

  • A pinned app (one that rejects our certificate on purpose, e.g. it checks the cert fingerprint itself) is refused, never forwarded unmasked, and named: omna status shows refused: AppName → host ×N (pinned) with the exact omna bypass app "AppName" command to let it through untouched instead.
  • If the daemon goes down, what a browser does next depends on whether it already loaded the PAC file: one that hasn't yet falls back to DIRECT (unmasked) until launchd restarts the daemon (seconds); one that already cached the proxy address instead fails closed (a connection error, nothing loads) until then. This Mac's file:// PAC is not honoured by Safari/Chrome, so the PAC itself is served by the proxy — an honest limit either way, not a fail-closed guarantee like the coding-CLI door.
  • Firefox, Node, Python, Java, Ruby and Deno keep their own certificate lists and ignore the macOS keychain, so each needs one line to trust the local certificate — all of them are in docs/certificate-trust.md. (Firefox: about:config → security.enterprise_roots.enabled = true. Node: NODE_EXTRA_CA_CERTS, which omna init already sets for VS Code.) Safari, Chrome, Edge, Brave, curl and Go need nothing.
  • Fast masking (rules + checksums) runs by default. Prose names in free text need --smart, which loads a local model and slows each request; it is off by default.
  • Windows: no engine wheel yet (WSL works).
  • A tool's own cloud features (for example Cursor tab-completion) never pass through a local proxy.
  • Model thinking blocks are passed through untouched in both directions (the API requires it), so a name the model mentions inside its thinking shows as a token there.
  • Two engine quirks are worked around here and logged upstream: assignment-style secrets keep their variable name outside the token (so AWS_ACCESS_KEY_ID=[SECRET_GENERIC_SECRET_1], not one anonymous token), and a secret span no longer swallows the newline after it. One known false positive remains: prose like "authorization context: something" can be flagged as a generic secret.
  • This masks secrets and PII. It does not make anything "compliant".
  • No exceptions, not even for us: with Claude Code wired to this proxy, Claude reading this README's own demo values (the fake email/AWS key/phone two sections up) sees them as tokens too, same as it would see yours. That's the proxy working as designed, not a bug — it is a side effect worth knowing if you develop this repo with Claude Code pointed at itself.

Free vs paid

Everything in this repository is free. The paid layer, for teams, is the proof: a signed weekly evidence pack built from these receipts, per-machine coverage, and org-wide rulesets. See a sample at omna.dev/sample-report.

Development

uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
.venv/bin/pytest -q          # 205 tests, ~7 s (fake upstream, no network)
.venv/bin/omna start          # foreground, then: ANTHROPIC_BASE_URL=http://127.0.0.1:7788 claude

MIT for this shell. The engine wheel (omna-pii-mask) is binary-only.

Metadata

Release files for omna-plugin 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distribution (wheel)

Table of built distributions (wheels) for omna-plugin 0.6.0
File Interpreter ABI Platform
omna_plugin-0.6.0-py3-none-any.whl Python 3 none any Details

Release files / omna_plugin-0.6.0-py3-none-any.whl

Download URL omna_plugin-0.6.0-py3-none-any.whl
Size 125.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
324e32b4bd9327f4d7bf8082f390312bdf291378cd0aa3edd61b53fb46fe9af1
BLAKE2b-256 checksum
How to use checksums
fd6b2a9009c25a94136b29bde7693b04105aee1732854eb79a2761f0b45ceee7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.13

Security for omna-plugin 0.6.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.6.0 This release

1 release file No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page