code-airlock 1.0.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.
- {code_airlock-1.0.0/src/code_airlock.egg-info → code_airlock-1.4.0}/PKG-INFO +400 -308
- {code_airlock-1.0.0 → code_airlock-1.4.0}/README.md +360 -269
- {code_airlock-1.0.0 → code_airlock-1.4.0}/pyproject.toml +7 -6
- {code_airlock-1.0.0 → code_airlock-1.4.0}/setup.cfg +4 -4
- {code_airlock-1.0.0 → code_airlock-1.4.0}/src/code_airlock/__init__.py +1 -1
- code_airlock-1.4.0/src/code_airlock/__main__.py +127 -0
- code_airlock-1.4.0/src/code_airlock/config.py +39 -0
- {code_airlock-1.0.0 → code_airlock-1.4.0}/src/code_airlock/engine.py +217 -199
- code_airlock-1.4.0/src/code_airlock/executor.py +87 -0
- code_airlock-1.4.0/src/code_airlock/mcp_server.py +124 -0
- code_airlock-1.4.0/src/code_airlock/onnx_ner.py +93 -0
- {code_airlock-1.0.0 → code_airlock-1.4.0}/src/code_airlock/proxy.py +1 -1
- {code_airlock-1.0.0 → code_airlock-1.4.0}/src/code_airlock/sanitizer.py +27 -7
- code_airlock-1.4.0/src/code_airlock/scanner.py +76 -0
- {code_airlock-1.0.0 → code_airlock-1.4.0/src/code_airlock.egg-info}/PKG-INFO +400 -308
- {code_airlock-1.0.0 → code_airlock-1.4.0}/src/code_airlock.egg-info/SOURCES.txt +4 -0
- {code_airlock-1.0.0 → code_airlock-1.4.0}/src/code_airlock.egg-info/requires.txt +6 -5
- code_airlock-1.4.0/tests/test_mcp.py +181 -0
- code_airlock-1.0.0/src/code_airlock/__main__.py +0 -6
- code_airlock-1.0.0/src/code_airlock/mcp_server.py +0 -71
- code_airlock-1.0.0/tests/test_mcp.py +0 -66
- {code_airlock-1.0.0 → code_airlock-1.4.0}/LICENSE +0 -0
- {code_airlock-1.0.0 → code_airlock-1.4.0}/src/code_airlock.egg-info/dependency_links.txt +0 -0
- {code_airlock-1.0.0 → code_airlock-1.4.0}/src/code_airlock.egg-info/entry_points.txt +0 -0
- {code_airlock-1.0.0 → code_airlock-1.4.0}/src/code_airlock.egg-info/top_level.txt +0 -0
- {code_airlock-1.0.0 → code_airlock-1.4.0}/tests/test_proxy.py +0 -0
|
@@ -1,308 +1,400 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: code-airlock
|
|
3
|
-
Version: 1.
|
|
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:
|
|
25
|
-
Requires-Dist:
|
|
26
|
-
Requires-Dist:
|
|
27
|
-
Requires-Dist:
|
|
28
|
-
Requires-Dist: fastapi>=0.110
|
|
29
|
-
Requires-Dist: uvicorn>=0.27
|
|
30
|
-
Requires-Dist: httpx>=0.27
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
Requires-Dist:
|
|
34
|
-
Requires-Dist:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
Requires-Dist:
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
* **
|
|
48
|
-
* **
|
|
49
|
-
* **
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
classDef
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
|
102
|
-
|
|
|
103
|
-
|
|
|
104
|
-
|
|
|
105
|
-
|
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
|
|
112
|
-
*
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
|
127
|
-
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
|
132
|
-
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
|
137
|
-
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
####
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
|
279
|
-
|
|
|
280
|
-
| `
|
|
281
|
-
| `
|
|
282
|
-
| `
|
|
283
|
-
| `
|
|
284
|
-
| `
|
|
285
|
-
| `
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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 <ENTITY_n> 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).
|