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.
Files changed (35) hide show
  1. privacy_flow_inspector-0.1.1/.gitignore +14 -0
  2. privacy_flow_inspector-0.1.1/CHANGELOG.md +44 -0
  3. privacy_flow_inspector-0.1.1/LICENSE +21 -0
  4. privacy_flow_inspector-0.1.1/PKG-INFO +329 -0
  5. privacy_flow_inspector-0.1.1/README.md +291 -0
  6. privacy_flow_inspector-0.1.1/SECURITY.md +25 -0
  7. privacy_flow_inspector-0.1.1/action-requirements.txt +6 -0
  8. privacy_flow_inspector-0.1.1/action.yml +91 -0
  9. privacy_flow_inspector-0.1.1/examples/demo-shop/app.js +76 -0
  10. privacy_flow_inspector-0.1.1/examples/demo-shop/index.html +23 -0
  11. privacy_flow_inspector-0.1.1/examples/pfi.toml +35 -0
  12. privacy_flow_inspector-0.1.1/examples/recorded/demo-shop.har +1 -0
  13. privacy_flow_inspector-0.1.1/pyproject.toml +93 -0
  14. privacy_flow_inspector-0.1.1/scripts/record_demo.py +71 -0
  15. privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/__init__.py +3 -0
  16. privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/__main__.py +3 -0
  17. privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/cli.py +96 -0
  18. privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/detectors.py +367 -0
  19. privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/engine.py +236 -0
  20. privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/extract.py +210 -0
  21. privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/har.py +187 -0
  22. privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/model.py +167 -0
  23. privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/output.py +424 -0
  24. privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/policy.py +244 -0
  25. privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/py.typed +0 -0
  26. privacy_flow_inspector-0.1.1/src/privacy_flow_inspector/recipients.py +71 -0
  27. privacy_flow_inspector-0.1.1/tests/conftest.py +69 -0
  28. privacy_flow_inspector-0.1.1/tests/test_cli.py +67 -0
  29. privacy_flow_inspector-0.1.1/tests/test_detectors.py +145 -0
  30. privacy_flow_inspector-0.1.1/tests/test_engine.py +94 -0
  31. privacy_flow_inspector-0.1.1/tests/test_extract.py +75 -0
  32. privacy_flow_inspector-0.1.1/tests/test_har.py +63 -0
  33. privacy_flow_inspector-0.1.1/tests/test_output.py +106 -0
  34. privacy_flow_inspector-0.1.1/tests/test_policy.py +91 -0
  35. 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
+ [![CI](https://github.com/Sushant-Nemade/privacy-flow-inspector/actions/workflows/ci.yml/badge.svg)](https://github.com/Sushant-Nemade/privacy-flow-inspector/actions/workflows/ci.yml)
42
+ [![GitHub Marketplace](https://img.shields.io/badge/Marketplace-Privacy%20Flow%20Inspector-purple?logo=github)](https://github.com/marketplace/actions/privacy-flow-inspector)
43
+ [![PyPI](https://img.shields.io/pypi/v/privacy-flow-inspector)](https://pypi.org/project/privacy-flow-inspector/)
44
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](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
+ ![Job summary for the fictional demo shop: REJECT, with a data-flow map of which data categories reach each recipient](https://raw.githubusercontent.com/Sushant-Nemade/privacy-flow-inspector/main/docs/images/job-summary.png)
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.