datafog-core 0.4.0__tar.gz → 0.4.2__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.
- {datafog_core-0.4.0 → datafog_core-0.4.2}/Cargo.lock +4 -4
- {datafog_core-0.4.0 → datafog_core-0.4.2}/PKG-INFO +15 -9
- {datafog_core-0.4.0/crates/core → datafog_core-0.4.2}/README.md +14 -8
- {datafog_core-0.4.0 → datafog_core-0.4.2}/bindings/python/Cargo.toml +1 -1
- datafog_core-0.4.2/bindings/python/tests/api_key_conformance.py +122 -0
- datafog_core-0.4.2/bindings/python/tests/bearer_token_conformance.py +134 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/bindings/python/tests/capabilities_conformance.py +1 -1
- datafog_core-0.4.2/bindings/python/tests/credential_overlap_conformance.py +103 -0
- datafog_core-0.4.2/bindings/python/tests/credential_uri_conformance.py +127 -0
- datafog_core-0.4.2/bindings/python/tests/email_context_conformance.py +49 -0
- datafog_core-0.4.2/bindings/python/tests/synthetic_fixtures.py +24 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/bindings/python/tests/test_installed.py +13 -0
- datafog_core-0.4.2/bindings/python/tests/test_python49_integration.py +204 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/bindings/python/tests/test_typing.py +2 -1
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/Cargo.toml +1 -1
- {datafog_core-0.4.0 → datafog_core-0.4.2/crates/core}/README.md +14 -8
- datafog_core-0.4.2/crates/core/src/api_key.rs +66 -0
- datafog_core-0.4.2/crates/core/src/bearer_token.rs +87 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/src/capabilities.rs +8 -1
- datafog_core-0.4.2/crates/core/src/credential_uri.rs +200 -0
- datafog_core-0.4.2/crates/core/src/email_context.rs +172 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/src/lib.rs +151 -3
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/src/structured.rs +13 -1
- datafog_core-0.4.2/crates/core/tests/api_key.rs +120 -0
- datafog_core-0.4.2/crates/core/tests/bearer_token.rs +113 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/tests/capabilities.rs +8 -3
- datafog_core-0.4.2/crates/core/tests/credential_overlaps.rs +113 -0
- datafog_core-0.4.2/crates/core/tests/credential_uri.rs +96 -0
- datafog_core-0.4.2/crates/core/tests/email_context.rs +107 -0
- datafog_core-0.4.2/crates/core/tests/support/synthetic_fixtures.rs +32 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/pyproject.toml +1 -1
- {datafog_core-0.4.0 → datafog_core-0.4.2}/python/datafog_core/__init__.pyi +1 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/Cargo.toml +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/bindings/python/src/lib.rs +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/bindings/python/tests/german_conformance.py +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/bindings/python/tests/jwt_conformance.py +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/bindings/python/tests/npi_conformance.py +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/bindings/python/tests/private_key_conformance.py +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/bindings/python/tests/requirements-typing.txt +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/bindings/python/tests/us_routing_number_conformance.py +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/bindings/python/tests/uuid_conformance.py +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/examples/german_benchmark.rs +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/examples/scan_benchmark.rs +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/src/german.rs +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/src/jwt.rs +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/src/npi.rs +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/src/offsets.rs +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/src/private_key.rs +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/src/selection_tests.rs +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/src/us_routing_number.rs +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/src/uuid.rs +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/tests/german.rs +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/tests/jwt.rs +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/tests/npi.rs +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/tests/private_key.rs +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/tests/us_routing_number.rs +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/crates/core/tests/uuid.rs +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/python/datafog_core/__init__.py +0 -0
- {datafog_core-0.4.0 → datafog_core-0.4.2}/python/datafog_core/py.typed +0 -0
|
@@ -80,7 +80,7 @@ checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d"
|
|
|
80
80
|
|
|
81
81
|
[[package]]
|
|
82
82
|
name = "datafog-core"
|
|
83
|
-
version = "0.4.
|
|
83
|
+
version = "0.4.2"
|
|
84
84
|
dependencies = [
|
|
85
85
|
"base64",
|
|
86
86
|
"futures",
|
|
@@ -94,7 +94,7 @@ dependencies = [
|
|
|
94
94
|
|
|
95
95
|
[[package]]
|
|
96
96
|
name = "datafog-core-python"
|
|
97
|
-
version = "0.4.
|
|
97
|
+
version = "0.4.2"
|
|
98
98
|
dependencies = [
|
|
99
99
|
"datafog-core",
|
|
100
100
|
"pyo3",
|
|
@@ -104,7 +104,7 @@ dependencies = [
|
|
|
104
104
|
|
|
105
105
|
[[package]]
|
|
106
106
|
name = "datafog-node"
|
|
107
|
-
version = "0.4.
|
|
107
|
+
version = "0.4.2"
|
|
108
108
|
dependencies = [
|
|
109
109
|
"datafog-core",
|
|
110
110
|
"napi",
|
|
@@ -115,7 +115,7 @@ dependencies = [
|
|
|
115
115
|
|
|
116
116
|
[[package]]
|
|
117
117
|
name = "datafog-wasm"
|
|
118
|
-
version = "0.4.
|
|
118
|
+
version = "0.4.2"
|
|
119
119
|
dependencies = [
|
|
120
120
|
"datafog-core",
|
|
121
121
|
"serde",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: datafog-core
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: License :: OSI Approved :: MIT License
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -25,7 +25,7 @@ Project-URL: Repository, https://github.com/DataFog/datafog-core
|
|
|
25
25
|
|
|
26
26
|
Fast structured PII detection, implemented in Rust and exposed for Rust, Python, Node.js, and browsers.
|
|
27
27
|
|
|
28
|
-
It detects `EMAIL`, `PHONE`, `SSN`, `CREDIT_CARD`, `IP_ADDRESS`, `DATE`, and `ZIP_CODE`.
|
|
28
|
+
It detects `EMAIL`, `PHONE`, `SSN`, `CREDIT_CARD`, `IP_ADDRESS`, `DATE`, and `ZIP_CODE`. Version 0.4.0 also detects `JWT` tokens by default; see the [JWT reference](docs/reference/jwt.mdx). With an explicit German locale, it also detects `DE_IBAN`, `DE_VAT_ID`, `DE_TAX_ID`, `DE_SOCIAL_SECURITY_NUMBER`, `DE_POSTAL_CODE`, `DE_PASSPORT_NUMBER`, and `DE_RESIDENCE_PERMIT_NUMBER`. Complete PEM private-key blocks are also detected as `PRIVATE_KEY` by default; see the [private-key reference](docs/reference/private-keys.mdx). Context-labeled `US_ROUTING_NUMBER` detection is also available; see [the detector rules](docs/reference/us-routing-number.mdx). Context-labeled `NPI` detection is also available; see [the detector rules](docs/reference/npi.mdx). Every binding returns the same finding information:
|
|
29
29
|
|
|
30
30
|
```text
|
|
31
31
|
entity type, matched text, byte range, code-point range,
|
|
@@ -79,8 +79,12 @@ full-match regex values:
|
|
|
79
79
|
}
|
|
80
80
|
```
|
|
81
81
|
|
|
82
|
-
`scan_and_transform` uses `{ scan?: { locale?: string, detect_uuid?: boolean }, transform: ... }` so
|
|
82
|
+
`scan_and_transform` uses `{ scan?: { locale?: string, detect_uuid?: boolean, format?: "text" | "env" | "sql" }, transform: ... }` so
|
|
83
83
|
detection settings remain separate from transformation policy.
|
|
84
|
+
For `.env` assignments or standard SQL quoted strings, select `format: "env"`
|
|
85
|
+
or `format: "sql"` to preserve email boundaries. See the
|
|
86
|
+
[email source-context policy](docs/guides/email-boundaries.mdx) for supported
|
|
87
|
+
syntax, raw escaped spans, and limitations. Default text scanning is unchanged.
|
|
84
88
|
|
|
85
89
|
## Packages
|
|
86
90
|
|
|
@@ -297,12 +301,12 @@ fixtures/ Shared conformance fixtures
|
|
|
297
301
|
|
|
298
302
|
[MIT](LICENSE)
|
|
299
303
|
|
|
300
|
-
## German structured identifiers
|
|
304
|
+
## German structured identifiers
|
|
301
305
|
|
|
302
306
|
Pass `{"locale":"de"}` to text or structured scans. Trimmed, ASCII
|
|
303
307
|
case-insensitive `de`, `de-DE`, and `de_DE` activate all seven German detectors;
|
|
304
308
|
omitted locale and recognized `en-US`/`fr` aliases keep base detection only.
|
|
305
|
-
|
|
309
|
+
Version 0.4.0 rejects unsupported explicit locales.
|
|
306
310
|
|
|
307
311
|
```python
|
|
308
312
|
from datafog_core import scan_and_transform
|
|
@@ -321,10 +325,10 @@ boundaries, never newlines. Returned text and offsets preserve the source.
|
|
|
321
325
|
Passport and residence-permit patterns are legacy heuristics with limited
|
|
322
326
|
coverage. See the [German entity reference](docs/reference/german-entities.mdx)
|
|
323
327
|
and [migration differences](docs/guides/migrating-from-datafog-python.mdx).
|
|
324
|
-
The Python
|
|
325
|
-
|
|
328
|
+
The higher-level Python adapter and its dependency update were merged in
|
|
329
|
+
[PR #179](https://github.com/DataFog/datafog-python/pull/179). Its package release is separate from Core.
|
|
326
330
|
|
|
327
|
-
## UUID identifiers
|
|
331
|
+
## UUID identifiers
|
|
328
332
|
|
|
329
333
|
Canonical UUID detection is opt-in: pass `{"detect_uuid":true}` to text or
|
|
330
334
|
structured scans, independently of locale. It emits `UUID` findings for versions
|
|
@@ -332,5 +336,7 @@ structured scans, independently of locale. It emits `UUID` findings for versions
|
|
|
332
336
|
sensitivity. See the [UUID reference](docs/reference/uuid.mdx) for boundaries,
|
|
333
337
|
excluded sentinel forms and transformation examples.
|
|
334
338
|
|
|
335
|
-
|
|
339
|
+
**0.4.1 is published** for Rust, Python, Node.js, and WASM. The higher-level Python adapter is integrated in [merged PR #179](https://github.com/DataFog/datafog-python/pull/179); its package release is separate. See the [release notes](docs/releases/0-4-1.mdx), [runtime capabilities](docs/reference/capabilities.mdx), and [0.4.x compatibility policy](docs/reference/compatibility.mdx).
|
|
340
|
+
|
|
341
|
+
**Core 0.4.1** adds three default detectors: provider-prefixed [`API_KEY`](docs/reference/api-keys.mdx), context-required [`BEARER_TOKEN`](docs/reference/bearer-token.mdx), and whole PostgreSQL [`CREDENTIAL_URI`](docs/reference/credential-uri.mdx). The registry reports 23 supported entities and 14 defaults with capability contract 1. See the [release notes](docs/releases/0-4-1.mdx) for exact scopes, preserved legacy Python overlap behavior, and the passed local exact-wheel Python 4.9 integration gate. Python's default backend is unchanged.
|
|
336
342
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Fast structured PII detection, implemented in Rust and exposed for Rust, Python, Node.js, and browsers.
|
|
4
4
|
|
|
5
|
-
It detects `EMAIL`, `PHONE`, `SSN`, `CREDIT_CARD`, `IP_ADDRESS`, `DATE`, and `ZIP_CODE`.
|
|
5
|
+
It detects `EMAIL`, `PHONE`, `SSN`, `CREDIT_CARD`, `IP_ADDRESS`, `DATE`, and `ZIP_CODE`. Version 0.4.0 also detects `JWT` tokens by default; see the [JWT reference](docs/reference/jwt.mdx). With an explicit German locale, it also detects `DE_IBAN`, `DE_VAT_ID`, `DE_TAX_ID`, `DE_SOCIAL_SECURITY_NUMBER`, `DE_POSTAL_CODE`, `DE_PASSPORT_NUMBER`, and `DE_RESIDENCE_PERMIT_NUMBER`. Complete PEM private-key blocks are also detected as `PRIVATE_KEY` by default; see the [private-key reference](docs/reference/private-keys.mdx). Context-labeled `US_ROUTING_NUMBER` detection is also available; see [the detector rules](docs/reference/us-routing-number.mdx). Context-labeled `NPI` detection is also available; see [the detector rules](docs/reference/npi.mdx). Every binding returns the same finding information:
|
|
6
6
|
|
|
7
7
|
```text
|
|
8
8
|
entity type, matched text, byte range, code-point range,
|
|
@@ -56,8 +56,12 @@ full-match regex values:
|
|
|
56
56
|
}
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
-
`scan_and_transform` uses `{ scan?: { locale?: string, detect_uuid?: boolean }, transform: ... }` so
|
|
59
|
+
`scan_and_transform` uses `{ scan?: { locale?: string, detect_uuid?: boolean, format?: "text" | "env" | "sql" }, transform: ... }` so
|
|
60
60
|
detection settings remain separate from transformation policy.
|
|
61
|
+
For `.env` assignments or standard SQL quoted strings, select `format: "env"`
|
|
62
|
+
or `format: "sql"` to preserve email boundaries. See the
|
|
63
|
+
[email source-context policy](docs/guides/email-boundaries.mdx) for supported
|
|
64
|
+
syntax, raw escaped spans, and limitations. Default text scanning is unchanged.
|
|
61
65
|
|
|
62
66
|
## Packages
|
|
63
67
|
|
|
@@ -274,12 +278,12 @@ fixtures/ Shared conformance fixtures
|
|
|
274
278
|
|
|
275
279
|
[MIT](LICENSE)
|
|
276
280
|
|
|
277
|
-
## German structured identifiers
|
|
281
|
+
## German structured identifiers
|
|
278
282
|
|
|
279
283
|
Pass `{"locale":"de"}` to text or structured scans. Trimmed, ASCII
|
|
280
284
|
case-insensitive `de`, `de-DE`, and `de_DE` activate all seven German detectors;
|
|
281
285
|
omitted locale and recognized `en-US`/`fr` aliases keep base detection only.
|
|
282
|
-
|
|
286
|
+
Version 0.4.0 rejects unsupported explicit locales.
|
|
283
287
|
|
|
284
288
|
```python
|
|
285
289
|
from datafog_core import scan_and_transform
|
|
@@ -298,10 +302,10 @@ boundaries, never newlines. Returned text and offsets preserve the source.
|
|
|
298
302
|
Passport and residence-permit patterns are legacy heuristics with limited
|
|
299
303
|
coverage. See the [German entity reference](docs/reference/german-entities.mdx)
|
|
300
304
|
and [migration differences](docs/guides/migrating-from-datafog-python.mdx).
|
|
301
|
-
The Python
|
|
302
|
-
|
|
305
|
+
The higher-level Python adapter and its dependency update were merged in
|
|
306
|
+
[PR #179](https://github.com/DataFog/datafog-python/pull/179). Its package release is separate from Core.
|
|
303
307
|
|
|
304
|
-
## UUID identifiers
|
|
308
|
+
## UUID identifiers
|
|
305
309
|
|
|
306
310
|
Canonical UUID detection is opt-in: pass `{"detect_uuid":true}` to text or
|
|
307
311
|
structured scans, independently of locale. It emits `UUID` findings for versions
|
|
@@ -309,4 +313,6 @@ structured scans, independently of locale. It emits `UUID` findings for versions
|
|
|
309
313
|
sensitivity. See the [UUID reference](docs/reference/uuid.mdx) for boundaries,
|
|
310
314
|
excluded sentinel forms and transformation examples.
|
|
311
315
|
|
|
312
|
-
|
|
316
|
+
**0.4.1 is published** for Rust, Python, Node.js, and WASM. The higher-level Python adapter is integrated in [merged PR #179](https://github.com/DataFog/datafog-python/pull/179); its package release is separate. See the [release notes](docs/releases/0-4-1.mdx), [runtime capabilities](docs/reference/capabilities.mdx), and [0.4.x compatibility policy](docs/reference/compatibility.mdx).
|
|
317
|
+
|
|
318
|
+
**Core 0.4.1** adds three default detectors: provider-prefixed [`API_KEY`](docs/reference/api-keys.mdx), context-required [`BEARER_TOKEN`](docs/reference/bearer-token.mdx), and whole PostgreSQL [`CREDENTIAL_URI`](docs/reference/credential-uri.mdx). The registry reports 23 supported entities and 14 defaults with capability contract 1. See the [release notes](docs/releases/0-4-1.mdx) for exact scopes, preserved legacy Python overlap behavior, and the passed local exact-wheel Python 4.9 integration gate. Python's default backend is unchanged.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Shared API-key fixtures against the installed wheel, including provider round trips."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import datafog_core as api
|
|
6
|
+
from synthetic_fixtures import expand_fixture
|
|
7
|
+
|
|
8
|
+
RECORDS = [
|
|
9
|
+
expand_fixture(json.loads(line))
|
|
10
|
+
for line in (Path(__file__).resolve().parents[3] / "fixtures/api-key.jsonl")
|
|
11
|
+
.read_text()
|
|
12
|
+
.splitlines()
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def project(text, findings):
|
|
17
|
+
result = []
|
|
18
|
+
for f in findings:
|
|
19
|
+
if f.entity_type != "API_KEY":
|
|
20
|
+
continue
|
|
21
|
+
assert (
|
|
22
|
+
text.encode()[f.byte_range.start : f.byte_range.end].decode()
|
|
23
|
+
== f.matched_text
|
|
24
|
+
)
|
|
25
|
+
assert text[f.codepoint_range.start : f.codepoint_range.end] == f.matched_text
|
|
26
|
+
assert f.confidence is None
|
|
27
|
+
assert f.detector_name == "datafog-core/" + f.entity_type.lower().replace(
|
|
28
|
+
"_", "-"
|
|
29
|
+
)
|
|
30
|
+
assert f.detector_version
|
|
31
|
+
result.append(
|
|
32
|
+
dict(
|
|
33
|
+
label=f.entity_type,
|
|
34
|
+
text=f.matched_text,
|
|
35
|
+
start=f.codepoint_range.start,
|
|
36
|
+
end=f.codepoint_range.end,
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
return result
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def verify():
|
|
43
|
+
for row in RECORDS:
|
|
44
|
+
findings = api.scan(row["text"], row["config"])
|
|
45
|
+
assert project(row["text"], findings) == row["entities"], row["id"]
|
|
46
|
+
data = {"a/b": row["text"], "array": [row["text"]], "z": row["text"]}
|
|
47
|
+
located = api.scan_structured(data, row["config"]).findings
|
|
48
|
+
for path in ["/a~1b", "/array/0", "/z"]:
|
|
49
|
+
assert (
|
|
50
|
+
project(row["text"], [f.finding for f in located if f.path == path])
|
|
51
|
+
== row["entities"]
|
|
52
|
+
), row["id"]
|
|
53
|
+
for case in row.get("transforms", []):
|
|
54
|
+
result = api.scan_and_transform(
|
|
55
|
+
row["text"], {"scan": row["config"], "transform": case["config"]}
|
|
56
|
+
)
|
|
57
|
+
assert result.text == case["text"], row["id"]
|
|
58
|
+
assert api.transform(row["text"], findings, case["config"]) == result
|
|
59
|
+
for t in result.transformations:
|
|
60
|
+
assert (
|
|
61
|
+
result.text.encode()[
|
|
62
|
+
t.output_byte_range.start : t.output_byte_range.end
|
|
63
|
+
].decode()
|
|
64
|
+
== t.replacement
|
|
65
|
+
)
|
|
66
|
+
assert (
|
|
67
|
+
result.text[
|
|
68
|
+
t.output_codepoint_range.start : t.output_codepoint_range.end
|
|
69
|
+
]
|
|
70
|
+
== t.replacement
|
|
71
|
+
)
|
|
72
|
+
assert any(
|
|
73
|
+
f.byte_range == t.source_byte_range
|
|
74
|
+
and f.entity_type == t.entity_type
|
|
75
|
+
for f in findings
|
|
76
|
+
)
|
|
77
|
+
assert not hasattr(t, "matched_text") and not hasattr(t, "finding")
|
|
78
|
+
structured = api.scan_and_transform_structured(
|
|
79
|
+
data, {"scan": row["config"], "transform": case["config"]}
|
|
80
|
+
)
|
|
81
|
+
assert structured.data == {
|
|
82
|
+
"a/b": case["text"],
|
|
83
|
+
"array": [case["text"]],
|
|
84
|
+
"z": case["text"],
|
|
85
|
+
}
|
|
86
|
+
explicit = api.transform_structured(data, located, case["config"])
|
|
87
|
+
assert explicit.data == structured.data
|
|
88
|
+
assert [(t.path, t.transformation) for t in explicit.transformations] == [
|
|
89
|
+
(t.path, t.transformation) for t in structured.transformations
|
|
90
|
+
]
|
|
91
|
+
async def verify_providers(manager, token_manager, context):
|
|
92
|
+
for row in RECORDS:
|
|
93
|
+
if not row.get("sample"):
|
|
94
|
+
continue
|
|
95
|
+
entities = list(dict.fromkeys(e["label"] for e in row["entities"]))
|
|
96
|
+
pseudonyms = await manager.scan_and_transform(
|
|
97
|
+
row["text"],
|
|
98
|
+
{
|
|
99
|
+
"scan": row["config"],
|
|
100
|
+
"transform": {
|
|
101
|
+
"default": {"strategy": "pseudonymize", "key_ref": "german"},
|
|
102
|
+
"entities": entities,
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
)
|
|
106
|
+
assert (
|
|
107
|
+
len(pseudonyms.transformations) == len(row["entities"])
|
|
108
|
+
and pseudonyms.text != row["text"]
|
|
109
|
+
)
|
|
110
|
+
tokens = await token_manager.scan_and_transform(
|
|
111
|
+
row["text"],
|
|
112
|
+
{
|
|
113
|
+
"scan": row["config"],
|
|
114
|
+
"transform": {
|
|
115
|
+
"default": {"strategy": "tokenize", "token_ref": "german"},
|
|
116
|
+
"entities": entities,
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
context,
|
|
120
|
+
)
|
|
121
|
+
assert len(tokens.transformations) == len(row["entities"])
|
|
122
|
+
assert (await token_manager.restore(tokens.text, context)).text == row["text"]
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Shared BEARER_TOKEN fixtures against the installed wheel, including provider round trips."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import datafog_core as api
|
|
6
|
+
|
|
7
|
+
RECORDS = [
|
|
8
|
+
json.loads(line)
|
|
9
|
+
for line in (Path(__file__).resolve().parents[3] / "fixtures/bearer-token.jsonl")
|
|
10
|
+
.read_text()
|
|
11
|
+
.splitlines()
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def project(text, findings):
|
|
16
|
+
result = []
|
|
17
|
+
for f in findings:
|
|
18
|
+
if not f.entity_type == "BEARER_TOKEN":
|
|
19
|
+
continue
|
|
20
|
+
assert (
|
|
21
|
+
text.encode()[f.byte_range.start : f.byte_range.end].decode()
|
|
22
|
+
== f.matched_text
|
|
23
|
+
)
|
|
24
|
+
assert text[f.codepoint_range.start : f.codepoint_range.end] == f.matched_text
|
|
25
|
+
assert f.confidence is None
|
|
26
|
+
assert f.detector_name == "datafog-core/" + f.entity_type.lower().replace(
|
|
27
|
+
"_", "-"
|
|
28
|
+
)
|
|
29
|
+
assert f.detector_version
|
|
30
|
+
result.append(
|
|
31
|
+
dict(
|
|
32
|
+
label=f.entity_type,
|
|
33
|
+
text=f.matched_text,
|
|
34
|
+
start=f.codepoint_range.start,
|
|
35
|
+
end=f.codepoint_range.end,
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
return result
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def verify():
|
|
42
|
+
for row in RECORDS:
|
|
43
|
+
findings = api.scan(row["text"], row["config"])
|
|
44
|
+
if "overlap" in row:
|
|
45
|
+
assert any(f.entity_type == row["overlap"] for f in findings)
|
|
46
|
+
assert project(row["text"], findings) == row["entities"], row["id"]
|
|
47
|
+
data = {"a/b": row["text"], "array": [row["text"]], "z": row["text"]}
|
|
48
|
+
located = api.scan_structured(data, row["config"]).findings
|
|
49
|
+
for path in ["/a~1b", "/array/0", "/z"]:
|
|
50
|
+
assert (
|
|
51
|
+
project(row["text"], [f.finding for f in located if f.path == path])
|
|
52
|
+
== row["entities"]
|
|
53
|
+
), row["id"]
|
|
54
|
+
for case in row.get("transforms", []):
|
|
55
|
+
result = api.scan_and_transform(
|
|
56
|
+
row["text"], {"scan": row["config"], "transform": case["config"]}
|
|
57
|
+
)
|
|
58
|
+
assert result.text == case["text"], row["id"]
|
|
59
|
+
assert api.transform(row["text"], findings, case["config"]) == result
|
|
60
|
+
for t in result.transformations:
|
|
61
|
+
assert (
|
|
62
|
+
result.text.encode()[
|
|
63
|
+
t.output_byte_range.start : t.output_byte_range.end
|
|
64
|
+
].decode()
|
|
65
|
+
== t.replacement
|
|
66
|
+
)
|
|
67
|
+
assert (
|
|
68
|
+
result.text[
|
|
69
|
+
t.output_codepoint_range.start : t.output_codepoint_range.end
|
|
70
|
+
]
|
|
71
|
+
== t.replacement
|
|
72
|
+
)
|
|
73
|
+
assert any(
|
|
74
|
+
f.byte_range == t.source_byte_range
|
|
75
|
+
and f.entity_type == t.entity_type
|
|
76
|
+
for f in findings
|
|
77
|
+
)
|
|
78
|
+
assert not hasattr(t, "matched_text") and not hasattr(t, "finding")
|
|
79
|
+
structured = api.scan_and_transform_structured(
|
|
80
|
+
data, {"scan": row["config"], "transform": case["config"]}
|
|
81
|
+
)
|
|
82
|
+
assert structured.data == {
|
|
83
|
+
"a/b": case["text"],
|
|
84
|
+
"array": [case["text"]],
|
|
85
|
+
"z": case["text"],
|
|
86
|
+
}
|
|
87
|
+
explicit = api.transform_structured(data, located, case["config"])
|
|
88
|
+
assert explicit.data == structured.data
|
|
89
|
+
assert [(t.path, t.transformation) for t in explicit.transformations] == [
|
|
90
|
+
(t.path, t.transformation) for t in structured.transformations
|
|
91
|
+
]
|
|
92
|
+
|
|
93
|
+
data = {"👋/~":{"aUtHoRiZaTiOn":" bEaReR abc._~+/==\t"}, "Authorization":["Bearer hidden"], "X-Authorization":"Bearer hidden", "a":"Authorization:", "b":"Bearer hidden", "invalid":{"Authorization":"Bearer bad!value"}}
|
|
94
|
+
found = [f for f in api.scan_structured(data).findings if f.finding.entity_type == "BEARER_TOKEN"]
|
|
95
|
+
assert len(found) == 1
|
|
96
|
+
assert found[0].path == "/👋~1~0/aUtHoRiZaTiOn"
|
|
97
|
+
assert project(data["👋/~"]["aUtHoRiZaTiOn"], [found[0].finding]) == [dict(label="BEARER_TOKEN",text="abc._~+/==",start=9,end=19)]
|
|
98
|
+
expected = {**data,"👋/~":{"aUtHoRiZaTiOn":" bEaReR [BEARER_TOKEN]\t"}}
|
|
99
|
+
assert api.scan_and_transform_structured(data,{"transform":{"default":{"strategy":"redact"},"entities":["BEARER_TOKEN"]}}).data == expected
|
|
100
|
+
assert not any(f.entity_type == "BEARER_TOKEN" for f in api.scan("Bearer abc123"))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
async def verify_providers(manager, token_manager, context):
|
|
104
|
+
for row in RECORDS:
|
|
105
|
+
if not row.get("sample"):
|
|
106
|
+
continue
|
|
107
|
+
entities = list(dict.fromkeys(e["label"] for e in row["entities"]))
|
|
108
|
+
pseudonyms = await manager.scan_and_transform(
|
|
109
|
+
row["text"],
|
|
110
|
+
{
|
|
111
|
+
"scan": row["config"],
|
|
112
|
+
"transform": {
|
|
113
|
+
"default": {"strategy": "pseudonymize", "key_ref": "jwt"},
|
|
114
|
+
"entities": entities,
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
)
|
|
118
|
+
assert (
|
|
119
|
+
len(pseudonyms.transformations) == len(row["entities"])
|
|
120
|
+
and pseudonyms.text != row["text"]
|
|
121
|
+
)
|
|
122
|
+
tokens = await token_manager.scan_and_transform(
|
|
123
|
+
row["text"],
|
|
124
|
+
{
|
|
125
|
+
"scan": row["config"],
|
|
126
|
+
"transform": {
|
|
127
|
+
"default": {"strategy": "tokenize", "token_ref": "jwt"},
|
|
128
|
+
"entities": entities,
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
context,
|
|
132
|
+
)
|
|
133
|
+
assert len(tokens.transformations) == len(row["entities"])
|
|
134
|
+
assert (await token_manager.restore(tokens.text, context)).text == row["text"]
|
|
@@ -5,7 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
import datafog_core as api
|
|
6
6
|
|
|
7
7
|
SUPPORTED = sorted([
|
|
8
|
-
"CREDIT_CARD", "DATE", "DE_IBAN", "DE_PASSPORT_NUMBER", "DE_POSTAL_CODE",
|
|
8
|
+
"BEARER_TOKEN", "CREDIT_CARD", "DATE", "DE_IBAN", "DE_PASSPORT_NUMBER", "DE_POSTAL_CODE", "API_KEY", "CREDENTIAL_URI",
|
|
9
9
|
"DE_RESIDENCE_PERMIT_NUMBER", "DE_SOCIAL_SECURITY_NUMBER", "DE_TAX_ID",
|
|
10
10
|
"DE_VAT_ID", "EMAIL", "IP_ADDRESS", "JWT", "NPI", "PERSON", "PHONE",
|
|
11
11
|
"PRIVATE_KEY", "SSN", "US_ROUTING_NUMBER", "UUID", "ZIP_CODE",
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""Cross-detector overlap selection against the installed wheel."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import datafog_core as api
|
|
6
|
+
from synthetic_fixtures import expand_fixture
|
|
7
|
+
|
|
8
|
+
RECORDS = [
|
|
9
|
+
expand_fixture(json.loads(line))
|
|
10
|
+
for line in (Path(__file__).resolve().parents[3] / "fixtures/credential-overlaps.jsonl")
|
|
11
|
+
.read_text()
|
|
12
|
+
.splitlines()
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def project(text, findings):
|
|
17
|
+
result = []
|
|
18
|
+
for f in findings:
|
|
19
|
+
if f.entity_type not in {"API_KEY", "BEARER_TOKEN", "CREDENTIAL_URI", "JWT"}:
|
|
20
|
+
continue
|
|
21
|
+
assert (
|
|
22
|
+
text.encode()[f.byte_range.start : f.byte_range.end].decode()
|
|
23
|
+
== f.matched_text
|
|
24
|
+
)
|
|
25
|
+
assert text[f.codepoint_range.start : f.codepoint_range.end] == f.matched_text
|
|
26
|
+
assert f.confidence is None
|
|
27
|
+
assert f.detector_name == "datafog-core/" + f.entity_type.lower().replace(
|
|
28
|
+
"_", "-"
|
|
29
|
+
)
|
|
30
|
+
assert f.detector_version
|
|
31
|
+
result.append(
|
|
32
|
+
dict(
|
|
33
|
+
label=f.entity_type,
|
|
34
|
+
text=f.matched_text,
|
|
35
|
+
start=f.codepoint_range.start,
|
|
36
|
+
end=f.codepoint_range.end,
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
return result
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def verify():
|
|
43
|
+
for row in RECORDS:
|
|
44
|
+
findings = api.scan(row["text"], row["config"])
|
|
45
|
+
if "authorization_value" in row:
|
|
46
|
+
value = row["authorization_value"]
|
|
47
|
+
data = {"👋/headers": {"aUtHoRiZaTiOn": value}}
|
|
48
|
+
located = api.scan_structured(data).findings
|
|
49
|
+
assert all(item.path == "/👋~1headers/aUtHoRiZaTiOn" for item in located)
|
|
50
|
+
expected = [dict(entity, start=7, end=len(value)) for entity in row["entities"]]
|
|
51
|
+
assert project(value, [item.finding for item in located]) == expected
|
|
52
|
+
for entity in row["entities"]:
|
|
53
|
+
label = entity["label"]
|
|
54
|
+
result = api.scan_and_transform_structured(data, {"transform": {"default": {"strategy": "redact"}, "entities": [label]}})
|
|
55
|
+
assert result.data == {"👋/headers": {"aUtHoRiZaTiOn": f"Bearer [{label}]"}}
|
|
56
|
+
if "overlap" in row:
|
|
57
|
+
assert any(f.entity_type == row["overlap"] for f in findings)
|
|
58
|
+
assert project(row["text"], findings) == row["entities"], row["id"]
|
|
59
|
+
data = {"a/b": row["text"], "array": [row["text"]], "z": row["text"]}
|
|
60
|
+
located = api.scan_structured(data, row["config"]).findings
|
|
61
|
+
for path in ["/a~1b", "/array/0", "/z"]:
|
|
62
|
+
assert (
|
|
63
|
+
project(row["text"], [f.finding for f in located if f.path == path])
|
|
64
|
+
== row["entities"]
|
|
65
|
+
), row["id"]
|
|
66
|
+
for case in row.get("transforms", []):
|
|
67
|
+
result = api.scan_and_transform(
|
|
68
|
+
row["text"], {"scan": row["config"], "transform": case["config"]}
|
|
69
|
+
)
|
|
70
|
+
assert result.text == case["text"], row["id"]
|
|
71
|
+
assert api.transform(row["text"], findings, case["config"]) == result
|
|
72
|
+
for t in result.transformations:
|
|
73
|
+
assert (
|
|
74
|
+
result.text.encode()[
|
|
75
|
+
t.output_byte_range.start : t.output_byte_range.end
|
|
76
|
+
].decode()
|
|
77
|
+
== t.replacement
|
|
78
|
+
)
|
|
79
|
+
assert (
|
|
80
|
+
result.text[
|
|
81
|
+
t.output_codepoint_range.start : t.output_codepoint_range.end
|
|
82
|
+
]
|
|
83
|
+
== t.replacement
|
|
84
|
+
)
|
|
85
|
+
assert any(
|
|
86
|
+
f.byte_range == t.source_byte_range
|
|
87
|
+
and f.entity_type == t.entity_type
|
|
88
|
+
for f in findings
|
|
89
|
+
)
|
|
90
|
+
assert not hasattr(t, "matched_text") and not hasattr(t, "finding")
|
|
91
|
+
structured = api.scan_and_transform_structured(
|
|
92
|
+
data, {"scan": row["config"], "transform": case["config"]}
|
|
93
|
+
)
|
|
94
|
+
assert structured.data == {
|
|
95
|
+
"a/b": case["text"],
|
|
96
|
+
"array": [case["text"]],
|
|
97
|
+
"z": case["text"],
|
|
98
|
+
}
|
|
99
|
+
explicit = api.transform_structured(data, located, case["config"])
|
|
100
|
+
assert explicit.data == structured.data
|
|
101
|
+
assert [(t.path, t.transformation) for t in explicit.transformations] == [
|
|
102
|
+
(t.path, t.transformation) for t in structured.transformations
|
|
103
|
+
]
|