privacy-flow-inspector 0.1.1__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.
- privacy_flow_inspector-0.1.1/.gitignore +14 -0
- privacy_flow_inspector-0.1.1/CHANGELOG.md +44 -0
- privacy_flow_inspector-0.1.1/LICENSE +21 -0
- privacy_flow_inspector-0.1.1/PKG-INFO +329 -0
- privacy_flow_inspector-0.1.1/README.md +291 -0
- privacy_flow_inspector-0.1.1/SECURITY.md +25 -0
- privacy_flow_inspector-0.1.1/action-requirements.txt +6 -0
- privacy_flow_inspector-0.1.1/action.yml +91 -0
- privacy_flow_inspector-0.1.1/examples/demo-shop/app.js +76 -0
- privacy_flow_inspector-0.1.1/examples/demo-shop/index.html +23 -0
- privacy_flow_inspector-0.1.1/examples/pfi.toml +35 -0
- privacy_flow_inspector-0.1.1/examples/recorded/demo-shop.har +1 -0
- privacy_flow_inspector-0.1.1/pyproject.toml +93 -0
- privacy_flow_inspector-0.1.1/scripts/record_demo.py +71 -0
- privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/__init__.py +3 -0
- privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/__main__.py +3 -0
- privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/cli.py +96 -0
- privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/detectors.py +367 -0
- privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/engine.py +236 -0
- privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/extract.py +210 -0
- privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/har.py +187 -0
- privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/model.py +167 -0
- privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/output.py +424 -0
- privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/policy.py +244 -0
- privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/py.typed +0 -0
- privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/recipients.py +71 -0
- privacy_flow_inspector-0.1.1/tests/conftest.py +69 -0
- privacy_flow_inspector-0.1.1/tests/test_cli.py +67 -0
- privacy_flow_inspector-0.1.1/tests/test_detectors.py +145 -0
- privacy_flow_inspector-0.1.1/tests/test_engine.py +94 -0
- privacy_flow_inspector-0.1.1/tests/test_extract.py +75 -0
- privacy_flow_inspector-0.1.1/tests/test_har.py +63 -0
- privacy_flow_inspector-0.1.1/tests/test_output.py +106 -0
- privacy_flow_inspector-0.1.1/tests/test_policy.py +91 -0
- privacy_flow_inspector-0.1.1/tests/test_record.py +31 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
.venv/
|
|
4
|
+
/build/
|
|
5
|
+
/dist/
|
|
6
|
+
*.egg-info/
|
|
7
|
+
.pytest_cache/
|
|
8
|
+
.ruff_cache/
|
|
9
|
+
.mypy_cache/
|
|
10
|
+
reports/
|
|
11
|
+
privacy-flow-reports/
|
|
12
|
+
# Real captures may contain personal data; only the fictional demo capture is committed.
|
|
13
|
+
*.har
|
|
14
|
+
!examples/recorded/demo-shop.har
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are recorded here. The project follows [Semantic Versioning](https://semver.org/);
|
|
4
|
+
while the major version is 0, minor releases may change behaviour.
|
|
5
|
+
|
|
6
|
+
## [0.1.1] - 2026-10-15
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
|
|
10
|
+
- Published on PyPI: `python -m pip install privacy-flow-inspector`. Releases are uploaded by the
|
|
11
|
+
release workflow through PyPI trusted publishing, without a stored token.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- README links are absolute so they also work on the PyPI project page.
|
|
16
|
+
|
|
17
|
+
## [0.1.0] - 2026-10-14
|
|
18
|
+
|
|
19
|
+
First public release.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Offline HAR 1.2 inspection for Chrome, Firefox and Playwright captures, with line and column
|
|
24
|
+
positions for SARIF and Chrome initiator scripts when present.
|
|
25
|
+
- Decoding of URL paths, query strings, fragments, headers, cookies, JSON, form, multipart,
|
|
26
|
+
Base64, JWT claims, nested URLs and percent-encoded values.
|
|
27
|
+
- Test-persona matching including lowercase, URL-encoded, Base64 and MD5/SHA-1/SHA-256 forms,
|
|
28
|
+
and phone digits / E.164.
|
|
29
|
+
- Checked detectors: email, phone, IBAN (mod-97), payment cards (issuer prefix + Luhn), German
|
|
30
|
+
Steuer-ID (ISO 7064 MOD 11,10 + digit rules), German KVNR, health, identity and location fields,
|
|
31
|
+
public IP addresses.
|
|
32
|
+
- Recipient grouping by registrable domain (Public Suffix List), first-party scope, approved
|
|
33
|
+
processors and expiring waivers; APPROVE / REVIEW / REJECT decision with coverage gaps.
|
|
34
|
+
- Reports: JSON, SARIF 2.1.0, Markdown data-flow map, and a self-checked redacted HAR export.
|
|
35
|
+
- Composite GitHub Action and a Playwright recorder for the fictional demo shop.
|
|
36
|
+
|
|
37
|
+
### Known limitations
|
|
38
|
+
|
|
39
|
+
- Only traffic present in the HAR is checked; WebSocket frames and service-worker traffic may be missing.
|
|
40
|
+
- Binary payloads (protobuf, compressed bodies) are not decoded.
|
|
41
|
+
- Findings are technical evidence for a privacy review, not legal advice.
|
|
42
|
+
|
|
43
|
+
[0.1.1]: https://github.com/Sushant-Nemade/privacy-flow-inspector/releases/tag/v0.1.1
|
|
44
|
+
[0.1.0]: https://github.com/Sushant-Nemade/privacy-flow-inspector/releases/tag/v0.1.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sushant Nemade
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: privacy-flow-inspector
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Local privacy gate for recorded web traffic: which personal data leaves the browser, to whom, with redacted evidence.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Sushant-Nemade/privacy-flow-inspector
|
|
6
|
+
Project-URL: Source, https://github.com/Sushant-Nemade/privacy-flow-inspector
|
|
7
|
+
Project-URL: Changelog, https://github.com/Sushant-Nemade/privacy-flow-inspector/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: Issues, https://github.com/Sushant-Nemade/privacy-flow-inspector/issues
|
|
9
|
+
Project-URL: GitHub Marketplace, https://github.com/marketplace/actions/privacy-flow-inspector
|
|
10
|
+
Author: Sushant Nemade
|
|
11
|
+
License-Expression: MIT
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Keywords: data-protection,gdpr,har,pii,playwright,privacy,sarif,tracking
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Environment :: Console
|
|
16
|
+
Classifier: Intended Audience :: Developers
|
|
17
|
+
Classifier: Intended Audience :: Legal Industry
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Security
|
|
24
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.11
|
|
27
|
+
Requires-Dist: publicsuffixlist>=1.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
30
|
+
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
31
|
+
Requires-Dist: pip-audit>=2.7; extra == 'dev'
|
|
32
|
+
Requires-Dist: playwright>=1.48; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest>=8.3; extra == 'dev'
|
|
34
|
+
Requires-Dist: python-stdnum>=1.20; extra == 'dev'
|
|
35
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
36
|
+
Requires-Dist: twine>=6; extra == 'dev'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# Privacy Flow Inspector
|
|
40
|
+
|
|
41
|
+
[](https://github.com/Sushant-Nemade/privacy-flow-inspector/actions/workflows/ci.yml)
|
|
42
|
+
[](https://github.com/marketplace/actions/privacy-flow-inspector)
|
|
43
|
+
[](https://pypi.org/project/privacy-flow-inspector/)
|
|
44
|
+
[](https://github.com/Sushant-Nemade/privacy-flow-inspector/blob/main/LICENSE)
|
|
45
|
+
|
|
46
|
+
**Which personal data leaves the browser, who receives it, and how?**
|
|
47
|
+
|
|
48
|
+
Privacy Flow Inspector reads recorded web traffic (HAR files from Chrome, Firefox or Playwright)
|
|
49
|
+
and reports every place where personal data is sent: in URLs, headers, cookies, JSON, forms,
|
|
50
|
+
Base64 blobs or login tokens, including **hashed** forms that ad pixels use. It groups recipients
|
|
51
|
+
into first and third parties, applies your policy and gives an **APPROVE / REVIEW / REJECT** decision
|
|
52
|
+
for CI. It works offline. Reports contain **no raw personal data**, and it can export a redacted HAR
|
|
53
|
+
that is safe to attach to a ticket.
|
|
54
|
+
|
|
55
|
+
> Status: [v0.1.1](https://github.com/Sushant-Nemade/privacy-flow-inspector/releases/tag/v0.1.1), an alpha
|
|
56
|
+
> release on [PyPI](https://pypi.org/project/privacy-flow-inspector/) and the
|
|
57
|
+
> [GitHub Marketplace](https://github.com/marketplace/actions/privacy-flow-inspector).
|
|
58
|
+
> Tested on Windows, Linux and macOS, including a demo that CI
|
|
59
|
+
> re-records with real Chromium on every run. Findings are technical evidence for a privacy review,
|
|
60
|
+
> not legal advice or a GDPR compliance verdict.
|
|
61
|
+
|
|
62
|
+
Part of a small **software trust toolkit** with
|
|
63
|
+
[Software Intake Gatekeeper](https://github.com/Sushant-Nemade/software-intake-gatekeeper): the
|
|
64
|
+
Gatekeeper checks what is **inside** an app; this tool checks what the app **sends out**. Both use the
|
|
65
|
+
same decision model, policy style and report formats.
|
|
66
|
+
|
|
67
|
+

|
|
68
|
+
|
|
69
|
+
## Where it helps
|
|
70
|
+
|
|
71
|
+
| Who | Situation | What they get |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| QA and end-to-end tests | The Playwright suite runs on every pull request | The build fails if the test user's email reaches an analytics domain |
|
|
74
|
+
| Data protection officer | Before a release, a DPIA or a records-of-processing update | A data-flow map of which data categories go to which recipients |
|
|
75
|
+
| Healthcare and patient portals | Checking for health-data leaks | Alerts when diagnosis fields or German health-insurance numbers reach third parties |
|
|
76
|
+
| Marketing / tag-manager changes | A new pixel or session-replay script is added | Detection of hashed emails and Base64-wrapped form snapshots |
|
|
77
|
+
|
|
78
|
+
## Quick start
|
|
79
|
+
|
|
80
|
+
Install from [PyPI](https://pypi.org/project/privacy-flow-inspector/) (Python 3.11+) and scan a HAR file:
|
|
81
|
+
|
|
82
|
+
```powershell
|
|
83
|
+
python -m pip install privacy-flow-inspector
|
|
84
|
+
|
|
85
|
+
privacy-flow-inspector scan session.har --config pfi.toml `
|
|
86
|
+
--json reports/report.json --sarif reports/report.sarif --markdown reports/summary.md `
|
|
87
|
+
--redact-dir reports/redacted
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Exit status: `0` the decision passed `--fail-on` (default `reject`), `1` the gate failed, `2` usage or
|
|
91
|
+
input error. `privacy-flow-inspector rules` lists the detectors.
|
|
92
|
+
|
|
93
|
+
### Recording a HAR
|
|
94
|
+
|
|
95
|
+
| Tool | How |
|
|
96
|
+
|---|---|
|
|
97
|
+
| Chrome / Edge DevTools | Network panel, then **Export HAR**. The sanitized export leaves out cookies and `Authorization`; those then cannot be checked. |
|
|
98
|
+
| Firefox DevTools | Network panel, then **Save All As HAR**. |
|
|
99
|
+
| Playwright (Python) | `browser.new_context(record_har_path="session.har", record_har_content="embed")` |
|
|
100
|
+
| Playwright (Node) | `browser.newContext({ recordHar: { path: "session.har", content: "embed" } })` |
|
|
101
|
+
|
|
102
|
+
Use `content="embed"`: with `omit` or `attach`, Playwright leaves **request bodies** out of the HAR.
|
|
103
|
+
The inspector detects this and reports a blocking `body-not-recorded` gap instead of approving.
|
|
104
|
+
|
|
105
|
+
### Try the fictional demo
|
|
106
|
+
|
|
107
|
+
[examples/demo-shop](https://github.com/Sushant-Nemade/privacy-flow-inspector/tree/main/examples/demo-shop) is a deliberately leaky, fictional health shop.
|
|
108
|
+
[scripts/record_demo.py](https://github.com/Sushant-Nemade/privacy-flow-inspector/blob/main/scripts/record_demo.py) drives it with real Chromium and answers every
|
|
109
|
+
request locally, so nothing leaves the machine. The recorded capture is committed as
|
|
110
|
+
[examples/recorded/demo-shop.har](https://github.com/Sushant-Nemade/privacy-flow-inspector/blob/main/examples/recorded/demo-shop.har).
|
|
111
|
+
|
|
112
|
+
```powershell
|
|
113
|
+
privacy-flow-inspector scan examples/recorded/demo-shop.har --config examples/pfi.toml
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Real output:
|
|
117
|
+
|
|
118
|
+
```text
|
|
119
|
+
Decision: REJECT
|
|
120
|
+
- 6 data flow(s) at or above high.
|
|
121
|
+
- 4 data flow(s) between low and high.
|
|
122
|
+
Requests: 10 | recipients: 7 (6 third-party) | personas: 3
|
|
123
|
+
Findings: critical 1, high 5, medium 2, low 2, info 4
|
|
124
|
+
[CRITICAL] PFI-PERSONA Test user email sent to legacy-ads.test via query
|
|
125
|
+
[HIGH ] PFI-PRECISE-LOCATION Precise location sent to geo-widgets.test via query
|
|
126
|
+
[HIGH ] PFI-IBAN IBAN sent to payments-processor.test via body (allowed)
|
|
127
|
+
[HIGH ] PFI-PERSONA Test user email sent to pixel-tracker.test via query
|
|
128
|
+
[HIGH ] PFI-PERSONA Test user phone sent to pixel-tracker.test via query
|
|
129
|
+
[HIGH ] PFI-DE-KVNR German health insurance number (KVNR) sent to session-cam.test via body
|
|
130
|
+
[HIGH ] PFI-HEALTH-FIELD Health data field sent to session-cam.test via body
|
|
131
|
+
[MEDIUM ] PFI-PERSONA Test user email sent to analytics-vendor.test via body
|
|
132
|
+
...
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The Markdown summary contains a data-flow map:
|
|
136
|
+
|
|
137
|
+
| Recipient | Party | contact | identity | financial | health | location |
|
|
138
|
+
|---|---|---|---|---|---|---|
|
|
139
|
+
| `legacy-ads.test` | **third** | CRIT | | | | |
|
|
140
|
+
| `geo-widgets.test` | **third** | | | | | HIGH |
|
|
141
|
+
| `payments-processor.test` | **third** | MED (allowed) | | HIGH (allowed) | | |
|
|
142
|
+
| `pixel-tracker.test` Ad pixel (fictional) | **third** | HIGH | | | | |
|
|
143
|
+
| `session-cam.test` Session replay (fictional) | **third** | | | | HIGH | |
|
|
144
|
+
| `analytics-vendor.test` Analytics vendor (fictional) | **third** | MED | MED | | | |
|
|
145
|
+
| `shop.example` | first | info | info | | LOW | |
|
|
146
|
+
|
|
147
|
+
What the demo shows:
|
|
148
|
+
- **Hashed email and phone** sent to an ad pixel (`sha256(plain)`, `sha256(digits)`).
|
|
149
|
+
- An email **inside a nested page URL** sent over **plain HTTP**: critical.
|
|
150
|
+
- A session-replay vendor receiving a **Base64-wrapped form** with a health-insurance number and an
|
|
151
|
+
ICD-10 diagnosis: health data, a GDPR Article 9 special category.
|
|
152
|
+
- A payment processor marked **allowed** because the config lists it as a contracted processor.
|
|
153
|
+
- First-party traffic still shows up (as info/low) so reviewers see the whole picture.
|
|
154
|
+
|
|
155
|
+
## How it works
|
|
156
|
+
|
|
157
|
+
```mermaid
|
|
158
|
+
flowchart LR
|
|
159
|
+
H[HAR files] --> P[Bounded HAR parser]
|
|
160
|
+
P --> X[Value extraction: URL, query, headers, cookies, JSON, form, multipart, Base64, JWT, nested URLs]
|
|
161
|
+
X --> D1[Test-persona matching incl. hashes]
|
|
162
|
+
X --> D2[Checked detectors]
|
|
163
|
+
D1 & D2 --> R[Recipient: eTLD+1, first/third party]
|
|
164
|
+
R --> S[Severity: category, party, channel, transport]
|
|
165
|
+
S --> Pol[Policy: approved processors, expiring waivers]
|
|
166
|
+
Pol --> Dec{APPROVE / REVIEW / REJECT}
|
|
167
|
+
Dec --> O[JSON, SARIF, Markdown, redacted HAR]
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Detectors
|
|
171
|
+
|
|
172
|
+
| Rule | Category | Evidence required |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| `PFI-PERSONA` | as configured | Exact test-persona value, or its lowercase, URL-encoded, Base64, MD5/SHA-1/SHA-256 or phone-digit form |
|
|
175
|
+
| `PFI-EMAIL` | contact | Email pattern (image names such as `icon@2x.png` excluded) |
|
|
176
|
+
| `PFI-PHONE` | contact | Whole value in E.164 format, or a phone-like number in a phone field |
|
|
177
|
+
| `PFI-IBAN` | financial | Country length and mod-97 checksum |
|
|
178
|
+
| `PFI-CARD` | financial | Known issuer prefix, matching length and Luhn checksum |
|
|
179
|
+
| `PFI-DE-TAX-ID` | government-id | Tax-ID field, ISO 7064 MOD 11,10 check digit and the official digit-repetition rules |
|
|
180
|
+
| `PFI-DE-KVNR` | health | German health insurance number with a valid check digit |
|
|
181
|
+
| `PFI-HEALTH-FIELD` | health | Non-empty value in a diagnosis, ICD, medication, symptom, allergy or similar field |
|
|
182
|
+
| `PFI-IDENTITY-FIELD` | identity | Name, birth-date or postal-address field |
|
|
183
|
+
| `PFI-PRECISE-LOCATION` | location | Latitude/longitude with four or more decimals (about 11 m) |
|
|
184
|
+
| `PFI-IP-ADDRESS` | online-identifier | Public IP in an IP field, or a whole-value IPv6 address |
|
|
185
|
+
|
|
186
|
+
The test-persona detector gives the most reliable results. Use a dedicated test account in your
|
|
187
|
+
end-to-end tests, list its values in the config, and every encoded or hashed copy is found with
|
|
188
|
+
essentially no false positives.
|
|
189
|
+
|
|
190
|
+
### Severity
|
|
191
|
+
|
|
192
|
+
Severity starts from the data category and then moves up or down:
|
|
193
|
+
|
|
194
|
+
| Step | Effect |
|
|
195
|
+
|---|---|
|
|
196
|
+
| Start | health, financial, government-id: high; contact, identity, location: medium; online identifier: low |
|
|
197
|
+
| Recipient is first party | two levels lower |
|
|
198
|
+
| Value in a URL, path or `Referer` | one level higher (URLs end up in logs and proxies) |
|
|
199
|
+
| Plain HTTP | one level higher |
|
|
200
|
+
|
|
201
|
+
For example, an email in a JSON body to your own HTTPS API is *info*. The same email in a
|
|
202
|
+
third-party URL over HTTP is *critical*.
|
|
203
|
+
|
|
204
|
+
## Configuration
|
|
205
|
+
|
|
206
|
+
See [examples/pfi.toml](https://github.com/Sushant-Nemade/privacy-flow-inspector/blob/main/examples/pfi.toml):
|
|
207
|
+
|
|
208
|
+
```toml
|
|
209
|
+
version = 1
|
|
210
|
+
|
|
211
|
+
[gate]
|
|
212
|
+
reject_at = "high"
|
|
213
|
+
review_at = "low"
|
|
214
|
+
|
|
215
|
+
[first_party]
|
|
216
|
+
domains = ["shop.example"]
|
|
217
|
+
|
|
218
|
+
[[persona]]
|
|
219
|
+
label = "Test user email"
|
|
220
|
+
value = "jane.test@example.com"
|
|
221
|
+
category = "contact"
|
|
222
|
+
|
|
223
|
+
# Contracted processors (GDPR Art. 28) may receive the listed categories.
|
|
224
|
+
[[allow]]
|
|
225
|
+
recipient = "payments-processor.test"
|
|
226
|
+
categories = ["financial", "contact"]
|
|
227
|
+
reason = "Payment processor with a signed data processing agreement."
|
|
228
|
+
|
|
229
|
+
[[waivers]]
|
|
230
|
+
id = "PFI-IP-ADDRESS"
|
|
231
|
+
recipient = "cdn.example"
|
|
232
|
+
reason = "CDN logs are covered by the hosting contract; reviewed by the DPO."
|
|
233
|
+
expires = 2026-12-31
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Unknown keys are rejected. Persona values must be **invented test data**, never real people.
|
|
237
|
+
Allowances and waivers need written reasons, and waivers expire. Without `[first_party]`, the
|
|
238
|
+
first page's site is used and the report says so.
|
|
239
|
+
|
|
240
|
+
## GitHub Action
|
|
241
|
+
|
|
242
|
+
```yaml
|
|
243
|
+
name: Privacy flows
|
|
244
|
+
on: [pull_request]
|
|
245
|
+
|
|
246
|
+
permissions:
|
|
247
|
+
contents: read
|
|
248
|
+
security-events: write # only for the SARIF upload
|
|
249
|
+
|
|
250
|
+
jobs:
|
|
251
|
+
e2e-privacy:
|
|
252
|
+
runs-on: ubuntu-latest
|
|
253
|
+
steps:
|
|
254
|
+
- uses: actions/checkout@v7
|
|
255
|
+
# ... run your Playwright tests with recordHar content "embed" into test-results/ ...
|
|
256
|
+
- name: Privacy Flow Inspector
|
|
257
|
+
id: privacy
|
|
258
|
+
uses: Sushant-Nemade/privacy-flow-inspector@v0
|
|
259
|
+
with:
|
|
260
|
+
har: test-results/**/*.har
|
|
261
|
+
config: .github/pfi.toml
|
|
262
|
+
fail-on: reject
|
|
263
|
+
- name: Upload SARIF
|
|
264
|
+
if: always() && steps.privacy.outputs.sarif != ''
|
|
265
|
+
uses: github/codeql-action/upload-sarif@v4
|
|
266
|
+
with:
|
|
267
|
+
sarif_file: ${{ steps.privacy.outputs.sarif }}
|
|
268
|
+
category: privacy-flow-inspector
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
| Input | Default | Description |
|
|
272
|
+
|---|---|---|
|
|
273
|
+
| `har` | (required) | HAR path or glob (`**` supported). No match is an error. |
|
|
274
|
+
| `config` | `""` | TOML config (first party, personas, allowances, waivers). |
|
|
275
|
+
| `fail-on` | `reject` | `reject`, `review` or `never`. |
|
|
276
|
+
| `redact` | `true` | Also write redacted HAR copies to the output directory. |
|
|
277
|
+
| `output-dir` | `privacy-flow-reports` | Where `report.json`, `report.sarif`, `summary.md` and `redacted/` go. |
|
|
278
|
+
|
|
279
|
+
Outputs: `decision` and `sarif`. The Markdown summary is added to the job summary. The action uses
|
|
280
|
+
hash-pinned dependencies in a private virtual environment, needs no token and makes no network
|
|
281
|
+
requests. Do not upload raw HAR files as workflow artifacts; upload the redacted copies instead.
|
|
282
|
+
|
|
283
|
+
## Privacy by design
|
|
284
|
+
|
|
285
|
+
- **Offline**: no network access and no telemetry.
|
|
286
|
+
- **No raw values in reports**: each value appears only as a short mask (`ja********om`) and a keyed
|
|
287
|
+
fingerprint, so the same value can be correlated within one report without being revealed.
|
|
288
|
+
- **Redacted HAR export**: detected values and their encoded forms are replaced, `Authorization`,
|
|
289
|
+
`Cookie` and `Set-Cookie` values are removed, and response bodies are dropped. The file is
|
|
290
|
+
re-checked and not written if anything would remain.
|
|
291
|
+
- **Bounded parsing** of untrusted input, and escaping of all capture-derived text in Markdown.
|
|
292
|
+
|
|
293
|
+
## Verification
|
|
294
|
+
|
|
295
|
+
| Check | How |
|
|
296
|
+
|---|---|
|
|
297
|
+
| Real browser traffic | CI re-records the demo with Playwright Chromium on every run and requires exactly the expected data flows |
|
|
298
|
+
| Leak-free outputs | A test checks that no persona value, nor any of its encoded or hashed forms, appears in the JSON, SARIF, Markdown, text or redacted HAR |
|
|
299
|
+
| Checksums | Steuer-ID, IBAN and Luhn compared with `python-stdnum` on 100,000+ random numbers. The only difference is numbers with three identical digits in a row, which the official Steuer-ID rules forbid and `stdnum` does not check. KVNR uses the published example `A123456780`. |
|
|
300
|
+
| False positives | A corpus of common tracking values (timestamps, UUIDs, ETags, version numbers, private IPs, invalid checksums) must produce no findings |
|
|
301
|
+
| Action | Runs on Ubuntu, Windows and macOS against the demo: decision, SARIF paths, redacted copy, `fail-on` and the no-match error |
|
|
302
|
+
| Code quality | pytest, ruff, mypy `--strict`, build, twine and pip-audit on Python 3.11 and 3.13 |
|
|
303
|
+
|
|
304
|
+
## Limitations
|
|
305
|
+
|
|
306
|
+
- Only what the HAR contains is checked. WebSocket messages, service-worker traffic and requests
|
|
307
|
+
made before recording started may be missing.
|
|
308
|
+
- Binary or compressed payloads (protobuf, gzip in JSON) are not decoded.
|
|
309
|
+
- Field-name detectors depend on meaningful field names. The test-persona detector does not.
|
|
310
|
+
- Domains are grouped by registrable domain; a vendor serving from your own subdomain (CNAME
|
|
311
|
+
cloaking) looks first-party. Name it under `[labels]` and review it.
|
|
312
|
+
- A finding is evidence for a privacy review. Whether a flow is lawful depends on purpose, consent
|
|
313
|
+
and contracts, which the tool cannot see.
|
|
314
|
+
|
|
315
|
+
## Development
|
|
316
|
+
|
|
317
|
+
```powershell
|
|
318
|
+
python -m pip install uv
|
|
319
|
+
uv sync --frozen --extra dev
|
|
320
|
+
uv run --frozen --extra dev pytest -q
|
|
321
|
+
uv run --frozen --extra dev playwright install chromium # optional, for the recording test
|
|
322
|
+
$env:PFI_RECORD = "1"; uv run --frozen --extra dev pytest -q tests/test_record.py
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
## License
|
|
326
|
+
|
|
327
|
+
[MIT](https://github.com/Sushant-Nemade/privacy-flow-inspector/blob/main/LICENSE) © 2026 Sushant Nemade. See [SECURITY.md](https://github.com/Sushant-Nemade/privacy-flow-inspector/blob/main/SECURITY.md) for vulnerability reporting.
|
|
328
|
+
Recipient grouping uses [publicsuffixlist](https://pypi.org/project/publicsuffixlist/) (MPL-2.0)
|
|
329
|
+
with the bundled Public Suffix List.
|