code-airlock 1.3.0__tar.gz → 1.4.0__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 (23) hide show
  1. {code_airlock-1.3.0/src/code_airlock.egg-info → code_airlock-1.4.0}/PKG-INFO +400 -395
  2. {code_airlock-1.3.0 → code_airlock-1.4.0}/README.md +360 -356
  3. {code_airlock-1.3.0 → code_airlock-1.4.0}/pyproject.toml +7 -6
  4. {code_airlock-1.3.0 → code_airlock-1.4.0}/setup.cfg +4 -4
  5. {code_airlock-1.3.0 → code_airlock-1.4.0}/src/code_airlock/__init__.py +1 -1
  6. {code_airlock-1.3.0 → code_airlock-1.4.0}/src/code_airlock/__main__.py +1 -1
  7. {code_airlock-1.3.0 → code_airlock-1.4.0}/src/code_airlock/engine.py +217 -219
  8. code_airlock-1.4.0/src/code_airlock/onnx_ner.py +93 -0
  9. {code_airlock-1.3.0 → code_airlock-1.4.0/src/code_airlock.egg-info}/PKG-INFO +400 -395
  10. {code_airlock-1.3.0 → code_airlock-1.4.0}/src/code_airlock.egg-info/SOURCES.txt +1 -0
  11. {code_airlock-1.3.0 → code_airlock-1.4.0}/src/code_airlock.egg-info/requires.txt +6 -5
  12. {code_airlock-1.3.0 → code_airlock-1.4.0}/LICENSE +0 -0
  13. {code_airlock-1.3.0 → code_airlock-1.4.0}/src/code_airlock/config.py +0 -0
  14. {code_airlock-1.3.0 → code_airlock-1.4.0}/src/code_airlock/executor.py +0 -0
  15. {code_airlock-1.3.0 → code_airlock-1.4.0}/src/code_airlock/mcp_server.py +0 -0
  16. {code_airlock-1.3.0 → code_airlock-1.4.0}/src/code_airlock/proxy.py +0 -0
  17. {code_airlock-1.3.0 → code_airlock-1.4.0}/src/code_airlock/sanitizer.py +0 -0
  18. {code_airlock-1.3.0 → code_airlock-1.4.0}/src/code_airlock/scanner.py +0 -0
  19. {code_airlock-1.3.0 → code_airlock-1.4.0}/src/code_airlock.egg-info/dependency_links.txt +0 -0
  20. {code_airlock-1.3.0 → code_airlock-1.4.0}/src/code_airlock.egg-info/entry_points.txt +0 -0
  21. {code_airlock-1.3.0 → code_airlock-1.4.0}/src/code_airlock.egg-info/top_level.txt +0 -0
  22. {code_airlock-1.3.0 → code_airlock-1.4.0}/tests/test_mcp.py +0 -0
  23. {code_airlock-1.3.0 → code_airlock-1.4.0}/tests/test_proxy.py +0 -0
@@ -1,395 +1,400 @@
1
- Metadata-Version: 2.4
2
- Name: code-airlock
3
- Version: 1.3.0
4
- Summary: Low-latency OpenAI-compatible PII redaction gateway and FastMCP server for coding agents
5
- Author: Sarthak Maurya
6
- License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/sarthexe/code-airlock
8
- Project-URL: Repository, https://github.com/sarthexe/code-airlock
9
- Project-URL: Issues, https://github.com/sarthexe/code-airlock/issues
10
- Project-URL: Dataset, https://huggingface.co/datasets/sarthax/pii-airlock-bench
11
- Keywords: pii,privacy,secrets,dlp,mcp,llm,security
12
- Classifier: Development Status :: 4 - Beta
13
- Classifier: Intended Audience :: Developers
14
- Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.10
16
- Classifier: Programming Language :: Python :: 3.11
17
- Classifier: Programming Language :: Python :: 3.12
18
- Classifier: Programming Language :: Python :: 3.13
19
- Classifier: Topic :: Security
20
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
- Requires-Python: >=3.10
22
- Description-Content-Type: text/markdown
23
- License-File: LICENSE
24
- Requires-Dist: gliner>=0.2.13
25
- Requires-Dist: torch>=2.0.0
26
- Requires-Dist: numpy>=1.24.0
27
- Requires-Dist: mcp<2,>=1.0
28
- Requires-Dist: fastapi>=0.110
29
- Requires-Dist: uvicorn>=0.27
30
- Requires-Dist: httpx>=0.27
31
- Provides-Extra: dev
32
- Requires-Dist: pytest>=8.0; extra == "dev"
33
- Requires-Dist: build>=1.2; extra == "dev"
34
- Requires-Dist: twine>=5.0; extra == "dev"
35
- Provides-Extra: benchmark
36
- Requires-Dist: datasets>=2.18; extra == "benchmark"
37
- Requires-Dist: huggingface_hub>=0.23; extra == "benchmark"
38
- Dynamic: license-file
39
-
40
- # 🛡️ Code-Airlock
41
-
42
- **Low-Latency AI Privacy, Secret/PII Redaction & Streaming Rehydration Gateway (`OpenAI Proxy` + `FastMCP Server`)**
43
-
44
- `code-airlock` is a zero-trust privacy layer for LLMs and agentic coding harnesses. It intercepts sensitive credentials, financial identifiers, and personal data before they leave your machine, replaces them with deterministic session tokens (`<API_KEY_1>`, `<PERSON_1>`), and seamlessly rehydrates them back into real-time Server-Sent Events (SSE) token streams or local file writes.
45
-
46
- * **Dual Deployment Modes:** Run as a drop-in **OpenAI-compatible `/v1/chat/completions` reverse proxy** or as a **Model Context Protocol (MCP) server** (`stdio`) for OpenCode, Cursor, and Claude Code.
47
- * **2-Stage Hybrid Cascade:** Combines sub-millisecond deterministic regex + mathematical checksums (**Luhn, Verhoeff $D_5$, ISO 7064 MOD-97**) with contextual neural NER (`urchade/gliner_small-v2.1`) and a curated developer allowlist.
48
- * **Deterministic Secret Coverage:** Stage 1 also captures inline connection-string passwords and `PASSWORD` / `SECRET` / `ACCESS_TOKEN` / `API_KEY` assignments, excluding placeholders, booleans, function calls, and existing vault tokens.
49
- * **Git & CI Guardrails:** `code-airlock scan --staged` catches secrets in staged files at sub-millisecond Stage 1 speed, masks every value in output, and installs a `.git/hooks/pre-commit` gate via `init --pre-commit`.
50
- * **Safe Agent Execution:** `airlock_exec` injects real `.env` values into a guarded subprocess (`shell=False`) and scrubs both stdout and stderr before returning them to the model.
51
- * **Project-Aware Rules:** `.airlock.toml` adds team allowlist terms and custom Stage-1 regexes such as `EMPLOYEE_ID = 'EMP-[0-9]{6}'`.
52
- * **Zero-Delay Streaming Rehydration:** Stateful sliding-window SSE parser reassembles split tokens across arbitrary 2-byte network chunks without delaying code comparison or bitwise operators (`<`, `<<`, `template <typename T>`).
53
- * **Minimalist Systems Footprint:** ~800 lines of core runtime code (`engine.py`, `sanitizer.py`, `proxy.py`, `mcp_server.py`, `scanner.py`, `config.py`, `executor.py`) plus a deterministic 2,000-row benchmark harness.
54
-
55
- ---
56
-
57
- ## 🏗️ System Architecture
58
-
59
- ```mermaid
60
- flowchart TB
61
- classDef client fill:#1e293b,stroke:#64748b,stroke-width:2px,color:#f8fafc
62
- classDef stage1 fill:#064e3b,stroke:#10b981,stroke-width:2px,color:#ecfdf5
63
- classDef stage2 fill:#1e3a8a,stroke:#3b82f6,stroke-width:2px,color:#eff6ff
64
- classDef merger fill:#312e81,stroke:#8b5cf6,stroke-width:2px,color:#f5f3ff
65
- classDef vault fill:#7c2d12,stroke:#f97316,stroke-width:2px,color:#fff7ed
66
- classDef stream fill:#4c1d95,stroke:#a855f7,stroke-width:2px,color:#faf5ff
67
-
68
- Client(["🖥️ Client SDK / Agent Harness<br/>(OpenAI SDK, OpenCode, Cursor, Claude Code)"]):::client
69
-
70
- subgraph Airlock["🛡️ CODE-AIRLOCK (proxy.py / mcp_server.py)"]
71
- direction TB
72
- subgraph Cascade["engine.py — 2-Stage Hybrid Cascade"]
73
- S1["⚡ Stage 1: Deterministic Fast-Pass (~0.44ms)<br/>• Regex: API_KEY, AUTH_TOKEN, EMAIL, PHONE, IPv4<br/>• Checksums: Luhn (Card), Verhoeff (ID), MOD-97 (IBAN)"]:::stage1
74
- S2["🧠 Stage 2: Guarded Neural NER (GLiNER-small)<br/>• Entities: PERSON, ORGANIZATION, LOCATION<br/>• Guards: 93-term TECH_ALLOWLIST + Code/Pronoun Filter"]:::stage2
75
- Merger{"⚖️ Span Conflict Resolver<br/>Stage 1 Overrides Stage 2<br/>Longest Span Wins"}:::merger
76
- S1 & S2 --> Merger
77
- end
78
-
79
- Vault[("🗄️ Ephemeral Session Vault<br/>sanitizer.py (VaultSession)<br/>Right-to-Left Tagging")]:::vault
80
- Rehydrator["🪟 StreamRehydrator<br/>Stateful Sliding-Window SSE & File Parser"]:::stream
81
-
82
- Merger --> Vault
83
- Vault -.->|"O(1) Token Lookup"| Rehydrator
84
- end
85
-
86
- LLM(["☁️ Upstream LLM Provider<br/>*Sees Only &lt;ENTITY_n&gt; Tokens*"]):::client
87
-
88
- Client -->|"1. Raw Prompt / File Read"| Cascade
89
- Vault -->|"2. Sanitized Payload"| LLM
90
- LLM -->|"3. Fragmented SSE Stream / Tool Patch"| Rehydrator
91
- Rehydrator -->|"4. Restored Plaintext"| Client
92
- ```
93
-
94
- ---
95
-
96
- ## 📊 Gold-Set Benchmark (`benchmarks/gold_set.jsonl`)
97
-
98
- Evaluated on **285 developer prompts** (sampled from `CodeAlpaca_20K` and `OpenCodeInstruct` with injected `.env` blocks, ticket escalations, billing metadata, and C++ template syntax) totaling **744 ground-truth PII spans** across 11 categories:
99
-
100
- | Metric / Entity Category | Strict (P / R / F1) | Partial (P / R / F1) | Latency / Verification |
101
- | :--- | :---: | :---: | :--- |
102
- | **Overall Benchmark (744 Spans)** | **96.5% / 99.1% / 97.7%** | **97.0% / 99.6% / 98.3%** | **285 / 285 SSE Round-Trip Pass** |
103
- | **Stage 1 Latency (Deterministic)** | — | — | **0.44 ms (p50)** / 0.78 ms (p95) |
104
- | **Total Cascade Latency (CPU/GPU)** | — | — | **14.6–42.5 ms (p50)** |
105
- | `API_KEY` *(OpenAI, AWS, GitHub, Slack, Anthropic)* | 100 / 100 / **100%** | 100 / 100 / **100%** | Prefix + length/charset bounds |
106
- | `AUTH_TOKEN` *(JWT, Bearer, PEM Private Keys)* | 100 / 100 / **100%** | 100 / 100 / **100%** | 3-part Base64URL + PEM headers |
107
- | `CREDIT_CARD` *(13–19 digits)* | 100 / 100 / **100%** | 100 / 100 / **100%** | Pure-Python **Luhn Checksum** |
108
- | `GOVT_ID` *(SSN, PAN, 12-Digit National ID)* | 100 / 100 / **100%** | 100 / 100 / **100%** | **Verhoeff $D_5$ Checksum** |
109
- | `IBAN_CRYPTO` *(IBAN, ETH `0x...`)* | 100 / 100 / **100%** | 100 / 100 / **100%** | **ISO 7064 MOD-97** Checksum |
110
- | `EMAIL` & `PHONE` | 100 / 100 / **100%** | 100 / 100 / **100%** | Negative lookbehind for order IDs |
111
- | `IP_ADDRESS` *(IPv4)* | 94 / 100 / **97%** | 94 / 100 / **97%** | Octet range `0..255` (rejects `.300`) |
112
- | `PERSON` | 99 / 99 / **99%** | 99 / 99 / **99%** | Pronoun + code-identifier stopword guard |
113
- | `ORGANIZATION` | 89 / 95 / **92%** | 91 / 97 / **94%** | Filtered via 93-term `TECH_ALLOWLIST` |
114
- | `LOCATION` | 88 / 98 / **93%** | 90 / 100 / **95%** | Adjacent comma-span coalescing |
115
-
116
- * **Hard-Negative Suite:** **61/61 Passed** (100% rejection of non-Luhn 16-digit order IDs, Verhoeff-invalid 12-digit numbers, MOD-97-invalid IBANs, and `v192.168.1.300` version strings).
117
- * **Tech Allowlist Check:** **30/30 Passed** (`FastAPI`, `Docker`, `Kubernetes`, `Redis`, `Postgres`, `AWS`, `Heroku`, etc. produce zero false positives).
118
-
119
- ---
120
-
121
- ## 🧪 Unified 2,000-Row Benchmark Matrix
122
-
123
- `benchmarks/build_benchmarks.py` deterministically generates four stratified splits into the git-ignored `benchmarks/` directory (`seed=42`). `benchmarks/benchmark.py` evaluates them and writes [`benchmarks/BENCHMARK_RESULTS.md`](benchmarks/BENCHMARK_RESULTS.md).
124
-
125
- | Split | Rows | Positive | Clean / Hard-neg | Ground-truth spans |
126
- | :--- | ---: | ---: | ---: | ---: |
127
- | `dev_prompts` | 1,000 | 600 | 300 / 100 | 2,400 |
128
- | `git_leaks` | 500 | 350 | 0 / 150 | 1,400 |
129
- | `mcp_configs` | 250 | 200 | 50 / 0 | 800 |
130
- | `adversarial_sse` | 250 | 250 | 0 / 0 | 1,217 |
131
- | **Total** | **2,000** | **1,400** | **350 / 250** | **5,817** |
132
-
133
- Reference result (strict exact-span matching, `code-airlock` v1.3.0):
134
-
135
- | Split | Strict P / R / F1 | Hard-neg | Clean 0-FP | SSE 1/2/7-byte |
136
- | :--- | :---: | :---: | :---: | :---: |
137
- | `dev_prompts` | 100% / 100% / 100% | 100/100 | 300/300 | 1000/1000 |
138
- | `git_leaks` | 100% / 100% / 100% | 150/150 | — | 500/500 |
139
- | `mcp_configs` | 100% / 100% / 100% | — | 50/50 | 250/250 |
140
- | `adversarial_sse` | 100% / 100% / 100% | — | — | 250/250 |
141
- | **Combined** | **100% / 100% / 100%** | **250/250** | **350/350** | **2000/2000** |
142
-
143
- - Latency: **0.232 ms** p50 / 0.428 ms p95 Stage 1; **32.354 ms** p50 / 43.966 ms p95 Stage 2; **32.704 ms** p50 / 44.112 ms p95 total cascade.
144
- - Per-entity support: `API_KEY` 756, `AUTH_TOKEN` 467, `CREDIT_CARD` 440, `EMAIL` 754, `PHONE` 465, `IP_ADDRESS` 442, `GOVT_ID` 465, `IBAN_CRYPTO` 465, `PERSON` 729, `ORGANIZATION` 417, `LOCATION` 417. Every entity is at **100% P/R/F1**.
145
- - Invariants: MCP JSON parse **100/100**, JSON round-trip **100/100**, adversarial SSE **2,000/2,000**, hard negatives **250/250**, clean rows **350/350 with zero false positives**.
146
-
147
- These are **synthetic, template-composed fixtures**, so the perfect headline is a regression/invariant result, not a claim of real-world accuracy. The older real-world suites (`benchmarks/gold_set.jsonl`: 96.7/99.1/97.9; Gitleaks artifacts: 100/88.5/93.9) remain the honest field tests.
148
-
149
- ### 🤗 Published Dataset
150
-
151
- One command publishes all four splits and the auto-generated card:
152
-
153
- ```bash
154
- python benchmarks/export_hf.py --repo-id sarthexe/pii-airlock-bench
155
- ```
156
-
157
- Dataset: [**sarthax/pii-airlock-bench**](https://huggingface.co/datasets/sarthax/pii-airlock-bench) (the token owner namespace is resolved automatically).
158
-
159
- ```python
160
- from datasets import load_dataset
161
- ds = load_dataset("sarthax/pii-airlock-bench")
162
- ```
163
-
164
- ---
165
-
166
- ## 🚀 Quickstart & MCP Setup
167
-
168
- ### Option 1: Zero-Install via `uvx` (Recommended)
169
-
170
- No cloning, manual virtual environments, or path debugging. Anyone with Astral's [`uv`](https://github.com/astral-sh/uv) can run `code-airlock` directly from PyPI:
171
-
172
- #### 🤖 Claude Code (CLI)
173
- ```bash
174
- claude mcp add code-airlock -- uvx code-airlock
175
- ```
176
-
177
- #### 💻 OpenCode (`opencode.json`)
178
- ```json
179
- {
180
- "$schema": "https://opencode.ai/config.json",
181
- "mcp": {
182
- "code-airlock": {
183
- "type": "local",
184
- "command": ["uvx", "code-airlock"],
185
- "enabled": true
186
- }
187
- }
188
- }
189
- ```
190
-
191
- #### 🖱️ Cursor (`.cursor/mcp.json`)
192
- ```json
193
- {
194
- "mcpServers": {
195
- "code-airlock": {
196
- "command": "uvx",
197
- "args": ["code-airlock"]
198
- }
199
- }
200
- }
201
- ```
202
-
203
- #### 🖥️ Claude Desktop (`claude_desktop_config.json`) & Windsurf (`~/.codeium/windsurf/mcp_config.json`)
204
- ```json
205
- {
206
- "mcpServers": {
207
- "code-airlock": {
208
- "command": "uvx",
209
- "args": ["code-airlock"]
210
- }
211
- }
212
- }
213
- ```
214
-
215
- *(You can also run directly from source via `uvx --from git+https://github.com/sarthexe/code-airlock code-airlock`)*.
216
-
217
- For a local editable checkout, use the package entrypoint instead of an
218
- absolute path:
219
-
220
- ```json
221
- {
222
- "mcpServers": {
223
- "code-airlock": {
224
- "command": "python",
225
- "args": ["-m", "code_airlock.mcp_server"]
226
- }
227
- }
228
- }
229
- ```
230
-
231
- `python -m code_airlock` starts the same MCP server.
232
-
233
- ---
234
-
235
- ### Option 2: Local Development / Manual Git Clone
236
-
237
- ```bash
238
- git clone https://github.com/sarthexe/code-airlock.git
239
- cd code-airlock
240
- pip install -e ".[dev,benchmark]"
241
-
242
- # Test tools locally over stdio
243
- python -m code_airlock
244
-
245
- # Or run the explicit MCP entrypoint
246
- code-airlock-mcp
247
- ```
248
-
249
- ### Zero-Friction CLI
250
-
251
- ```bash
252
- python -m code_airlock doctor # model warmup + round-trip + MCP config
253
- python -m code_airlock init # add rules to .cursorrules / CLAUDE.md
254
- python -m code_airlock init --pre-commit # also install .git/hooks/pre-commit
255
- python -m code_airlock init --config # create a starter .airlock.toml
256
- python -m code_airlock scan --staged # fast Stage 1 scan of staged files
257
- python -m code_airlock scan --deep src/ # add GLiNER detection for a path
258
- python -m code_airlock proxy # start the HTTP proxy
259
- python -m code_airlock # start the MCP server (default)
260
- ```
261
-
262
- Installed entrypoints: `code-airlock-mcp`, `code-airlock-proxy`, and the
263
- backward-compatible `code-airlock` alias.
264
-
265
- `scan` skips binaries, files over 1 MB, `tests/`, and benchmark fixtures. It
266
- prints `file:line -> [CATEGORY] masked-preview` and exits `1` when secrets are
267
- found, so it works unchanged in CI and Git hooks. `--deep` enables Stage 2 NER;
268
- the default Stage 1 path is sub-millisecond. `init` is idempotent, and
269
- `init --pre-commit` installs a hook that runs `python -m code_airlock scan --staged`.
270
-
271
- ### 🧰 MCP Tools
272
-
273
- | Tool | Purpose |
274
- | :--- | :--- |
275
- | `airlock_read_file` | Read a bounded UTF-8 file through the vault; blocks `.ssh`, `.aws`, `.gnupg`, `.kube`, `/etc`, `System32`, `AIRLOCK_WORKSPACE` escapes, binaries, and files over 1 MB. |
276
- | `airlock_write_file` | Rehydrate locally and write; refuses to write if unresolved/hallucinated `<ENTITY_N>` tokens remain. |
277
- | `airlock_scrub_text` | Redact arbitrary in-memory text or terminal output; `use_ner=False` forces the fast Stage 1 path. |
278
- | `airlock_exec` | Run a guarded subprocess (`shell=False`), inject real `.env` values into its environment, and scrub stdout/stderr before returning. Blocks destructive executables and shells. |
279
- | `airlock_vault_status` | Return active token keys and per-category counts only; never raw values. |
280
- | `airlock_dehydrate` | Redact text and return session-stable tokens. |
281
- | `airlock_rehydrate` | Restore canonical, case-mangled, or whitespace-padded vault tokens. |
282
-
283
- All file tools resolve paths with `Path.expanduser().resolve()` and jail access
284
- to `AIRLOCK_WORKSPACE` when that variable is set. Rehydration is tolerant of
285
- `<api_key_1>` / `< API_KEY_1 >`, while the SSE `StreamRehydrator` remains
286
- zero-delay and exact.
287
-
288
- `airlock_exec` is designed for safe test runs:
289
-
290
- ```python
291
- airlock_exec(
292
- [sys.executable, "-c", "import os; print(os.environ['STRIPE_SECRET_KEY'])"],
293
- ".env",
294
- )
295
- # stdout: "<API_KEY_1>" ← the real key never reaches the model
296
- ```
297
-
298
- ### ⚙️ Project Config (`.airlock.toml`)
299
-
300
- Create a starter with `python -m code_airlock init --config`, or write one
301
- manually. `CascadeEngine` loads `.airlock.toml` from the current directory, or
302
- from `AIRLOCK_CONFIG` when set:
303
-
304
- ```toml
305
- [allowlist]
306
- terms = ["MyInternalApp", "staging.corp.local"]
307
-
308
- [custom_patterns]
309
- EMPLOYEE_ID = 'EMP-[0-9]{6}'
310
- INTERNAL_TICKET = 'JIRA-[0-9]{4,6}'
311
- ```
312
-
313
- - Allowlist terms are merged into the Stage 2 allowlist so they are not
314
- misclassified as `PERSON`, `ORGANIZATION`, or `LOCATION`.
315
- - Custom patterns run in Stage 1 alongside built-in rules and produce
316
- `<EMPLOYEE_ID_n>` / `<INTERNAL_TICKET_n>` vault tokens.
317
- - Invalid regexes and malformed labels are ignored rather than crashing the
318
- engine.
319
-
320
- ---
321
-
322
- ### Option 3: OpenAI-Compatible Reverse Proxy (`/v1/chat/completions`)
323
-
324
- Run as a transparent privacy gateway intercepting HTTP requests:
325
-
326
- ```bash
327
- # Run with built-in zero-leak mock upstream (offline validation)
328
- code-airlock-proxy
329
-
330
- # Or point to a live upstream provider (OpenAI, Ollama, OpenRouter, vLLM)
331
- export AIRLOCK_UPSTREAM_URL="https://api.openai.com"
332
- code-airlock-proxy
333
- ```
334
-
335
- The proxy binds to `127.0.0.1:8000` by default. Override with
336
- `AIRLOCK_HOST` / `AIRLOCK_PORT`.
337
-
338
- ---
339
-
340
- ## 🧠 Guiding Your Agent (`.cursorrules` / System Prompt)
341
-
342
- To ensure your coding agent actually leverages the airlock tools instead of raw file reads, include this rule in your `.cursorrules`, `CLAUDE.md`, or system instructions:
343
-
344
- ```markdown
345
- # Code-Airlock Agent Guardrails
346
- 1. When reading `.env`, configuration, or log files, NEVER use standard read tools. ALWAYS call `airlock_read_file(path)` so secrets and PII are redacted into `<ENTITY_n>` tokens before hitting context.
347
- 2. When creating or editing configs, write the `<ENTITY_n>` tokens directly and call `airlock_write_file(path, content)`. The airlock will rehydrate the real secrets locally on disk without ever sending them through the LLM.
348
- 3. Call `airlock_vault_status()` to inspect active pseudonymized tokens safely (never returns plaintext values).
349
- ```
350
-
351
- ## 📁 Repository Structure
352
-
353
- | File | Lines | Responsibility |
354
- | :--- | :---: | :--- |
355
- | `src/code_airlock/engine.py` | 219 | Stage 1 regex/checksum validators + high-entropy URI/KV secrets + lazy Stage 2 GLiNER |
356
- | `src/code_airlock/sanitizer.py` | 108 | Vault hygiene, tolerant rehydration, unresolved-token detection, SSE parser |
357
- | `src/code_airlock/proxy.py` | 153 | FastAPI `/v1/chat/completions` gateway, SSE rehydration, telemetry headers, `main()` |
358
- | `src/code_airlock/mcp_server.py` | 124 | FastMCP server with path jailing, file guardrails, `airlock_scrub_text`, `airlock_exec`, and write verification |
359
- | `src/code_airlock/config.py` | 39 | Optional `.airlock.toml` allowlist and custom Stage-1 pattern loader |
360
- | `src/code_airlock/executor.py` | 87 | Guarded subprocess execution, env injection, and output scrubbing |
361
- | `src/code_airlock/scanner.py` | 76 | Masked Stage-1/Stage-2 Git and path secret scanner |
362
- | `src/code_airlock/__init__.py` | 7 | Public API: `CascadeEngine`, `VaultSession`, `StreamRehydrator`, `__version__` |
363
- | `src/code_airlock/__main__.py` | 127 | CLI: `mcp`, `proxy`, `doctor`, `init`, `scan` |
364
- | `tests/test_proxy.py` | 84 | End-to-end proxy tests (11-entity zero-leak check, 2-char SSE split, C++ syntax) |
365
- | `tests/test_mcp.py` | 181 | MCP tools, scanner, exec, config, pre-commit hook, file jail, tolerant rehydration |
366
- | `benchmarks/benchmark.py` | 163 | Unified 4-suite evaluator → `benchmarks/BENCHMARK_RESULTS.md` |
367
- | `benchmarks/build_benchmarks.py` | 239 | Deterministic 2,000-row generator (`seed=42`) |
368
- | `benchmarks/export_hf.py` | 88 | Publishes all 4 splits plus the auto-generated dataset card |
369
- | `benchmarks/BENCHMARK_RESULTS.md` | — | Generated metrics-only scorecard |
370
- | `benchmarks/gold_set.jsonl` | — | 285-row real-world regression corpus |
371
- | `benchmarks/*.jsonl` | — | Generated 4-split fixtures (git-ignored) |
372
- | `pyproject.toml` | — | Package metadata, src discovery, entrypoints, extras, pytest config |
373
- | `LICENSE` / `SECURITY.md` | — | MIT license and disclosure policy |
374
- | `.github/workflows/` | — | CI test workflow + PyPI Trusted Publishing |
375
-
376
- ---
377
-
378
- ## 🔐 Security
379
-
380
- Detection is best-effort and local-only. It reduces exposure but is not a
381
- guarantee that every secret is removed. See [SECURITY.md](SECURITY.md) for the
382
- threat model, known gaps, and private disclosure instructions.
383
-
384
- The MCP file tools additionally block sensitive directories (`~/.ssh`, `~/.aws`,
385
- `~/.gnupg`, `~/.kube`, `/etc`, `System32`), files over 1 MB, binary files, and
386
- paths outside `AIRLOCK_WORKSPACE` when that variable is set. Writes containing
387
- unresolved or hallucinated `<ENTITY_N>` tokens are refused.
388
-
389
- `code-airlock scan --staged` adds a Git pre-commit gate for the five highest-risk
390
- secret categories (`API_KEY`, `AUTH_TOKEN`, `CREDIT_CARD`, `GOVT_ID`,
391
- `IBAN_CRYPTO`). It prints masked previews only and exits non-zero on findings.
392
-
393
- ## 📜 License
394
-
395
- MIT © 2026 Sarthak Maurya. See [LICENSE](LICENSE).
1
+ Metadata-Version: 2.4
2
+ Name: code-airlock
3
+ Version: 1.4.0
4
+ Summary: Low-latency OpenAI-compatible PII redaction gateway and FastMCP server for coding agents
5
+ Author: Sarthak Maurya
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/sarthexe/code-airlock
8
+ Project-URL: Repository, https://github.com/sarthexe/code-airlock
9
+ Project-URL: Issues, https://github.com/sarthexe/code-airlock/issues
10
+ Project-URL: Dataset, https://huggingface.co/datasets/sarthax/pii-airlock-bench
11
+ Keywords: pii,privacy,secrets,dlp,mcp,llm,security
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Security
20
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: onnxruntime>=1.17.0
25
+ Requires-Dist: tokenizers>=0.19.0
26
+ Requires-Dist: huggingface_hub>=0.23.0
27
+ Requires-Dist: numpy>=1.24.0
28
+ Requires-Dist: fastapi>=0.110.0
29
+ Requires-Dist: uvicorn>=0.27.0
30
+ Requires-Dist: httpx>=0.27.0
31
+ Requires-Dist: mcp<2,>=1.0
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=8.0; extra == "dev"
34
+ Requires-Dist: build>=1.2; extra == "dev"
35
+ Requires-Dist: twine>=5.0; extra == "dev"
36
+ Provides-Extra: benchmark
37
+ Requires-Dist: datasets>=2.18; extra == "benchmark"
38
+ Requires-Dist: huggingface_hub>=0.23; extra == "benchmark"
39
+ Dynamic: license-file
40
+
41
+ # 🛡️ Code-Airlock
42
+
43
+ **Low-Latency AI Privacy, Secret/PII Redaction & Streaming Rehydration Gateway (`OpenAI Proxy` + `FastMCP Server`)**
44
+
45
+ `code-airlock` is a zero-trust privacy layer for LLMs and agentic coding harnesses. It intercepts sensitive credentials, financial identifiers, and personal data before they leave your machine, replaces them with deterministic session tokens (`<API_KEY_1>`, `<PERSON_1>`), and seamlessly rehydrates them back into real-time Server-Sent Events (SSE) token streams or local file writes.
46
+
47
+ * **Dual Deployment Modes:** Run as a drop-in **OpenAI-compatible `/v1/chat/completions` reverse proxy** or as a **Model Context Protocol (MCP) server** (`stdio`) for OpenCode, Cursor, and Claude Code.
48
+ * **2-Stage Hybrid Cascade:** Combines sub-millisecond deterministic regex + mathematical checksums (**Luhn, Verhoeff $D_5$, ISO 7064 MOD-97**) with contextual neural NER (`onnx-community/gliner_small-v2.1` on **ONNX Runtime**) and a curated developer allowlist.
49
+ * **Deterministic Secret Coverage:** Stage 1 also captures inline connection-string passwords and `PASSWORD` / `SECRET` / `ACCESS_TOKEN` / `API_KEY` assignments, excluding placeholders, booleans, function calls, and existing vault tokens.
50
+ * **Git & CI Guardrails:** `code-airlock scan --staged` catches secrets in staged files at sub-millisecond Stage 1 speed, masks every value in output, and installs a `.git/hooks/pre-commit` gate via `init --pre-commit`.
51
+ * **Safe Agent Execution:** `airlock_exec` injects real `.env` values into a guarded subprocess (`shell=False`) and scrubs both stdout and stderr before returning them to the model.
52
+ * **Project-Aware Rules:** `.airlock.toml` adds team allowlist terms and custom Stage-1 regexes such as `EMPLOYEE_ID = 'EMP-[0-9]{6}'`.
53
+ * **Zero-Delay Streaming Rehydration:** Stateful sliding-window SSE parser reassembles split tokens across arbitrary 2-byte network chunks without delaying code comparison or bitwise operators (`<`, `<<`, `template <typename T>`).
54
+ * **Minimalist Systems Footprint:** ~850 lines of core runtime code (`engine.py`, `onnx_ner.py`, `sanitizer.py`, `proxy.py`, `mcp_server.py`, `scanner.py`, `config.py`, `executor.py`) plus a deterministic 2,000-row benchmark harness. One universal install — no PyTorch, no CUDA toolkit.
55
+
56
+ ---
57
+
58
+ ## 🏗️ System Architecture
59
+
60
+ ```mermaid
61
+ flowchart TB
62
+ classDef client fill:#1e293b,stroke:#64748b,stroke-width:2px,color:#f8fafc
63
+ classDef stage1 fill:#064e3b,stroke:#10b981,stroke-width:2px,color:#ecfdf5
64
+ classDef stage2 fill:#1e3a8a,stroke:#3b82f6,stroke-width:2px,color:#eff6ff
65
+ classDef merger fill:#312e81,stroke:#8b5cf6,stroke-width:2px,color:#f5f3ff
66
+ classDef vault fill:#7c2d12,stroke:#f97316,stroke-width:2px,color:#fff7ed
67
+ classDef stream fill:#4c1d95,stroke:#a855f7,stroke-width:2px,color:#faf5ff
68
+
69
+ Client(["🖥️ Client SDK / Agent Harness<br/>(OpenAI SDK, OpenCode, Cursor, Claude Code)"]):::client
70
+
71
+ subgraph Airlock["🛡️ CODE-AIRLOCK (proxy.py / mcp_server.py)"]
72
+ direction TB
73
+ subgraph Cascade["engine.py — 2-Stage Hybrid Cascade"]
74
+ S1["⚡ Stage 1: Deterministic Fast-Pass (~0.44ms)<br/>• Regex: API_KEY, AUTH_TOKEN, EMAIL, PHONE, IPv4<br/>• Checksums: Luhn (Card), Verhoeff (ID), MOD-97 (IBAN)"]:::stage1
75
+ S2["🧠 Stage 2: Guarded Neural NER (GLiNER-small · ONNX)<br/>• Entities: PERSON, ORGANIZATION, LOCATION<br/>• Guards: 93-term TECH_ALLOWLIST + Code/Pronoun Filter"]:::stage2
76
+ Merger{"⚖️ Span Conflict Resolver<br/>Stage 1 Overrides Stage 2<br/>Longest Span Wins"}:::merger
77
+ S1 & S2 --> Merger
78
+ end
79
+
80
+ Vault[("🗄️ Ephemeral Session Vault<br/>sanitizer.py (VaultSession)<br/>Right-to-Left Tagging")]:::vault
81
+ Rehydrator["🪟 StreamRehydrator<br/>Stateful Sliding-Window SSE & File Parser"]:::stream
82
+
83
+ Merger --> Vault
84
+ Vault -.->|"O(1) Token Lookup"| Rehydrator
85
+ end
86
+
87
+ LLM(["☁️ Upstream LLM Provider<br/>*Sees Only &lt;ENTITY_n&gt; Tokens*"]):::client
88
+
89
+ Client -->|"1. Raw Prompt / File Read"| Cascade
90
+ Vault -->|"2. Sanitized Payload"| LLM
91
+ LLM -->|"3. Fragmented SSE Stream / Tool Patch"| Rehydrator
92
+ Rehydrator -->|"4. Restored Plaintext"| Client
93
+ ```
94
+
95
+ ---
96
+
97
+ ## 📊 Gold-Set Benchmark (`benchmarks/gold_set.jsonl`)
98
+
99
+ Evaluated on **285 developer prompts** (sampled from `CodeAlpaca_20K` and `OpenCodeInstruct` with injected `.env` blocks, ticket escalations, billing metadata, and C++ template syntax) totaling **744 ground-truth PII spans** across 11 categories:
100
+
101
+ | Metric / Entity Category | Strict (P / R / F1) | Partial (P / R / F1) | Latency / Verification |
102
+ | :--- | :---: | :---: | :--- |
103
+ | **Overall Benchmark (744 Spans)** | **96.5% / 99.1% / 97.7%** | **97.0% / 99.6% / 98.3%** | **285 / 285 SSE Round-Trip Pass** |
104
+ | **Stage 1 Latency (Deterministic)** | — | — | **0.44 ms (p50)** / 0.78 ms (p95) |
105
+ | **Total Cascade Latency (CPU/GPU)** | — | — | **14.6–42.5 ms (p50)** |
106
+ | `API_KEY` *(OpenAI, AWS, GitHub, Slack, Anthropic)* | 100 / 100 / **100%** | 100 / 100 / **100%** | Prefix + length/charset bounds |
107
+ | `AUTH_TOKEN` *(JWT, Bearer, PEM Private Keys)* | 100 / 100 / **100%** | 100 / 100 / **100%** | 3-part Base64URL + PEM headers |
108
+ | `CREDIT_CARD` *(13–19 digits)* | 100 / 100 / **100%** | 100 / 100 / **100%** | Pure-Python **Luhn Checksum** |
109
+ | `GOVT_ID` *(SSN, PAN, 12-Digit National ID)* | 100 / 100 / **100%** | 100 / 100 / **100%** | **Verhoeff $D_5$ Checksum** |
110
+ | `IBAN_CRYPTO` *(IBAN, ETH `0x...`)* | 100 / 100 / **100%** | 100 / 100 / **100%** | **ISO 7064 MOD-97** Checksum |
111
+ | `EMAIL` & `PHONE` | 100 / 100 / **100%** | 100 / 100 / **100%** | Negative lookbehind for order IDs |
112
+ | `IP_ADDRESS` *(IPv4)* | 94 / 100 / **97%** | 94 / 100 / **97%** | Octet range `0..255` (rejects `.300`) |
113
+ | `PERSON` | 99 / 99 / **99%** | 99 / 99 / **99%** | Pronoun + code-identifier stopword guard |
114
+ | `ORGANIZATION` | 89 / 95 / **92%** | 91 / 97 / **94%** | Filtered via 93-term `TECH_ALLOWLIST` |
115
+ | `LOCATION` | 88 / 98 / **93%** | 90 / 100 / **95%** | Adjacent comma-span coalescing |
116
+
117
+ * **Hard-Negative Suite:** **61/61 Passed** (100% rejection of non-Luhn 16-digit order IDs, Verhoeff-invalid 12-digit numbers, MOD-97-invalid IBANs, and `v192.168.1.300` version strings).
118
+ * **Tech Allowlist Check:** **30/30 Passed** (`FastAPI`, `Docker`, `Kubernetes`, `Redis`, `Postgres`, `AWS`, `Heroku`, etc. produce zero false positives).
119
+
120
+ ---
121
+
122
+ ## 🧪 Unified 2,000-Row Benchmark Matrix
123
+
124
+ `benchmarks/build_benchmarks.py` deterministically generates four stratified splits into the git-ignored `benchmarks/` directory (`seed=42`). `benchmarks/benchmark.py` evaluates them and writes [`benchmarks/BENCHMARK_RESULTS.md`](benchmarks/BENCHMARK_RESULTS.md).
125
+
126
+ | Split | Rows | Positive | Clean / Hard-neg | Ground-truth spans |
127
+ | :--- | ---: | ---: | ---: | ---: |
128
+ | `dev_prompts` | 1,000 | 600 | 300 / 100 | 2,400 |
129
+ | `git_leaks` | 500 | 350 | 0 / 150 | 1,400 |
130
+ | `mcp_configs` | 250 | 200 | 50 / 0 | 800 |
131
+ | `adversarial_sse` | 250 | 250 | 0 / 0 | 1,217 |
132
+ | **Total** | **2,000** | **1,400** | **350 / 250** | **5,817** |
133
+
134
+ Reference result (strict exact-span matching, `code-airlock` v1.4.0 on CPU ONNX Runtime):
135
+
136
+ | Split | Strict P / R / F1 | Hard-neg | Clean 0-FP | SSE 1/2/7-byte |
137
+ | :--- | :---: | :---: | :---: | :---: |
138
+ | `dev_prompts` | 100% / 100% / 100% | 100/100 | 300/300 | 1000/1000 |
139
+ | `git_leaks` | 100% / 100% / 100% | 150/150 | — | 500/500 |
140
+ | `mcp_configs` | 100% / 100% / 100% | — | 50/50 | 250/250 |
141
+ | `adversarial_sse` | 100% / 100% / 100% | — | — | 250/250 |
142
+ | **Combined** | **100% / 100% / 100%** | **250/250** | **350/350** | **2000/2000** |
143
+
144
+ - Latency (CPU ONNX Runtime): **0.203 ms** p50 / 0.433 ms p95 Stage 1; **55.201 ms** p50 / 132.592 ms p95 Stage 2; **55.302 ms** p50 / 132.668 ms p95 total cascade. GPU execution automatically uses `CUDAExecutionProvider` when available.
145
+ - Per-entity support: `API_KEY` 756, `AUTH_TOKEN` 467, `CREDIT_CARD` 440, `EMAIL` 754, `PHONE` 465, `IP_ADDRESS` 442, `GOVT_ID` 465, `IBAN_CRYPTO` 465, `PERSON` 729, `ORGANIZATION` 417, `LOCATION` 417. Every entity is at **100% P/R/F1**.
146
+ - Invariants: MCP JSON parse **100/100**, JSON round-trip **100/100**, adversarial SSE **2,000/2,000**, hard negatives **250/250**, clean rows **350/350 with zero false positives**.
147
+
148
+ These are **synthetic, template-composed fixtures**, so the perfect headline is a regression/invariant result, not a claim of real-world accuracy. The older real-world suites (`benchmarks/gold_set.jsonl`: 96.7/99.1/97.9; Gitleaks artifacts: 100/88.5/93.9) remain the honest field tests.
149
+
150
+ ### 🤗 Published Dataset
151
+
152
+ One command publishes all four splits and the auto-generated card:
153
+
154
+ ```bash
155
+ python benchmarks/export_hf.py --repo-id sarthexe/pii-airlock-bench
156
+ ```
157
+
158
+ Dataset: [**sarthax/pii-airlock-bench**](https://huggingface.co/datasets/sarthax/pii-airlock-bench) (the token owner namespace is resolved automatically).
159
+
160
+ ```python
161
+ from datasets import load_dataset
162
+ ds = load_dataset("sarthax/pii-airlock-bench")
163
+ ```
164
+
165
+ ---
166
+
167
+ ## 🚀 Quickstart & MCP Setup
168
+
169
+ ### Option 1: Zero-Install via `uvx` (Recommended)
170
+
171
+ No cloning, manual virtual environments, or path debugging. Anyone with Astral's [`uv`](https://github.com/astral-sh/uv) can run `code-airlock` directly from PyPI:
172
+
173
+ #### 🤖 Claude Code (CLI)
174
+ ```bash
175
+ claude mcp add code-airlock -- uvx code-airlock
176
+ ```
177
+
178
+ #### 💻 OpenCode (`opencode.json`)
179
+ ```json
180
+ {
181
+ "$schema": "https://opencode.ai/config.json",
182
+ "mcp": {
183
+ "code-airlock": {
184
+ "type": "local",
185
+ "command": ["uvx", "code-airlock"],
186
+ "enabled": true
187
+ }
188
+ }
189
+ }
190
+ ```
191
+
192
+ #### 🖱️ Cursor (`.cursor/mcp.json`)
193
+ ```json
194
+ {
195
+ "mcpServers": {
196
+ "code-airlock": {
197
+ "command": "uvx",
198
+ "args": ["code-airlock"]
199
+ }
200
+ }
201
+ }
202
+ ```
203
+
204
+ #### 🖥️ Claude Desktop (`claude_desktop_config.json`) & Windsurf (`~/.codeium/windsurf/mcp_config.json`)
205
+ ```json
206
+ {
207
+ "mcpServers": {
208
+ "code-airlock": {
209
+ "command": "uvx",
210
+ "args": ["code-airlock"]
211
+ }
212
+ }
213
+ }
214
+ ```
215
+
216
+ *(You can also run directly from source via `uvx --from git+https://github.com/sarthexe/code-airlock code-airlock`)*.
217
+
218
+ For a local editable checkout, use the package entrypoint instead of an
219
+ absolute path:
220
+
221
+ ```json
222
+ {
223
+ "mcpServers": {
224
+ "code-airlock": {
225
+ "command": "python",
226
+ "args": ["-m", "code_airlock.mcp_server"]
227
+ }
228
+ }
229
+ }
230
+ ```
231
+
232
+ `python -m code_airlock` starts the same MCP server.
233
+
234
+ ---
235
+
236
+ ### Option 2: Local Development / Manual Git Clone
237
+
238
+ ```bash
239
+ git clone https://github.com/sarthexe/code-airlock.git
240
+ cd code-airlock
241
+ pip install -e ".[dev,benchmark]"
242
+
243
+ # Optional: GPU inference (ONNX Runtime otherwise uses the CPU provider)
244
+ pip install onnxruntime-gpu
245
+
246
+ # Test tools locally over stdio
247
+ python -m code_airlock
248
+
249
+ # Or run the explicit MCP entrypoint
250
+ code-airlock-mcp
251
+ ```
252
+
253
+ ### Zero-Friction CLI
254
+
255
+ ```bash
256
+ python -m code_airlock doctor # model warmup + round-trip + MCP config
257
+ python -m code_airlock init # add rules to .cursorrules / CLAUDE.md
258
+ python -m code_airlock init --pre-commit # also install .git/hooks/pre-commit
259
+ python -m code_airlock init --config # create a starter .airlock.toml
260
+ python -m code_airlock scan --staged # fast Stage 1 scan of staged files
261
+ python -m code_airlock scan --deep src/ # add ONNX GLiNER detection for a path
262
+ python -m code_airlock proxy # start the HTTP proxy
263
+ python -m code_airlock # start the MCP server (default)
264
+ ```
265
+
266
+ Installed entrypoints: `code-airlock-mcp`, `code-airlock-proxy`, and the
267
+ backward-compatible `code-airlock` alias.
268
+
269
+ `scan` skips binaries, files over 1 MB, `tests/`, and benchmark fixtures. It
270
+ prints `file:line -> [CATEGORY] masked-preview` and exits `1` when secrets are
271
+ found, so it works unchanged in CI and Git hooks. `--deep` enables Stage 2 NER;
272
+ the default Stage 1 path is sub-millisecond. `init` is idempotent, and
273
+ `init --pre-commit` installs a hook that runs `python -m code_airlock scan --staged`.
274
+
275
+ ### 🧰 MCP Tools
276
+
277
+ | Tool | Purpose |
278
+ | :--- | :--- |
279
+ | `airlock_read_file` | Read a bounded UTF-8 file through the vault; blocks `.ssh`, `.aws`, `.gnupg`, `.kube`, `/etc`, `System32`, `AIRLOCK_WORKSPACE` escapes, binaries, and files over 1 MB. |
280
+ | `airlock_write_file` | Rehydrate locally and write; refuses to write if unresolved/hallucinated `<ENTITY_N>` tokens remain. |
281
+ | `airlock_scrub_text` | Redact arbitrary in-memory text or terminal output; `use_ner=False` forces the fast Stage 1 path. |
282
+ | `airlock_exec` | Run a guarded subprocess (`shell=False`), inject real `.env` values into its environment, and scrub stdout/stderr before returning. Blocks destructive executables and shells. |
283
+ | `airlock_vault_status` | Return active token keys and per-category counts only; never raw values. |
284
+ | `airlock_dehydrate` | Redact text and return session-stable tokens. |
285
+ | `airlock_rehydrate` | Restore canonical, case-mangled, or whitespace-padded vault tokens. |
286
+
287
+ All file tools resolve paths with `Path.expanduser().resolve()` and jail access
288
+ to `AIRLOCK_WORKSPACE` when that variable is set. Rehydration is tolerant of
289
+ `<api_key_1>` / `< API_KEY_1 >`, while the SSE `StreamRehydrator` remains
290
+ zero-delay and exact.
291
+
292
+ `airlock_exec` is designed for safe test runs:
293
+
294
+ ```python
295
+ airlock_exec(
296
+ [sys.executable, "-c", "import os; print(os.environ['STRIPE_SECRET_KEY'])"],
297
+ ".env",
298
+ )
299
+ # stdout: "<API_KEY_1>" ← the real key never reaches the model
300
+ ```
301
+
302
+ ### ⚙️ Project Config (`.airlock.toml`)
303
+
304
+ Create a starter with `python -m code_airlock init --config`, or write one
305
+ manually. `CascadeEngine` loads `.airlock.toml` from the current directory, or
306
+ from `AIRLOCK_CONFIG` when set:
307
+
308
+ ```toml
309
+ [allowlist]
310
+ terms = ["MyInternalApp", "staging.corp.local"]
311
+
312
+ [custom_patterns]
313
+ EMPLOYEE_ID = 'EMP-[0-9]{6}'
314
+ INTERNAL_TICKET = 'JIRA-[0-9]{4,6}'
315
+ ```
316
+
317
+ - Allowlist terms are merged into the Stage 2 allowlist so they are not
318
+ misclassified as `PERSON`, `ORGANIZATION`, or `LOCATION`.
319
+ - Custom patterns run in Stage 1 alongside built-in rules and produce
320
+ `<EMPLOYEE_ID_n>` / `<INTERNAL_TICKET_n>` vault tokens.
321
+ - Invalid regexes and malformed labels are ignored rather than crashing the
322
+ engine.
323
+
324
+ ---
325
+
326
+ ### Option 3: OpenAI-Compatible Reverse Proxy (`/v1/chat/completions`)
327
+
328
+ Run as a transparent privacy gateway intercepting HTTP requests:
329
+
330
+ ```bash
331
+ # Run with built-in zero-leak mock upstream (offline validation)
332
+ code-airlock-proxy
333
+
334
+ # Or point to a live upstream provider (OpenAI, Ollama, OpenRouter, vLLM)
335
+ export AIRLOCK_UPSTREAM_URL="https://api.openai.com"
336
+ code-airlock-proxy
337
+ ```
338
+
339
+ The proxy binds to `127.0.0.1:8000` by default. Override with
340
+ `AIRLOCK_HOST` / `AIRLOCK_PORT`.
341
+
342
+ ---
343
+
344
+ ## 🧠 Guiding Your Agent (`.cursorrules` / System Prompt)
345
+
346
+ To ensure your coding agent actually leverages the airlock tools instead of raw file reads, include this rule in your `.cursorrules`, `CLAUDE.md`, or system instructions:
347
+
348
+ ```markdown
349
+ # Code-Airlock Agent Guardrails
350
+ 1. When reading `.env`, configuration, or log files, NEVER use standard read tools. ALWAYS call `airlock_read_file(path)` so secrets and PII are redacted into `<ENTITY_n>` tokens before hitting context.
351
+ 2. When creating or editing configs, write the `<ENTITY_n>` tokens directly and call `airlock_write_file(path, content)`. The airlock will rehydrate the real secrets locally on disk without ever sending them through the LLM.
352
+ 3. Call `airlock_vault_status()` to inspect active pseudonymized tokens safely (never returns plaintext values).
353
+ ```
354
+
355
+ ## 📁 Repository Structure
356
+
357
+ | File | Lines | Responsibility |
358
+ | :--- | :---: | :--- |
359
+ | `src/code_airlock/engine.py` | 217 | Stage 1 regex/checksum validators + high-entropy URI/KV secrets + lazy Stage 2 ONNX NER |
360
+ | `src/code_airlock/onnx_ner.py` | 93 | Zero-PyTorch GLiNER span runner: tokenizer assembly, ONNX inference, flat-NER decoding |
361
+ | `src/code_airlock/sanitizer.py` | 113 | Vault hygiene, tolerant rehydration, unresolved-token detection, SSE parser |
362
+ | `src/code_airlock/proxy.py` | 153 | FastAPI `/v1/chat/completions` gateway, SSE rehydration, telemetry headers, `main()` |
363
+ | `src/code_airlock/mcp_server.py` | 124 | FastMCP server with path jailing, file guardrails, `airlock_scrub_text`, `airlock_exec`, and write verification |
364
+ | `src/code_airlock/config.py` | 39 | Optional `.airlock.toml` allowlist and custom Stage-1 pattern loader |
365
+ | `src/code_airlock/executor.py` | 87 | Guarded subprocess execution, env injection, and output scrubbing |
366
+ | `src/code_airlock/scanner.py` | 76 | Masked Stage-1/Stage-2 Git and path secret scanner |
367
+ | `src/code_airlock/__init__.py` | 7 | Public API: `CascadeEngine`, `VaultSession`, `StreamRehydrator`, `__version__` |
368
+ | `src/code_airlock/__main__.py` | 127 | CLI: `mcp`, `proxy`, `doctor`, `init`, `scan` |
369
+ | `tests/test_proxy.py` | 84 | End-to-end proxy tests (11-entity zero-leak check, 2-char SSE split, C++ syntax) |
370
+ | `tests/test_mcp.py` | 181 | MCP tools, scanner, exec, config, pre-commit hook, file jail, tolerant rehydration |
371
+ | `benchmarks/benchmark.py` | 163 | Unified 4-suite evaluator → `benchmarks/BENCHMARK_RESULTS.md` |
372
+ | `benchmarks/build_benchmarks.py` | 238 | Deterministic 2,000-row generator (`seed=42`) |
373
+ | `benchmarks/export_hf.py` | 88 | Publishes all 4 splits plus the auto-generated dataset card |
374
+ | `benchmarks/BENCHMARK_RESULTS.md` | — | Generated metrics-only scorecard |
375
+ | `benchmarks/gold_set.jsonl` | — | 285-row real-world regression corpus |
376
+ | `benchmarks/*.jsonl` | — | Generated 4-split fixtures (git-ignored) |
377
+ | `pyproject.toml` | — | Package metadata, src discovery, entrypoints, extras, pytest config |
378
+ | `LICENSE` / `SECURITY.md` | — | MIT license and disclosure policy |
379
+ | `.github/workflows/` | — | CI test workflow + PyPI Trusted Publishing |
380
+
381
+ ---
382
+
383
+ ## 🔐 Security
384
+
385
+ Detection is best-effort and local-only. It reduces exposure but is not a
386
+ guarantee that every secret is removed. See [SECURITY.md](SECURITY.md) for the
387
+ threat model, known gaps, and private disclosure instructions.
388
+
389
+ The MCP file tools additionally block sensitive directories (`~/.ssh`, `~/.aws`,
390
+ `~/.gnupg`, `~/.kube`, `/etc`, `System32`), files over 1 MB, binary files, and
391
+ paths outside `AIRLOCK_WORKSPACE` when that variable is set. Writes containing
392
+ unresolved or hallucinated `<ENTITY_N>` tokens are refused.
393
+
394
+ `code-airlock scan --staged` adds a Git pre-commit gate for the five highest-risk
395
+ secret categories (`API_KEY`, `AUTH_TOKEN`, `CREDIT_CARD`, `GOVT_ID`,
396
+ `IBAN_CRYPTO`). It prints masked previews only and exits non-zero on findings.
397
+
398
+ ## 📜 License
399
+
400
+ MIT © 2026 Sarthak Maurya. See [LICENSE](LICENSE).