omna-plugin 0.6.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. omna_plugin-0.6.3/.gitignore +9 -0
  2. omna_plugin-0.6.3/.python-version +1 -0
  3. omna_plugin-0.6.3/CLAUDE.md +74 -0
  4. omna_plugin-0.6.3/Casks/omna.rb +46 -0
  5. omna_plugin-0.6.3/LICENSE +21 -0
  6. omna_plugin-0.6.3/PKG-INFO +479 -0
  7. omna_plugin-0.6.3/README.md +446 -0
  8. omna_plugin-0.6.3/SECURITY.md +87 -0
  9. omna_plugin-0.6.3/docs/SBOM.md +49 -0
  10. omna_plugin-0.6.3/docs/certificate-trust.md +152 -0
  11. omna_plugin-0.6.3/docs/homebrew.md +107 -0
  12. omna_plugin-0.6.3/docs/sbom.cdx.json +492 -0
  13. omna_plugin-0.6.3/docs/signing.md +89 -0
  14. omna_plugin-0.6.3/docs/superpowers/plans/2026-09-17-product-4-plugin.md +88 -0
  15. omna_plugin-0.6.3/docs/superpowers/plans/2026-09-17-stage-2-3-mac-coverage.md +1686 -0
  16. omna_plugin-0.6.3/docs/superpowers/plans/2026-09-20-mappings-review.md +1318 -0
  17. omna_plugin-0.6.3/docs/superpowers/plans/2026-09-20-masking-styles.md +1511 -0
  18. omna_plugin-0.6.3/install.sh +51 -0
  19. omna_plugin-0.6.3/omna.spec +79 -0
  20. omna_plugin-0.6.3/pyproject.toml +58 -0
  21. omna_plugin-0.6.3/scripts/build-cask.sh +65 -0
  22. omna_plugin-0.6.3/scripts/live/README.md +17 -0
  23. omna_plugin-0.6.3/scripts/live/leak_test.mjs +119 -0
  24. omna_plugin-0.6.3/scripts/live/send_test.mjs +117 -0
  25. omna_plugin-0.6.3/scripts/live/upload_test.mjs +85 -0
  26. omna_plugin-0.6.3/scripts/make-sbom.py +84 -0
  27. omna_plugin-0.6.3/src/omna_plugin/__init__.py +0 -0
  28. omna_plugin-0.6.3/src/omna_plugin/__main__.py +16 -0
  29. omna_plugin-0.6.3/src/omna_plugin/adapters/__init__.py +29 -0
  30. omna_plugin-0.6.3/src/omna_plugin/adapters/base.py +44 -0
  31. omna_plugin-0.6.3/src/omna_plugin/adapters/generic.py +58 -0
  32. omna_plugin-0.6.3/src/omna_plugin/adapters/uploads.py +193 -0
  33. omna_plugin-0.6.3/src/omna_plugin/aider.py +147 -0
  34. omna_plugin-0.6.3/src/omna_plugin/assets/AppIcon.icns +0 -0
  35. omna_plugin-0.6.3/src/omna_plugin/assets/menubar-icon.png +0 -0
  36. omna_plugin-0.6.3/src/omna_plugin/body.py +101 -0
  37. omna_plugin-0.6.3/src/omna_plugin/claude_code.py +120 -0
  38. omna_plugin-0.6.3/src/omna_plugin/cli.py +1091 -0
  39. omna_plugin-0.6.3/src/omna_plugin/codex.py +91 -0
  40. omna_plugin-0.6.3/src/omna_plugin/config.py +114 -0
  41. omna_plugin-0.6.3/src/omna_plugin/continue_dev.py +145 -0
  42. omna_plugin-0.6.3/src/omna_plugin/crashlog.py +266 -0
  43. omna_plugin-0.6.3/src/omna_plugin/crashsend.py +104 -0
  44. omna_plugin-0.6.3/src/omna_plugin/daemon.py +111 -0
  45. omna_plugin-0.6.3/src/omna_plugin/dashboard.py +197 -0
  46. omna_plugin-0.6.3/src/omna_plugin/engine.py +687 -0
  47. omna_plugin-0.6.3/src/omna_plugin/fakes.py +248 -0
  48. omna_plugin-0.6.3/src/omna_plugin/hands_off.py +67 -0
  49. omna_plugin-0.6.3/src/omna_plugin/mac/__init__.py +0 -0
  50. omna_plugin-0.6.3/src/omna_plugin/mac/app_bundle.py +104 -0
  51. omna_plugin-0.6.3/src/omna_plugin/mac/certs.py +20 -0
  52. omna_plugin-0.6.3/src/omna_plugin/mac/launchd.py +101 -0
  53. omna_plugin-0.6.3/src/omna_plugin/mac/netproxy.py +58 -0
  54. omna_plugin-0.6.3/src/omna_plugin/mac/setup.py +144 -0
  55. omna_plugin-0.6.3/src/omna_plugin/mac/vscode.py +160 -0
  56. omna_plugin-0.6.3/src/omna_plugin/mappings.py +291 -0
  57. omna_plugin-0.6.3/src/omna_plugin/menubar.py +270 -0
  58. omna_plugin-0.6.3/src/omna_plugin/office.py +634 -0
  59. omna_plugin-0.6.3/src/omna_plugin/pipeline.py +290 -0
  60. omna_plugin-0.6.3/src/omna_plugin/policy.py +320 -0
  61. omna_plugin-0.6.3/src/omna_plugin/procs.py +73 -0
  62. omna_plugin-0.6.3/src/omna_plugin/proxy.py +477 -0
  63. omna_plugin-0.6.3/src/omna_plugin/receipts.py +141 -0
  64. omna_plugin-0.6.3/src/omna_plugin/report.py +376 -0
  65. omna_plugin-0.6.3/src/omna_plugin/stream.py +220 -0
  66. omna_plugin-0.6.3/src/omna_plugin/style.py +106 -0
  67. omna_plugin-0.6.3/src/omna_plugin/system_door.py +380 -0
  68. omna_plugin-0.6.3/src/omna_plugin/upstream_tls.py +198 -0
  69. omna_plugin-0.6.3/src/omna_plugin/vault.py +215 -0
  70. omna_plugin-0.6.3/src/omna_plugin/while_typing.py +42 -0
  71. omna_plugin-0.6.3/tests/conftest.py +18 -0
  72. omna_plugin-0.6.3/tests/test_adapters.py +365 -0
  73. omna_plugin-0.6.3/tests/test_aider.py +86 -0
  74. omna_plugin-0.6.3/tests/test_app_bundle.py +103 -0
  75. omna_plugin-0.6.3/tests/test_body.py +105 -0
  76. omna_plugin-0.6.3/tests/test_claude_code.py +61 -0
  77. omna_plugin-0.6.3/tests/test_cli.py +715 -0
  78. omna_plugin-0.6.3/tests/test_codex.py +70 -0
  79. omna_plugin-0.6.3/tests/test_config.py +21 -0
  80. omna_plugin-0.6.3/tests/test_continue_dev.py +144 -0
  81. omna_plugin-0.6.3/tests/test_crashlog.py +188 -0
  82. omna_plugin-0.6.3/tests/test_crashsend.py +145 -0
  83. omna_plugin-0.6.3/tests/test_daemon.py +42 -0
  84. omna_plugin-0.6.3/tests/test_engine.py +498 -0
  85. omna_plugin-0.6.3/tests/test_fakes.py +137 -0
  86. omna_plugin-0.6.3/tests/test_hands_off.py +46 -0
  87. omna_plugin-0.6.3/tests/test_mac_setup.py +397 -0
  88. omna_plugin-0.6.3/tests/test_mappings.py +216 -0
  89. omna_plugin-0.6.3/tests/test_menubar.py +217 -0
  90. omna_plugin-0.6.3/tests/test_office.py +362 -0
  91. omna_plugin-0.6.3/tests/test_pipeline.py +212 -0
  92. omna_plugin-0.6.3/tests/test_policy.py +225 -0
  93. omna_plugin-0.6.3/tests/test_procs.py +29 -0
  94. omna_plugin-0.6.3/tests/test_proxy.py +645 -0
  95. omna_plugin-0.6.3/tests/test_receipts.py +135 -0
  96. omna_plugin-0.6.3/tests/test_report.py +245 -0
  97. omna_plugin-0.6.3/tests/test_stream.py +161 -0
  98. omna_plugin-0.6.3/tests/test_style.py +107 -0
  99. omna_plugin-0.6.3/tests/test_system_door.py +632 -0
  100. omna_plugin-0.6.3/tests/test_upstream_tls.py +210 -0
  101. omna_plugin-0.6.3/tests/test_vault.py +129 -0
  102. omna_plugin-0.6.3/tests/test_version.py +9 -0
  103. omna_plugin-0.6.3/tests/test_vscode.py +150 -0
  104. omna_plugin-0.6.3/tests/tls_helpers.py +116 -0
  105. omna_plugin-0.6.3/uv.lock +1328 -0
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.egg-info/
4
+ dist/
5
+ .pytest_cache/
6
+
7
+ # PyInstaller work directory (dist/ is already ignored above).
8
+ build/
9
+ .DS_Store
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,74 @@
1
+ # omna-plugin/CLAUDE.md
2
+
3
+ ## What this repo is
4
+ Product #4 of Omna: **the plugin** — ONE command that masks everything an AI sees from this machine and will replace the Mac app. The Chrome extension STAYS alongside it (owner decision 2026-10-18 — never plan an extension sunset). This repo is Stage 1 (the mail-room proxy + coding tools via base URL); Stages 1b–5 (report, Mac system proxy + certificate via mitmproxy, VPN extension, Windows/Linux, Mac-app sunset) are in the Product Spec §6. Thin Python shell
5
+ (`src/omna_plugin/`) over the compiled `omna-pii-mask` engine wheel (built from the private
6
+ `omna-workspace/bindings/omna-core-py`; never copy engine code here). Strategy and the blueprint live in
7
+ `~/Developer/Omna Labs/Omna Notes/:Vision/Omna Plugin — Product Spec.md` §6. Plans: Stage 1 =
8
+ `docs/superpowers/plans/2026-09-17-product-4-plugin.md` (done); **Stages 2+3 =
9
+ `docs/superpowers/plans/2026-09-17-stage-2-3-mac-coverage.md`** (the architecture decisions with their
10
+ reasons are Product Spec §6.9 — read it before touching Stage 2/3 code).
11
+
12
+ ## Stage 2/3 shape (decided 2026-09-17): three doors, one mail room
13
+ - **Mail room** = `pipeline.py` (mask → forward → restore → receipt). Every door calls it; one `MaskingSession`.
14
+ - **API door** = `proxy.py` on 127.0.0.1:7788 (Stage 1, unchanged; tools use a base URL).
15
+ - **System door** = a `mitmproxy` "regular" listener on 127.0.0.1:7789 behind a macOS PAC that names ONLY
16
+ AI hostnames (`policy.json`); `allow_hosts` decrypts only those; a local CA "Omna Local Certificate
17
+ Authority" in `~/.omna/ca` is trusted once (one `sudo` batch, printed before it runs); `launchd` keeps it alive.
18
+ - **Deep door** = `mitmproxy` "local" mode per app name (its signed "Mitmproxy Redirector" network
19
+ extension; no Swift of ours). Only for apps that ignore the system proxy.
20
+ - **Policy** = `~/.omna/policy.json`: hosts, tools on/off, apps mask/bypass (by process name via `lsof`),
21
+ deep-capture apps, doors, masking style. A bypass is always receipted. Pinned apps are refused and named
22
+ in `omna status`. **The menu bar writes this file from ANOTHER process**, so anything it can toggle must be
23
+ re-read from disk, not captured at start-up — `OmnaAddon._fresh()` (mtime-gated) and `proxy._live_style()`
24
+ do that for `restore_browser` and `style`. Before 2026-09-21 they did not, and the menu-bar restore toggle
25
+ silently did nothing until a restart. Everything else (hosts, per-app rules) is fixed when the door starts.
26
+ - **Masking style** = `style.py`, the ONLY place that decides. `style_for_door(door, requested)` for the three
27
+ doors; `style_for_extension(requested, system_door_on)` for Omna's Chrome extension, which masks inside the
28
+ browser and reads its answer from `/omna/health`'s `style` field (#143). A realistic fake value cannot
29
+ survive being masked a second time — it looks like real data to the next masker — so the extension keeps
30
+ numbered tokens while the system door is on. A refusal is always returned with a printable sentence.
31
+ - `mitmproxy` is a library dependency, used as-is (fork-free). Verified hook/option names are listed at the
32
+ top of the Stage 2+3 plan; trust that list over memory.
33
+
34
+ ## Token literals: two pieces, always
35
+ Never type a full placeholder token (open bracket, KIND, underscore, digits, close bracket) in any file in
36
+ this repo — write it as two adjacent string pieces, e.g. `"[EMAIL_" "1]"` in Python. A Claude Code
37
+ session that is itself wired to the plugin restores every full token literal into the real value from the
38
+ registry while writing the file (that is the restore-in-tool-calls feature working). On 2026-09-17 this put
39
+ the owner's real e-mail into a plan file; caught before commit. Same trick as the runtime-built fake keys.
40
+
41
+ ## Vocabulary (use these words)
42
+ - Product name: **the plugin**. "Proxy" is the plumbing word for how it works.
43
+ - Masking layers: L1 Deterministic, L2 Secrets, L3 Contextual (the privacy-filter model), L4 Fusion.
44
+ Fast masking = L1+L2 (default). Smart masking = +L3 (`--smart`).
45
+ - Never say "compliant". Say "masks secrets and PII".
46
+
47
+ ## Rules
48
+ - Tests first; `.venv/bin/pytest -q` must be green before any commit. The proxy tests use an in-process
49
+ fake upstream (`tests/test_proxy.py`); no network in tests.
50
+ - **Nothing in this repo may send anything to Omna.** The only outbound connection is the user's own
51
+ request to their own provider. Crashes go to `~/.omna/crashes.jsonl` (`crashlog.py`), masked by the
52
+ engine before they are written and sent only when the person runs `omna crash --send`. A test reads
53
+ `crashlog.py`'s own source and fails if `httpx`/`requests`/`socket` ever appear in it — that test is
54
+ the guard on the product's central claim, so do not weaken it.
55
+ - The dashboard needs the token in `~/.omna/dashboard.token` (#134); CORS is answered only for
56
+ localhost origins plus `policy.cors_origins` (#137a). Both exist because loopback is not a security
57
+ boundary — do not "simplify" either back to open.
58
+ - Never log or persist a real value except in `registry.json` (0600, AES-256-GCM via `vault.py`, key in
59
+ the macOS Keychain; plaintext fallback with a loud `omna status` line where there is no Keychain).
60
+ Receipts are counts only. Tests must never touch the real Keychain — `tests/conftest.py` forces
61
+ `OMNA_REGISTRY_ENCRYPTION=off` for the whole suite; opt in with the in-memory fake in `test_vault.py`.
62
+ - Keep the gateway contract: forward `anthropic-beta`/`anthropic-version` verbatim, never reshape the
63
+ `system` array, never buffer SSE, forward upstream error bodies unmodified, never touch `thinking`,
64
+ `signature`, `cache_control`, or base64 `data`.
65
+ - Live verification = a nested headless run: `env -u CLAUDECODE ANTHROPIC_BASE_URL=http://127.0.0.1:7788
66
+ claude -p "..." --allowedTools Read,Write` with a file containing a fake key; check `omna log`.
67
+ - Claude Code hooks (re-checked live on 2.1.310, 2026-10-25): PostToolUse `updatedToolOutput` DOES replace a
68
+ tool result before the model sees it (all tools). Typed prompts still CANNOT be rewritten (`updatedPrompt` is
69
+ gone; UserPromptSubmit can only block or add context). Hooks = optional second layer, never the main door.
70
+
71
+ ## Publishing (owner-manual)
72
+ The public GitHub repo `gaurjin/omna-plugin` already exists (tags up to `v0.5.1` pushed). Releasing a
73
+ new version: `git tag vX.Y.Z && git push origin vX.Y.Z` → update the version pin in `README.md` and
74
+ `install.sh` to match → `uv build && uv publish`. The installer URL in README assumes that repo path.
@@ -0,0 +1,46 @@
1
+ cask "omna" do
2
+ version "0.6.2"
3
+ sha256 "19e7a44651c56af66ee43837416ae3118a30a3f38444dcde7edea3a7af891cc1"
4
+
5
+ url "https://github.com/gaurjin/omna-plugin/releases/download/v#{version}/omna-#{version}-macos-arm64.zip",
6
+ verified: "github.com/gaurjin/omna-plugin/"
7
+ name "Omna"
8
+ desc "Masks secrets and PII before your prompt leaves the machine"
9
+ homepage "https://omna.dev/"
10
+
11
+ # Apple Silicon only, for the same reason the engine has no source
12
+ # distribution: it ships as an architecture-specific compiled wheel.
13
+ depends_on arch: :arm64
14
+ depends_on macos: ">= :ventura"
15
+
16
+ binary "omna/omna"
17
+
18
+ caveats <<~EOS
19
+ Wire up your tools (Claude Code, aider, Codex CLI, VS Code, Continue) and,
20
+ on a Mac, the system proxy + local certificate:
21
+
22
+ omna init
23
+ omna start -d
24
+ omna status
25
+
26
+ "omna init" asks for your password once, and prints every command it will
27
+ run as administrator before running it.
28
+
29
+ To remove Omna completely, run this FIRST:
30
+
31
+ omna uninstall
32
+
33
+ It un-wires your tools, removes the certificate and system proxy, and
34
+ deletes the encryption keys from your Keychain. Homebrew cannot reach the
35
+ Keychain, so "brew uninstall" alone would leave those two keys behind.
36
+ EOS
37
+
38
+ # Everything Omna writes on disk lives in one directory. Verified 2026-09-20:
39
+ # `brew uninstall --zap` does remove it — but it canNOT remove the two
40
+ # Keychain items (the registry key and the receipts key), because a zap
41
+ # stanza only trashes files. Hence the caveat above telling people to run
42
+ # `omna uninstall` first, which does.
43
+ zap trash: [
44
+ "~/.omna",
45
+ ]
46
+ end
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gaurav Jain
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,479 @@
1
+ Metadata-Version: 2.5
2
+ Name: omna-plugin
3
+ Version: 0.6.3
4
+ Summary: Omna — local privacy proxy for AI coding tools. Masks secrets and PII before a prompt leaves your machine, restores them in the reply.
5
+ Project-URL: Homepage, https://omna.dev
6
+ Project-URL: Repository, https://github.com/gaurjin/omna-plugin
7
+ Author-email: gaurav <gaurjin@gmail.com>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: aider,anthropic,claude-code,dlp,openai,pii,privacy,proxy,secrets
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: MacOS
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Security
18
+ Requires-Python: >=3.12
19
+ Requires-Dist: cryptography>=42
20
+ Requires-Dist: httpx>=0.27
21
+ Requires-Dist: mitmproxy<13,>=12.2
22
+ Requires-Dist: omna-pii-mask<0.3,>=0.2.10
23
+ Requires-Dist: pillow>=10
24
+ Requires-Dist: pystray<1,>=0.19
25
+ Requires-Dist: ruamel-yaml>=0.18
26
+ Requires-Dist: starlette>=0.38
27
+ Requires-Dist: uvicorn>=0.30
28
+ Provides-Extra: dev
29
+ Requires-Dist: anyio>=4; extra == 'dev'
30
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
31
+ Requires-Dist: pytest>=8; extra == 'dev'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # Omna — mask secrets and PII before your prompt leaves the machine
35
+
36
+ A small local proxy for AI coding tools. Point Claude Code, aider, Codex CLI or any
37
+ Anthropic/OpenAI SDK app at `http://127.0.0.1:7788` and every request is masked on
38
+ the way out, sent to the provider with **your own key or login**, and un-masked on
39
+ the way back. The tool never notices. The model never sees the real values.
40
+
41
+ ```
42
+ you type: "fix the config, SUPPORT_EMAIL=jane.doe@example.com AWS_ACCESS_KEY_ID=AKIA…"
43
+ model sees: "fix the config, SUPPORT_EMAIL=[EMAIL_1] AWS_ACCESS_KEY_ID=[SECRET_GENERIC_SECRET_1]"
44
+ you get back: "…set SUPPORT_EMAIL to jane.doe@example.com…" ← restored, streaming, inside tool calls too
45
+ ```
46
+
47
+ - **Secrets** (240 rules: AWS, GitHub, Stripe, OpenAI, Anthropic, database URLs, JWTs, private keys, …) never
48
+ reach the provider. They become numbered tokens like `[SECRET_AWS_KEY_1]` that live only in the proxy's memory,
49
+ never on disk, and are put back only in what comes *back* from the model, so when Claude edits the line that
50
+ holds a key, the edit still matches your file. `--no-restore-secrets` redacts them for good instead.
51
+ - **Personal data** (emails, phones, SSNs and 50 international ID formats with real checksum validation,
52
+ cards, IBANs, IPs, names) becomes stable tokens: `[EMAIL_1]` is the same address in every request, so
53
+ prompt caching keeps working.
54
+ - **Nothing is sent to Omna.** The only network destination is the provider you were already using.
55
+ A local receipt log (counts only, hash-chained) shows what was caught.
56
+ - **16 ms** to mask a 68 KB request — measured, not estimated, on an M5 MacBook Air, and it is on top of a
57
+ call that already takes seconds. You will not feel it. ([How it was measured](#speed).)
58
+
59
+ Same compiled engine as the Omna Mac app, browser extension and the `omna` Python library.
60
+
61
+ ## Install (macOS Apple Silicon, Linux x86_64/aarch64)
62
+
63
+ ```sh
64
+ curl -fsSL https://omna.dev/cli/install.sh | bash
65
+ ```
66
+
67
+ That installs the `omna` command (via [uv](https://docs.astral.sh/uv/)), wires Claude Code, and starts the
68
+ proxy.
69
+
70
+ ### Installing without piping to a shell
71
+
72
+ Piping a script from the internet straight into a shell means running it before you have read it.
73
+ If that bothers you — and for a privacy tool it reasonably might — **you never have to do it.**
74
+ Every step the installer takes, you can take yourself:
75
+
76
+ ```sh
77
+ # 1. Read the script first, if you want to see what the one-liner does.
78
+ curl -fsSL https://omna.dev/cli/install.sh -o install.sh && less install.sh
79
+
80
+ # 2. Or skip it entirely — this is all it really does:
81
+ uv tool install --python 3.12 omna-plugin # or: pipx install omna-plugin
82
+ omna init # Claude Code: ANTHROPIC_BASE_URL in ~/.claude/settings.json + a SessionStart hook
83
+ omna start -d # background proxy on 127.0.0.1:7788
84
+ omna status
85
+ ```
86
+
87
+ The installer has no privileged step of its own: it installs `uv` if you do not have it, runs the
88
+ `uv tool install` above, and calls `omna init`. `omna init` is the only part that asks for `sudo`,
89
+ it prints the exact commands before it runs them, and `omna uninstall` reverses all of it.
90
+
91
+ ### Or with Homebrew
92
+
93
+ ```sh
94
+ brew tap gaurjin/omna https://github.com/gaurjin/omna-plugin
95
+ brew install --cask omna
96
+ ```
97
+
98
+ This installs a single self-contained binary — Python, every dependency and the masking engine
99
+ baked in — signed with our Developer ID and notarized by Apple. Two commands rather than one
100
+ because the recipe lives in this repo instead of a separate tap; see
101
+ [docs/homebrew.md](docs/homebrew.md) for why, and [docs/signing.md](docs/signing.md) for how to
102
+ verify the signature yourself.
103
+
104
+ Run `omna uninstall` **before** `brew uninstall` — Homebrew cannot remove the encryption keys
105
+ Omna stores in your Keychain, and `omna uninstall` does.
106
+
107
+ `omna init` also wires **aider**, **Codex CLI**, **VS Code** (Copilot Chat and similar chat
108
+ extensions), and **Continue** automatically if each is already installed/configured — nothing is
109
+ added for a tool you don't have. Wire one later, or a tool you didn't have yet: `omna enable aider`
110
+ / `codex` / `continue`; `omna disable` reverts it. VS Code is Mac-only for now (`omna init`'s Mac
111
+ step); see [omna.dev/help/plugin-supported-tools](https://omna.dev/help/plugin-supported-tools) for
112
+ the full list, including the tools that only offer an in-app setting (Cursor, Cline, Windsurf — one
113
+ line, typed once, inside the tool) and the ones not supported yet, with the real reason why.
114
+
115
+ On a Mac, `omna init` also does one more thing: it trusts a local certificate ("Omna Local
116
+ Certificate Authority") and points the system proxy at Omna via a PAC file that names only AI
117
+ hostnames, so apps that don't use `ANTHROPIC_BASE_URL`/`OPENAI_BASE_URL` (a desktop chat app, a
118
+ browser) are masked too — not just Claude Code. That's one `sudo` password prompt, printed before
119
+ it runs. Pass `--no-system` to skip it and wire Claude Code only. `omna uninstall` reverts it.
120
+
121
+ On a Mac, `omna init` also starts a menu-bar icon (`omna menubar`) showing live status — secrets
122
+ kept off the wire, personal values tokenised, requests masked, what's covered, and masking
123
+ overhead in ms, each on its own line. Its top line is the toggle itself (click, or the native
124
+ checkmark, to pause/resume masking), and it's the only thing Omna adds to System Settings → Login
125
+ Items: the masking proxy has no login item of its own, the menu-bar process supervises it.
126
+ Quitting the icon only stops that one process: relaunch it any time from Spotlight/Launchpad as
127
+ **Omna Plugin** (a small `.app` at `/Applications/Omna Plugin.app`, kept distinct from the native
128
+ Omna Mac app so the two never collide — though it now shares that app's icon, so look for the name
129
+ if you have both installed), or turn on its **Launch at Login** toggle so it comes back on its own
130
+ after every reboot or log-out. Its **Keep Local Reports** toggle turns the receipt log off entirely
131
+ (not just hides it) for anyone who doesn't want even anonymous counts kept on their machine —
132
+ `omna status`/`omna report`/`omna log` still run, they just have nothing new to show while it's off.
133
+
134
+ Cursor and Cline only offer an in-app setting (paste `http://127.0.0.1:7788` / `/v1` — see the docs
135
+ link above for the exact menu path). Any custom SDK app uses the same address, by hand:
136
+
137
+ ```sh
138
+ export ANTHROPIC_BASE_URL=http://127.0.0.1:7788 # Anthropic SDK
139
+ export OPENAI_BASE_URL=http://127.0.0.1:7788/v1 # OpenAI SDK, Cursor BYOK
140
+ ```
141
+
142
+ ## What is covered
143
+
144
+ | Surface | How | Turn it off |
145
+ |---|---|---|
146
+ | 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` |
147
+ | Cursor, Cline, Windsurf | one in-app setting, typed once — [full instructions](https://omna.dev/help/plugin-supported-tools) | unset it in the tool |
148
+ | Custom SDK apps | `ANTHROPIC_BASE_URL`/`OPENAI_BASE_URL` set by hand | unset the env var |
149
+ | 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 |
150
+ | Desktop AI apps that honour the system proxy | same system proxy as browsers | same as above |
151
+ | 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) |
152
+ | Any specific app, at any layer | `omna bypass app NAME` — tunnelled through untouched, still receipted | `omna mask app NAME` |
153
+
154
+ On a chat website, Omna masks what **you** send (your messages, pastes and files). The site's own
155
+ security checks (Cloudflare's robot check, ChatGPT's anti-abuse check) and its own usage and error
156
+ reports are forwarded byte-for-byte and receipted as `passthrough-security-check` /
157
+ `passthrough-site-report`. Editing them got people signed out of claude.ai, so Omna never does.
158
+
159
+ **Attached files** are masked inside and stay the same kind of file: text, `.env` and code files,
160
+ Word `.docx`, Excel `.xlsx`, LibreOffice `.odt` / `.ods` and `.rtf` (only the text changes;
161
+ formatting, styles and images are kept). PDFs, old `.doc` / `.xls`, PowerPoint and images cannot be
162
+ masked in place by a network proxy, so they go as they are and `omna log` says
163
+ `file NOT masked (pdf)`. (Omna's Chrome extension masks PDFs and screenshots on the AI websites.)
164
+
165
+ It makes no difference how a file or text gets into the chat box — the + button, drag and drop,
166
+ or a paste (some sites turn a long paste into a file) — and Perplexity's uploads to its own Amazon
167
+ storage are covered too. A value Omna finds once is masked everywhere it appears in that message.
168
+
169
+ ## Try it in 30 seconds
170
+
171
+ ```sh
172
+ omna mask "email jane.doe@example.com, key AKIAIOSFODNN7EXAMPLE, phone 212-555-0199"
173
+ # email [EMAIL_1], key [SECRET_AWS_KEY_1], phone [PHONE_1]
174
+
175
+ omna mask --realistic "email jane.doe@example.com, key AKIAIOSFODNN7EXAMPLE, phone 212-555-0199"
176
+ # email robert.jones@example.org, key [SECRET_AWS_KEY_N], phone 415-555-0128
177
+ # ...the key stays a label on purpose. See "Two masking styles" below.
178
+
179
+ omna log # one line per request: time, route, status, ms, what was masked (or "file NOT masked")
180
+ omna log --verify
181
+ ```
182
+
183
+ ## Commands
184
+
185
+ | Command | What it does |
186
+ |---|---|
187
+ | `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). |
188
+ | `omna stop` / `omna ensure` | Stop the background proxy / start it if it is not running (the Claude Code hook calls this). |
189
+ | `omna status` | Running? Claude Code wired? Receipts today. |
190
+ | `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, **Restore Real Values in Browser** or **Use Realistic Fake Values in Browser**, or uninstall. Both take effect on the next request — no restart. |
191
+ | `omna log [-n 20] [--verify] [--json]` | Local receipts. `--verify` checks the hash chain. |
192
+ | `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. |
193
+ | `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. |
194
+ | `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. |
195
+ | `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. |
196
+ | `omna mask [TEXT or -]` | Mask a string or stdin. |
197
+ | `omna style [tokens\|realistic]` | How a masked value is written: a numbered token (the default, every door) or a realistic fake value (your browser only — refused for anything that writes files). Prints what changed and which doors it affects. |
198
+ | `omna allow VALUE` | Never mask this exact value again (false positive). |
199
+ | `omna forget` | Wipe the token registry and its encryption key (tokens renumber). |
200
+ | `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. |
201
+ | `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. |
202
+ | `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. |
203
+ | `omna mappings` | Open the Mappings Review screen — every value Omna has masked, real values hidden until you click Reveal. Same token as the dashboard. |
204
+ | `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. |
205
+ | `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. |
206
+ | `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). |
207
+ | `omna capture app NAME` | Stage 3: deep-capture an app that ignores the system proxy, via its own signed network extension. |
208
+ | `omna hosts` / `omna hosts add HOST` / `omna hosts remove HOST` | Show, or add/remove, the hostnames the system proxy treats as AI traffic. |
209
+
210
+ ## Speed
211
+
212
+ Measured on a MacBook Air M5 with Claude Code 2.1.274, a 4-request task (read a file, write a file, answer):
213
+
214
+ | | direct | through omna |
215
+ |---|---|---|
216
+ | wall time | 8.8 s | 9.8 s |
217
+ | fast masking, 68 KB request body | | **16 ms** |
218
+
219
+ Fast masking (rules + checksums, the default) is the 16 ms. Smart masking (`--smart`, which adds the
220
+ on-device Contextual model for prose names) is slower and off by default — it is a deliberate choice you
221
+ make, not a surprise you discover.
222
+
223
+ `omna status` reports the real average for **your** machine and your traffic
224
+ (`avg_mask_ms_this_run`), so you never have to take this table's word for it.
225
+
226
+ The proxy relays the stream as it arrives (pings included), forwards `anthropic-beta`, `anthropic-version`,
227
+ `cache_control` and the `system` array untouched, and forwards provider errors unmodified, per Claude Code's
228
+ [gateway compatibility guide](https://code.claude.com/docs/en/llm-gateway-protocol).
229
+
230
+ ## What is stored on your machine
231
+
232
+ | File (`~/.omna/`) | Contents | Permissions |
233
+ |---|---|---|
234
+ | `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** |
235
+ | `receipts.jsonl` | one hash-chained line per request: route, status, ms, counts per entity. No values. | 0600 |
236
+ | `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. | |
237
+ | `proxy.log`, `omna.pid` | background-process housekeeping | |
238
+ | `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 |
239
+ | `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 |
240
+ | `dashboard.token` | the local secret the dashboard asks for. | 0600 |
241
+
242
+ ### What Omna sends, and to whom
243
+
244
+ Nothing, to us. The plugin makes exactly one kind of outbound connection: the request you were
245
+ already making, to the AI provider you were already using, with your own key.
246
+
247
+ There is no analytics call, no crash reporter phoning home, no licence check, no update ping.
248
+ When something crashes, it is written to `~/.omna/crashes.jsonl` on your machine and stays there —
249
+ `omna crash` shows you the exact bytes, and `omna crash --send` opens a pre-filled GitHub issue
250
+ that **you** submit, or close. That is a deliberate difference from tools that ship an opt-in
251
+ crash reporter: an opt-in promise is something you have to trust, and a file on your disk is
252
+ something you can read.
253
+
254
+ ### Where the real values live, and what protects them
255
+
256
+ `registry.json` is the only file Omna writes that contains real values, so it is
257
+ the only one that needs more than file permissions.
258
+
259
+ It is sealed with **AES-256-GCM**. The key is 32 random bytes kept in your
260
+ **macOS Keychain** — never on disk next to the file it opens, and never sent
261
+ anywhere. GCM also authenticates, so a file that someone edited fails to open
262
+ instead of quietly handing back changed mappings.
263
+
264
+ - `omna status` always tells you which state you are in, in plain words.
265
+ - If your machine has no Keychain (Linux, CI, a headless box), the registry
266
+ stays as it was before: plaintext at `0600`. Omna says so loudly rather than
267
+ refusing to run — and on that kind of machine, turn on full-disk encryption.
268
+ - `OMNA_REGISTRY_ENCRYPTION=off` opts out entirely and touches the Keychain not
269
+ at all.
270
+ - If the key is ever lost, Omna keeps masking but stops writing, so it can never
271
+ overwrite mappings it cannot read. `omna forget` starts cleanly from scratch.
272
+ - `omna forget` and `omna uninstall` remove the key along with the file.
273
+
274
+ ## Company reports, without a server
275
+
276
+ A company with 100 laptops does not want 100 separate reports, and Omna does not
277
+ want their prompts. So the rollup is done with files, not a backend:
278
+
279
+ 1. IT tags each machine at install time (or later with `omna enroll`):
280
+ `curl -fsSL https://omna.dev/cli/install.sh | sh -s -- --org "Acme Inc" --dept engineering`
281
+ 2. Each machine writes a counts-only file: `omna report --export machine.json`
282
+ 3. Whoever is collecting them runs `omna report --merge *.json` and gets a
283
+ company total plus a per-department breakdown.
284
+
285
+ **Nothing is uploaded.** There is no Omna server in this picture at all — the
286
+ files move however that company already moves files. The export is an
287
+ allowlist of number fields, so a new field added to the report later cannot
288
+ quietly start riding along. It deliberately leaves out your home path (which
289
+ carries your username) and which apps you run. The device id is random and
290
+ local; it exists so two files can be told apart and a duplicate can be spotted,
291
+ and `omna enroll --forget` deletes it.
292
+
293
+ ## Two masking styles: numbered tokens, or realistic fake values
294
+
295
+ Omna can write a masked value in one of two ways. **Numbered tokens are the
296
+ default, everywhere**, and most people never need to change it.
297
+
298
+ | | what the AI receives | when it suits |
299
+ |---|---|---|
300
+ | **tokens** (default) | `email [EMAIL_N]` | anything that writes files, and anywhere you want a mistake to be obvious |
301
+ | **realistic** | `email robert.jones@example.org` | chat and prose, where ordinary-looking text gets you a better answer |
302
+
303
+ ```sh
304
+ omna style # what is in force, door by door
305
+ omna style realistic # switch on realistic values (browser only — see below)
306
+ omna style tokens # back to the default
307
+
308
+ omna mask --realistic "email jane.doe@acme.com, key AKIAIOSFODNN7EXAMPLE"
309
+ # email michael.jones63@example.org, key [SECRET_AWS_KEY_N]
310
+ ```
311
+
312
+ ### Why both exist, and why the realistic style is refused for coding tools
313
+
314
+ A numbered token fails **loudly**. If a restore ever breaks, a leftover
315
+ `[EMAIL_N]` sitting in your code is visibly wrong and somebody notices
316
+ immediately.
317
+
318
+ A realistic fake value fails **silently**. A leftover `robert.jones@example.org`
319
+ looks like ordinary data. Nobody notices, and it gets committed to a repository
320
+ for ever.
321
+
322
+ So this is a safety trade, not a preference — and Omna decides it for you where
323
+ the stakes are one-sided:
324
+
325
+ | Door | What it reaches | Realistic values |
326
+ |---|---|---|
327
+ | API door | Claude Code, aider, Codex, Continue, VS Code | **refused** — they all write files |
328
+ | System door | your browser and the chat websites | allowed |
329
+ | Deep door | a named desktop app, captured individually | **refused** — that list can hold an editor |
330
+ | Omna's Chrome extension | the browser, masked before the request leaves it | allowed, unless the system door is also on — see below |
331
+
332
+ A refusal is never quiet: `omna style realistic` prints which doors kept
333
+ numbered tokens and why, `omna status` shows the style per door, and every
334
+ receipt records which style that request actually used.
335
+
336
+ ### The Chrome extension, and the one case it keeps numbered tokens
337
+
338
+ If you also run Omna's Chrome extension, it masks inside the browser before the
339
+ request ever leaves it, so it has to be told which style to use. It asks the
340
+ plugin — on the same 60-second check-in it was already making — and does what
341
+ it is told. It never decides for itself: with the plugin not running, or
342
+ answering anything other than "realistic", it uses numbered tokens.
343
+
344
+ There is one case where the plugin answers "tokens" even though you asked for
345
+ realistic: **while the system door is also on.** Both halves would then mask the
346
+ same browser request, one after the other, and the second pass cannot tell a
347
+ stand-in from real data. A numbered token has brackets, so it is walked straight
348
+ past and comes back to you unchanged. A realistic fake value looks exactly like
349
+ a real e-mail — that is the whole point of it — so it would be masked a second
350
+ time and nothing could put your real value back. `omna style` prints this, in
351
+ those words, on the `browser ext` line.
352
+
353
+ **Secrets are never given a fake value, under any style.** An API key, a token,
354
+ a password or a private key always becomes a numbered `[SECRET_…]` label. A fake
355
+ API key that looked real is the worst thing this program could hand you.
356
+
357
+ ### What the realistic style guarantees
358
+
359
+ - **Stability** — the same real value always becomes the same fake value, on
360
+ every request and after a restart. Otherwise the provider's prompt cache is
361
+ thrown away and the AI sees the conversation change under it between turns.
362
+ - **Uniqueness** — a generated value is checked against the text being masked
363
+ and against every real value Omna already knows, and regenerated on a clash,
364
+ so restoring can never put back the wrong thing. If no unique value can be
365
+ made, Omna falls back to the numbered token.
366
+ - **Reversibility** — real values come back in replies, in streamed replies and
367
+ inside tool-call arguments, exactly as they do with tokens.
368
+ - **Nothing invented can belong to anybody** — e-mail addresses use the reserved
369
+ `example.org` domains, phone numbers the reserved `555-01xx` range, IP
370
+ addresses the documentation ranges, and Social Security numbers an area
371
+ number that is never issued. A generated card number is deliberately left
372
+ failing its checksum: one that passed might be somebody's real card.
373
+
374
+ ## Reviewing what was masked
375
+
376
+ `omna mappings` opens a screen showing every personal value Omna has masked on
377
+ this machine — behind the same `~/.omna/dashboard.token` as the dashboard,
378
+ never a second password to manage. It shows more than "what was masked":
379
+ which layer caught it (L1 rules, L2 secrets, L3 the contextual model), whether
380
+ a checksum actually validated it, which style it was last written in (tokens
381
+ or realistic — a value masked before the two-style feature existed says
382
+ "unknown", never a guessed default), how many times it has been sent, when
383
+ last, and to which provider and app.
384
+
385
+ Real values are never on the page until you ask. Each row is hidden by
386
+ default; click **reveal** to fetch just that one, or **reveal all** for every
387
+ row on the current page. Nothing is embedded in the page source ahead of
388
+ time, so a screenshot or a browser extension reading the DOM sees nothing
389
+ until you click. Search, filter by entity type, sort any column, and
390
+ paginate are all built in.
391
+
392
+ **Secrets never appear here.** They are memory-only from the moment they are
393
+ caught and are never written to the registry — the page says so, rather than
394
+ silently omitting them.
395
+
396
+ **Delete** removes one mapping — its token, its real value, and its
397
+ realistic-style fake together, so a "deleted" value can't keep resolving
398
+ through the fake path. The same real value gets a brand-new token or fake the
399
+ next time it's seen; the deleted one never comes back. **Clear all** is the
400
+ same as `omna forget` — it also resets your secret counters and your
401
+ encryption key.
402
+
403
+ ## Reading browser replies: real values or labels
404
+
405
+ **Use Realistic Fake Values in Browser** (menu bar, off by default) is the same
406
+ switch as `omna style realistic`, for people who would rather not open a
407
+ terminal. It changes only how a masked value is WRITTEN, in the browser.
408
+
409
+ **Restore Real Values in Browser** (menu bar, on by default) decides what you
410
+ read back in a browser reply: the real name, or the `[EMAIL_1]` label that was
411
+ actually sent. **Masking is not affected by it and is never optional** — this
412
+ only changes what comes back.
413
+
414
+ It is scoped to the browser on purpose. The coding tools always get real values
415
+ put back, because there a label reaching the tool breaks it: Claude Code would
416
+ write `[SECRET_AWS_KEY_1]` into your file instead of editing the real line.
417
+
418
+ If you also run the Chrome extension, the plugin steps aside and lets the
419
+ extension handle the browser reply. It has to: neither half knows the other's
420
+ list, so whoever restores second could swap in a different person's value. That
421
+ is true of the `[EMAIL_1]` labels, which both sides number independently, and
422
+ worse of realistic fake values, where two generators draw ordinary names out of
423
+ one small pool and the wrong substitution reads as perfectly correct data. The
424
+ extension tags the requests it handles so the plugin knows, per request, to
425
+ leave that reply alone — masking still happens either way.
426
+
427
+ ## What it refuses
428
+
429
+ A request to an inference path (`/v1/messages`, `/v1/chat/completions`, `/v1/responses`, …) whose body cannot be
430
+ parsed as JSON (compressed, malformed, wrong content type) is answered with `400 omna_refused` and never forwarded.
431
+ Non-inference uploads (`/v1/files`, audio) pass through unmasked and are marked `passthrough` in the receipt.
432
+ If the proxy is not running, the tool's requests fail to connect: nothing leaves unmasked.
433
+
434
+ ## Honest limits (v1)
435
+
436
+ - A pinned app (one that rejects our certificate on purpose, e.g. it checks the cert fingerprint itself) is
437
+ refused, never forwarded unmasked, and named: `omna status` shows `refused: AppName → host ×N (pinned)`
438
+ with the exact `omna bypass app "AppName"` command to let it through untouched instead.
439
+ - If the daemon goes down, what a browser does next depends on whether it already loaded the PAC file: one
440
+ that hasn't yet falls back to DIRECT (unmasked) until `launchd` restarts the daemon (seconds); one that
441
+ already cached the proxy address instead fails closed (a connection error, nothing loads) until then. This
442
+ Mac's `file://` PAC is not honoured by Safari/Chrome, so the PAC itself is served by the proxy — an honest
443
+ limit either way, not a fail-closed guarantee like the coding-CLI door.
444
+ - Firefox, Node, Python, Java, Ruby and Deno keep their own certificate lists and ignore the macOS
445
+ keychain, so each needs one line to trust the local certificate — all of them are in
446
+ [docs/certificate-trust.md](docs/certificate-trust.md). (Firefox: `about:config` →
447
+ `security.enterprise_roots.enabled` = `true`. Node: `NODE_EXTRA_CA_CERTS`, which `omna init`
448
+ already sets for VS Code.) Safari, Chrome, Edge, Brave, curl and Go need nothing.
449
+ - Fast masking (rules + checksums) runs by default. Prose names in free text need `--smart`, which loads a
450
+ local model and slows each request; it is off by default.
451
+ - Windows: no engine wheel yet (WSL works).
452
+ - A tool's own cloud features (for example Cursor tab-completion) never pass through a local proxy.
453
+ - Model thinking blocks are passed through untouched in both directions (the API requires it), so a name the
454
+ model mentions inside its thinking shows as a token there.
455
+ - Two engine quirks are worked around here and logged upstream: assignment-style secrets keep their variable
456
+ name outside the token (so `AWS_ACCESS_KEY_ID=[SECRET_GENERIC_SECRET_1]`, not one anonymous token), and a
457
+ secret span no longer swallows the newline after it. One known false positive remains: prose like
458
+ "authorization context: something" can be flagged as a generic secret.
459
+ - This masks secrets and PII. It does not make anything "compliant".
460
+ - No exceptions, not even for us: with Claude Code wired to this proxy, Claude reading this
461
+ README's own demo values (the fake email/AWS key/phone two sections up) sees them as tokens
462
+ too, same as it would see yours. That's the proxy working as designed, not a bug — it is a
463
+ side effect worth knowing if you develop this repo with Claude Code pointed at itself.
464
+
465
+ ## Free vs paid
466
+
467
+ Everything in this repository is free. The paid layer, for teams, is the proof: a signed weekly evidence pack
468
+ built from these receipts, per-machine coverage, and org-wide rulesets. See a sample at
469
+ [omna.dev/sample-report](https://omna.dev/sample-report).
470
+
471
+ ## Development
472
+
473
+ ```sh
474
+ uv venv .venv && uv pip install -p .venv/bin/python -e '.[dev]'
475
+ .venv/bin/pytest -q # 205 tests, ~7 s (fake upstream, no network)
476
+ .venv/bin/omna start # foreground, then: ANTHROPIC_BASE_URL=http://127.0.0.1:7788 claude
477
+ ```
478
+
479
+ MIT for this shell. The engine wheel (`omna-pii-mask`) is binary-only.