openadapt-privacy 0.1.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.
- openadapt_privacy-0.1.0/.github/workflows/publish.yml +29 -0
- openadapt_privacy-0.1.0/.github/workflows/test.yml +54 -0
- openadapt_privacy-0.1.0/.gitignore +47 -0
- openadapt_privacy-0.1.0/PKG-INFO +276 -0
- openadapt_privacy-0.1.0/README.md +242 -0
- openadapt_privacy-0.1.0/assets/screenshot_original.png +0 -0
- openadapt_privacy-0.1.0/assets/screenshot_scrubbed.png +0 -0
- openadapt_privacy-0.1.0/openadapt_privacy/__init__.py +43 -0
- openadapt_privacy-0.1.0/openadapt_privacy/base.py +330 -0
- openadapt_privacy-0.1.0/openadapt_privacy/config.py +78 -0
- openadapt_privacy-0.1.0/openadapt_privacy/loaders.py +369 -0
- openadapt_privacy-0.1.0/openadapt_privacy/pipelines/__init__.py +13 -0
- openadapt_privacy-0.1.0/openadapt_privacy/pipelines/dicts.py +108 -0
- openadapt_privacy-0.1.0/openadapt_privacy/providers/__init__.py +71 -0
- openadapt_privacy-0.1.0/openadapt_privacy/providers/presidio.py +197 -0
- openadapt_privacy-0.1.0/pyproject.toml +66 -0
- openadapt_privacy-0.1.0/scripts/generate_demo_images.py +86 -0
- openadapt_privacy-0.1.0/tests/__init__.py +1 -0
- openadapt_privacy-0.1.0/tests/fixtures/__init__.py +144 -0
- openadapt_privacy-0.1.0/tests/test_base.py +140 -0
- openadapt_privacy-0.1.0/tests/test_config.py +59 -0
- openadapt_privacy-0.1.0/tests/test_fixtures.py +236 -0
- openadapt_privacy-0.1.0/tests/test_loaders.py +281 -0
- openadapt_privacy-0.1.0/tests/test_pipelines.py +167 -0
- openadapt_privacy-0.1.0/tests/test_presidio.py +133 -0
- openadapt_privacy-0.1.0/tests/test_providers.py +43 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write # Required for trusted publishing
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Install uv
|
|
18
|
+
uses: astral-sh/setup-uv@v4
|
|
19
|
+
with:
|
|
20
|
+
version: "latest"
|
|
21
|
+
|
|
22
|
+
- name: Set up Python
|
|
23
|
+
run: uv python install 3.12
|
|
24
|
+
|
|
25
|
+
- name: Build package
|
|
26
|
+
run: uv build
|
|
27
|
+
|
|
28
|
+
- name: Publish to PyPI
|
|
29
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
name: Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Install uv
|
|
20
|
+
uses: astral-sh/setup-uv@v4
|
|
21
|
+
with:
|
|
22
|
+
version: "latest"
|
|
23
|
+
|
|
24
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
25
|
+
run: uv python install ${{ matrix.python-version }}
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: uv sync --extra dev
|
|
29
|
+
|
|
30
|
+
- name: Run tests (without Presidio)
|
|
31
|
+
run: uv run pytest tests/ -v --ignore=tests/test_presidio.py
|
|
32
|
+
|
|
33
|
+
test-presidio:
|
|
34
|
+
runs-on: ubuntu-latest
|
|
35
|
+
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
|
|
39
|
+
- name: Install uv
|
|
40
|
+
uses: astral-sh/setup-uv@v4
|
|
41
|
+
with:
|
|
42
|
+
version: "latest"
|
|
43
|
+
|
|
44
|
+
- name: Set up Python
|
|
45
|
+
run: uv python install 3.12
|
|
46
|
+
|
|
47
|
+
- name: Install dependencies with Presidio
|
|
48
|
+
run: |
|
|
49
|
+
uv sync --extra dev --extra presidio
|
|
50
|
+
uv pip install pip
|
|
51
|
+
uv run python -m spacy download en_core_web_trf
|
|
52
|
+
|
|
53
|
+
- name: Run all tests
|
|
54
|
+
run: uv run pytest tests/ -v
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
.installed.cfg
|
|
21
|
+
*.egg
|
|
22
|
+
|
|
23
|
+
# Virtual environments
|
|
24
|
+
.venv/
|
|
25
|
+
venv/
|
|
26
|
+
ENV/
|
|
27
|
+
|
|
28
|
+
# Testing
|
|
29
|
+
.pytest_cache/
|
|
30
|
+
.coverage
|
|
31
|
+
htmlcov/
|
|
32
|
+
.tox/
|
|
33
|
+
.nox/
|
|
34
|
+
|
|
35
|
+
# IDEs
|
|
36
|
+
.idea/
|
|
37
|
+
.vscode/
|
|
38
|
+
*.swp
|
|
39
|
+
*.swo
|
|
40
|
+
*~
|
|
41
|
+
|
|
42
|
+
# OS
|
|
43
|
+
.DS_Store
|
|
44
|
+
Thumbs.db
|
|
45
|
+
|
|
46
|
+
# uv
|
|
47
|
+
uv.lock
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openadapt-privacy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Privacy scrubbing for GUI automation data - PII/PHI detection and redaction
|
|
5
|
+
Project-URL: Homepage, https://github.com/OpenAdaptAI/openadapt-privacy
|
|
6
|
+
Project-URL: Repository, https://github.com/OpenAdaptAI/openadapt-privacy
|
|
7
|
+
Author-email: "MLDSAI Inc." <richard@mldsai.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: automation,gui,phi,pii,privacy,redaction,scrubbing
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Security
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: pillow>=10.0.0
|
|
21
|
+
Requires-Dist: pip>=25.3
|
|
22
|
+
Requires-Dist: pydantic>=2.0.0
|
|
23
|
+
Provides-Extra: dev
|
|
24
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: ruff>=0.1.0; extra == 'dev'
|
|
27
|
+
Provides-Extra: presidio
|
|
28
|
+
Requires-Dist: presidio-analyzer>=2.2.0; extra == 'presidio'
|
|
29
|
+
Requires-Dist: presidio-anonymizer>=2.2.0; extra == 'presidio'
|
|
30
|
+
Requires-Dist: presidio-image-redactor>=0.0.50; extra == 'presidio'
|
|
31
|
+
Requires-Dist: spacy-transformers>=1.3.0; extra == 'presidio'
|
|
32
|
+
Requires-Dist: spacy>=3.7.0; extra == 'presidio'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
# openadapt-privacy
|
|
36
|
+
|
|
37
|
+
Privacy scrubbing for GUI automation data - PII/PHI detection and redaction.
|
|
38
|
+
|
|
39
|
+
## Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install openadapt-privacy
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
For Presidio-based scrubbing (recommended):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install openadapt-privacy[presidio]
|
|
49
|
+
python -m spacy download en_core_web_trf
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
### Text Scrubbing
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from openadapt_privacy.providers.presidio import PresidioScrubbingProvider
|
|
58
|
+
|
|
59
|
+
scrubber = PresidioScrubbingProvider()
|
|
60
|
+
|
|
61
|
+
text = "Contact John Smith at john.smith@example.com or 555-123-4567"
|
|
62
|
+
scrubbed = scrubber.scrub_text(text)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Input:**
|
|
66
|
+
```
|
|
67
|
+
Contact John Smith at john.smith@example.com or 555-123-4567
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Output:**
|
|
71
|
+
```
|
|
72
|
+
Contact <PERSON> at <EMAIL_ADDRESS> or <PHONE_NUMBER>
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Example Inputs & Outputs
|
|
76
|
+
|
|
77
|
+
| Input | Output |
|
|
78
|
+
|-------|--------|
|
|
79
|
+
| `My email is john@example.com` | `My email is <EMAIL_ADDRESS>` |
|
|
80
|
+
| `SSN: 923-45-6789` | `SSN: <US_SSN>` |
|
|
81
|
+
| `Card: 4532-1234-5678-9012` | `Card: <CREDIT_CARD>` |
|
|
82
|
+
| `Call me at 555-123-4567` | `Call me at <PHONE_NUMBER>` |
|
|
83
|
+
| `DOB: 01/15/1985` | `DOB: <DATE_TIME>` |
|
|
84
|
+
| `Contact John Smith` | `Contact <PERSON>` |
|
|
85
|
+
|
|
86
|
+
## Dict Scrubbing
|
|
87
|
+
|
|
88
|
+
Scrub PII from nested dictionaries (e.g., GUI element trees):
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from openadapt_privacy import scrub_dict
|
|
92
|
+
from openadapt_privacy.providers.presidio import PresidioScrubbingProvider
|
|
93
|
+
|
|
94
|
+
scrubber = PresidioScrubbingProvider()
|
|
95
|
+
action = {
|
|
96
|
+
"text": "Email: john@example.com",
|
|
97
|
+
"metadata": {
|
|
98
|
+
"title": "User Profile - John Smith",
|
|
99
|
+
"tooltip": "Click to contact john@example.com",
|
|
100
|
+
},
|
|
101
|
+
"coordinates": {"x": 100, "y": 200},
|
|
102
|
+
}
|
|
103
|
+
scrubbed = scrub_dict(action, scrubber)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Input:**
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"text": "Email: john@example.com",
|
|
110
|
+
"metadata": {
|
|
111
|
+
"title": "User Profile - John Smith",
|
|
112
|
+
"tooltip": "Click to contact john@example.com"
|
|
113
|
+
},
|
|
114
|
+
"coordinates": {"x": 100, "y": 200}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Output:**
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"text": "Email: <EMAIL_ADDRESS>",
|
|
122
|
+
"metadata": {
|
|
123
|
+
"title": "User Profile - <PERSON>",
|
|
124
|
+
"tooltip": "Click to contact <EMAIL_ADDRESS>"
|
|
125
|
+
},
|
|
126
|
+
"coordinates": {"x": 100, "y": 200}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Recording Pipeline
|
|
131
|
+
|
|
132
|
+
Process complete GUI automation recordings:
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from openadapt_privacy import DictRecordingLoader
|
|
136
|
+
from openadapt_privacy.providers.presidio import PresidioScrubbingProvider
|
|
137
|
+
|
|
138
|
+
scrubber = PresidioScrubbingProvider()
|
|
139
|
+
loader = DictRecordingLoader()
|
|
140
|
+
|
|
141
|
+
recording = loader.load_from_dict({
|
|
142
|
+
"task_description": "Send email to John Smith at john@example.com",
|
|
143
|
+
"actions": [
|
|
144
|
+
{"id": 1, "action_type": "click", "text": "Compose", "timestamp": 1000},
|
|
145
|
+
{"id": 2, "action_type": "type", "text": "john@example.com", "timestamp": 2000},
|
|
146
|
+
{"id": 3, "action_type": "click", "text": "Send", "window_title": "Email to john@example.com", "timestamp": 3000},
|
|
147
|
+
],
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
scrubbed = recording.scrub(scrubber)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Input Recording:**
|
|
154
|
+
```
|
|
155
|
+
task_description: "Send email to John Smith at john@example.com"
|
|
156
|
+
|
|
157
|
+
actions:
|
|
158
|
+
[1] click: "Compose"
|
|
159
|
+
[2] type: "john@example.com"
|
|
160
|
+
[3] click: "Send" (window: "Email to john@example.com")
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Output Recording:**
|
|
164
|
+
```
|
|
165
|
+
task_description: "Send email to <PERSON> at <EMAIL_ADDRESS>"
|
|
166
|
+
|
|
167
|
+
actions:
|
|
168
|
+
[1] click: "Compose"
|
|
169
|
+
[2] type: "<EMAIL_ADDRESS>"
|
|
170
|
+
[3] click: "Send" (window: "Email to <EMAIL_ADDRESS>")
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Image Scrubbing
|
|
174
|
+
|
|
175
|
+
Redact PII from screenshots using OCR + NER:
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
from PIL import Image
|
|
179
|
+
from openadapt_privacy.providers.presidio import PresidioScrubbingProvider
|
|
180
|
+
|
|
181
|
+
scrubber = PresidioScrubbingProvider()
|
|
182
|
+
|
|
183
|
+
image = Image.open("screenshot.png")
|
|
184
|
+
scrubbed_image = scrubber.scrub_image(image)
|
|
185
|
+
scrubbed_image.save("screenshot_scrubbed.png")
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Input Screenshot:**
|
|
189
|
+
|
|
190
|
+

|
|
191
|
+
|
|
192
|
+
**Output Screenshot:**
|
|
193
|
+
|
|
194
|
+

|
|
195
|
+
|
|
196
|
+
The image redactor:
|
|
197
|
+
1. Runs OCR to detect text regions
|
|
198
|
+
2. Analyzes text for PII entities (email, phone, SSN, etc.)
|
|
199
|
+
3. Fills detected PII regions with solid color (configurable, default: red)
|
|
200
|
+
|
|
201
|
+
## Custom Data Loader
|
|
202
|
+
|
|
203
|
+
Implement your own loader for custom storage formats:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from openadapt_privacy import RecordingLoader, Recording
|
|
207
|
+
|
|
208
|
+
class SQLiteRecordingLoader(RecordingLoader):
|
|
209
|
+
def __init__(self, db_path: str):
|
|
210
|
+
self.db_path = db_path
|
|
211
|
+
|
|
212
|
+
def load(self, recording_id: str) -> Recording:
|
|
213
|
+
# Load from SQLite database
|
|
214
|
+
...
|
|
215
|
+
|
|
216
|
+
def save(self, recording: Recording, recording_id: str) -> None:
|
|
217
|
+
# Save to SQLite database
|
|
218
|
+
...
|
|
219
|
+
|
|
220
|
+
# Usage
|
|
221
|
+
loader = SQLiteRecordingLoader("recordings.db")
|
|
222
|
+
scrubber = PresidioScrubbingProvider()
|
|
223
|
+
|
|
224
|
+
# Load, scrub, and save
|
|
225
|
+
scrubbed = loader.load_and_scrub("recording_001", scrubber)
|
|
226
|
+
loader.save(scrubbed, "recording_001_scrubbed")
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Configuration
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
from openadapt_privacy.config import PrivacyConfig
|
|
233
|
+
|
|
234
|
+
custom_config = PrivacyConfig(
|
|
235
|
+
SCRUB_CHAR="X", # Character for scrub_text_all
|
|
236
|
+
SCRUB_FILL_COLOR=0xFF0000, # Red for image redaction (BGR)
|
|
237
|
+
SCRUB_KEYS_HTML=[ # Keys to scrub in dicts
|
|
238
|
+
"text", "value", "title", "tooltip", "custom_field"
|
|
239
|
+
],
|
|
240
|
+
SCRUB_PRESIDIO_IGNORE_ENTITIES=[ # Entity types to skip
|
|
241
|
+
"DATE_TIME",
|
|
242
|
+
],
|
|
243
|
+
)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Supported Entity Types
|
|
247
|
+
|
|
248
|
+
| Entity | Example Input | Example Output |
|
|
249
|
+
|--------|---------------|----------------|
|
|
250
|
+
| `PERSON` | `John Smith` | `<PERSON>` |
|
|
251
|
+
| `EMAIL_ADDRESS` | `john@example.com` | `<EMAIL_ADDRESS>` |
|
|
252
|
+
| `PHONE_NUMBER` | `555-123-4567` | `<PHONE_NUMBER>` |
|
|
253
|
+
| `US_SSN` | `923-45-6789` | `<US_SSN>` |
|
|
254
|
+
| `CREDIT_CARD` | `4532-1234-5678-9012` | `<CREDIT_CARD>` |
|
|
255
|
+
| `US_BANK_NUMBER` | `635526789012` | `<US_BANK_NUMBER>` |
|
|
256
|
+
| `US_DRIVER_LICENSE` | `A123-456-789-012` | `<US_DRIVER_LICENSE>` |
|
|
257
|
+
| `DATE_TIME` | `01/15/1985` | `<DATE_TIME>` |
|
|
258
|
+
| `LOCATION` | `Toronto, ON` | `<LOCATION>` |
|
|
259
|
+
|
|
260
|
+
## Architecture
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
openadapt_privacy/
|
|
264
|
+
├── base.py # ScrubbingProvider, TextScrubbingMixin
|
|
265
|
+
├── config.py # PrivacyConfig dataclass
|
|
266
|
+
├── loaders.py # Recording, Action, Screenshot, RecordingLoader
|
|
267
|
+
├── providers/
|
|
268
|
+
│ ├── __init__.py # ScrubProvider registry
|
|
269
|
+
│ └── presidio.py # PresidioScrubbingProvider
|
|
270
|
+
└── pipelines/
|
|
271
|
+
└── dicts.py # scrub_dict, scrub_list_dicts
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
MIT
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# openadapt-privacy
|
|
2
|
+
|
|
3
|
+
Privacy scrubbing for GUI automation data - PII/PHI detection and redaction.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install openadapt-privacy
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
For Presidio-based scrubbing (recommended):
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install openadapt-privacy[presidio]
|
|
15
|
+
python -m spacy download en_core_web_trf
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
### Text Scrubbing
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
from openadapt_privacy.providers.presidio import PresidioScrubbingProvider
|
|
24
|
+
|
|
25
|
+
scrubber = PresidioScrubbingProvider()
|
|
26
|
+
|
|
27
|
+
text = "Contact John Smith at john.smith@example.com or 555-123-4567"
|
|
28
|
+
scrubbed = scrubber.scrub_text(text)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Input:**
|
|
32
|
+
```
|
|
33
|
+
Contact John Smith at john.smith@example.com or 555-123-4567
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Output:**
|
|
37
|
+
```
|
|
38
|
+
Contact <PERSON> at <EMAIL_ADDRESS> or <PHONE_NUMBER>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Example Inputs & Outputs
|
|
42
|
+
|
|
43
|
+
| Input | Output |
|
|
44
|
+
|-------|--------|
|
|
45
|
+
| `My email is john@example.com` | `My email is <EMAIL_ADDRESS>` |
|
|
46
|
+
| `SSN: 923-45-6789` | `SSN: <US_SSN>` |
|
|
47
|
+
| `Card: 4532-1234-5678-9012` | `Card: <CREDIT_CARD>` |
|
|
48
|
+
| `Call me at 555-123-4567` | `Call me at <PHONE_NUMBER>` |
|
|
49
|
+
| `DOB: 01/15/1985` | `DOB: <DATE_TIME>` |
|
|
50
|
+
| `Contact John Smith` | `Contact <PERSON>` |
|
|
51
|
+
|
|
52
|
+
## Dict Scrubbing
|
|
53
|
+
|
|
54
|
+
Scrub PII from nested dictionaries (e.g., GUI element trees):
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from openadapt_privacy import scrub_dict
|
|
58
|
+
from openadapt_privacy.providers.presidio import PresidioScrubbingProvider
|
|
59
|
+
|
|
60
|
+
scrubber = PresidioScrubbingProvider()
|
|
61
|
+
action = {
|
|
62
|
+
"text": "Email: john@example.com",
|
|
63
|
+
"metadata": {
|
|
64
|
+
"title": "User Profile - John Smith",
|
|
65
|
+
"tooltip": "Click to contact john@example.com",
|
|
66
|
+
},
|
|
67
|
+
"coordinates": {"x": 100, "y": 200},
|
|
68
|
+
}
|
|
69
|
+
scrubbed = scrub_dict(action, scrubber)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Input:**
|
|
73
|
+
```json
|
|
74
|
+
{
|
|
75
|
+
"text": "Email: john@example.com",
|
|
76
|
+
"metadata": {
|
|
77
|
+
"title": "User Profile - John Smith",
|
|
78
|
+
"tooltip": "Click to contact john@example.com"
|
|
79
|
+
},
|
|
80
|
+
"coordinates": {"x": 100, "y": 200}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
**Output:**
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"text": "Email: <EMAIL_ADDRESS>",
|
|
88
|
+
"metadata": {
|
|
89
|
+
"title": "User Profile - <PERSON>",
|
|
90
|
+
"tooltip": "Click to contact <EMAIL_ADDRESS>"
|
|
91
|
+
},
|
|
92
|
+
"coordinates": {"x": 100, "y": 200}
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Recording Pipeline
|
|
97
|
+
|
|
98
|
+
Process complete GUI automation recordings:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from openadapt_privacy import DictRecordingLoader
|
|
102
|
+
from openadapt_privacy.providers.presidio import PresidioScrubbingProvider
|
|
103
|
+
|
|
104
|
+
scrubber = PresidioScrubbingProvider()
|
|
105
|
+
loader = DictRecordingLoader()
|
|
106
|
+
|
|
107
|
+
recording = loader.load_from_dict({
|
|
108
|
+
"task_description": "Send email to John Smith at john@example.com",
|
|
109
|
+
"actions": [
|
|
110
|
+
{"id": 1, "action_type": "click", "text": "Compose", "timestamp": 1000},
|
|
111
|
+
{"id": 2, "action_type": "type", "text": "john@example.com", "timestamp": 2000},
|
|
112
|
+
{"id": 3, "action_type": "click", "text": "Send", "window_title": "Email to john@example.com", "timestamp": 3000},
|
|
113
|
+
],
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
scrubbed = recording.scrub(scrubber)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Input Recording:**
|
|
120
|
+
```
|
|
121
|
+
task_description: "Send email to John Smith at john@example.com"
|
|
122
|
+
|
|
123
|
+
actions:
|
|
124
|
+
[1] click: "Compose"
|
|
125
|
+
[2] type: "john@example.com"
|
|
126
|
+
[3] click: "Send" (window: "Email to john@example.com")
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Output Recording:**
|
|
130
|
+
```
|
|
131
|
+
task_description: "Send email to <PERSON> at <EMAIL_ADDRESS>"
|
|
132
|
+
|
|
133
|
+
actions:
|
|
134
|
+
[1] click: "Compose"
|
|
135
|
+
[2] type: "<EMAIL_ADDRESS>"
|
|
136
|
+
[3] click: "Send" (window: "Email to <EMAIL_ADDRESS>")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Image Scrubbing
|
|
140
|
+
|
|
141
|
+
Redact PII from screenshots using OCR + NER:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
from PIL import Image
|
|
145
|
+
from openadapt_privacy.providers.presidio import PresidioScrubbingProvider
|
|
146
|
+
|
|
147
|
+
scrubber = PresidioScrubbingProvider()
|
|
148
|
+
|
|
149
|
+
image = Image.open("screenshot.png")
|
|
150
|
+
scrubbed_image = scrubber.scrub_image(image)
|
|
151
|
+
scrubbed_image.save("screenshot_scrubbed.png")
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**Input Screenshot:**
|
|
155
|
+
|
|
156
|
+

|
|
157
|
+
|
|
158
|
+
**Output Screenshot:**
|
|
159
|
+
|
|
160
|
+

|
|
161
|
+
|
|
162
|
+
The image redactor:
|
|
163
|
+
1. Runs OCR to detect text regions
|
|
164
|
+
2. Analyzes text for PII entities (email, phone, SSN, etc.)
|
|
165
|
+
3. Fills detected PII regions with solid color (configurable, default: red)
|
|
166
|
+
|
|
167
|
+
## Custom Data Loader
|
|
168
|
+
|
|
169
|
+
Implement your own loader for custom storage formats:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from openadapt_privacy import RecordingLoader, Recording
|
|
173
|
+
|
|
174
|
+
class SQLiteRecordingLoader(RecordingLoader):
|
|
175
|
+
def __init__(self, db_path: str):
|
|
176
|
+
self.db_path = db_path
|
|
177
|
+
|
|
178
|
+
def load(self, recording_id: str) -> Recording:
|
|
179
|
+
# Load from SQLite database
|
|
180
|
+
...
|
|
181
|
+
|
|
182
|
+
def save(self, recording: Recording, recording_id: str) -> None:
|
|
183
|
+
# Save to SQLite database
|
|
184
|
+
...
|
|
185
|
+
|
|
186
|
+
# Usage
|
|
187
|
+
loader = SQLiteRecordingLoader("recordings.db")
|
|
188
|
+
scrubber = PresidioScrubbingProvider()
|
|
189
|
+
|
|
190
|
+
# Load, scrub, and save
|
|
191
|
+
scrubbed = loader.load_and_scrub("recording_001", scrubber)
|
|
192
|
+
loader.save(scrubbed, "recording_001_scrubbed")
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Configuration
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
from openadapt_privacy.config import PrivacyConfig
|
|
199
|
+
|
|
200
|
+
custom_config = PrivacyConfig(
|
|
201
|
+
SCRUB_CHAR="X", # Character for scrub_text_all
|
|
202
|
+
SCRUB_FILL_COLOR=0xFF0000, # Red for image redaction (BGR)
|
|
203
|
+
SCRUB_KEYS_HTML=[ # Keys to scrub in dicts
|
|
204
|
+
"text", "value", "title", "tooltip", "custom_field"
|
|
205
|
+
],
|
|
206
|
+
SCRUB_PRESIDIO_IGNORE_ENTITIES=[ # Entity types to skip
|
|
207
|
+
"DATE_TIME",
|
|
208
|
+
],
|
|
209
|
+
)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Supported Entity Types
|
|
213
|
+
|
|
214
|
+
| Entity | Example Input | Example Output |
|
|
215
|
+
|--------|---------------|----------------|
|
|
216
|
+
| `PERSON` | `John Smith` | `<PERSON>` |
|
|
217
|
+
| `EMAIL_ADDRESS` | `john@example.com` | `<EMAIL_ADDRESS>` |
|
|
218
|
+
| `PHONE_NUMBER` | `555-123-4567` | `<PHONE_NUMBER>` |
|
|
219
|
+
| `US_SSN` | `923-45-6789` | `<US_SSN>` |
|
|
220
|
+
| `CREDIT_CARD` | `4532-1234-5678-9012` | `<CREDIT_CARD>` |
|
|
221
|
+
| `US_BANK_NUMBER` | `635526789012` | `<US_BANK_NUMBER>` |
|
|
222
|
+
| `US_DRIVER_LICENSE` | `A123-456-789-012` | `<US_DRIVER_LICENSE>` |
|
|
223
|
+
| `DATE_TIME` | `01/15/1985` | `<DATE_TIME>` |
|
|
224
|
+
| `LOCATION` | `Toronto, ON` | `<LOCATION>` |
|
|
225
|
+
|
|
226
|
+
## Architecture
|
|
227
|
+
|
|
228
|
+
```
|
|
229
|
+
openadapt_privacy/
|
|
230
|
+
├── base.py # ScrubbingProvider, TextScrubbingMixin
|
|
231
|
+
├── config.py # PrivacyConfig dataclass
|
|
232
|
+
├── loaders.py # Recording, Action, Screenshot, RecordingLoader
|
|
233
|
+
├── providers/
|
|
234
|
+
│ ├── __init__.py # ScrubProvider registry
|
|
235
|
+
│ └── presidio.py # PresidioScrubbingProvider
|
|
236
|
+
└── pipelines/
|
|
237
|
+
└── dicts.py # scrub_dict, scrub_list_dicts
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
MIT
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""OpenAdapt Privacy - PII/PHI detection and redaction for GUI automation data."""
|
|
2
|
+
|
|
3
|
+
from openadapt_privacy.base import (
|
|
4
|
+
Modality,
|
|
5
|
+
ScrubbingProvider,
|
|
6
|
+
ScrubbingProviderFactory,
|
|
7
|
+
TextScrubbingMixin,
|
|
8
|
+
)
|
|
9
|
+
from openadapt_privacy.config import PrivacyConfig, config
|
|
10
|
+
from openadapt_privacy.loaders import (
|
|
11
|
+
Action,
|
|
12
|
+
DictRecordingLoader,
|
|
13
|
+
Recording,
|
|
14
|
+
RecordingLoader,
|
|
15
|
+
Screenshot,
|
|
16
|
+
)
|
|
17
|
+
from openadapt_privacy.pipelines.dicts import DictScrubber, scrub_dict, scrub_list_dicts
|
|
18
|
+
from openadapt_privacy.providers import ScrubProvider
|
|
19
|
+
|
|
20
|
+
__version__ = "0.1.0"
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
# Base classes
|
|
24
|
+
"Modality",
|
|
25
|
+
"ScrubbingProvider",
|
|
26
|
+
"ScrubbingProviderFactory",
|
|
27
|
+
"TextScrubbingMixin",
|
|
28
|
+
# Config
|
|
29
|
+
"PrivacyConfig",
|
|
30
|
+
"config",
|
|
31
|
+
# Providers
|
|
32
|
+
"ScrubProvider",
|
|
33
|
+
# Pipelines
|
|
34
|
+
"DictScrubber",
|
|
35
|
+
"scrub_dict",
|
|
36
|
+
"scrub_list_dicts",
|
|
37
|
+
# Data loaders
|
|
38
|
+
"Action",
|
|
39
|
+
"Screenshot",
|
|
40
|
+
"Recording",
|
|
41
|
+
"RecordingLoader",
|
|
42
|
+
"DictRecordingLoader",
|
|
43
|
+
]
|