cloakapi 0.5.1__py3-none-any.whl
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.
- LICENSE +202 -0
- NOTICE +18 -0
- THIRD-PARTY-NOTICES.md +22 -0
- TRADEMARKS.md +19 -0
- cloakapi/__init__.py +271 -0
- cloakapi/_disclosure/clk.txt +11 -0
- cloakapi/_disclosure/clk_message.txt +11 -0
- cloakapi/_disclosure/cloak_angle.txt +16 -0
- cloakapi/_disclosure/cloak_angle_message.txt +16 -0
- cloakapi/_http.py +443 -0
- cloakapi/_pretokenised.py +134 -0
- cloakapi/_relay.py +213 -0
- cloakapi/_release.py +30 -0
- cloakapi/_request_strings.py +1267 -0
- cloakapi/_ruleset/patterns.json +2238 -0
- cloakapi/_stream_detok.py +788 -0
- cloakapi/_version.py +31 -0
- cloakapi/async_client.py +704 -0
- cloakapi/attachments.py +176 -0
- cloakapi/cli.py +245 -0
- cloakapi/client.py +848 -0
- cloakapi/config.py +491 -0
- cloakapi/coverage.py +21 -0
- cloakapi/degraded.py +196 -0
- cloakapi/disclosure.py +334 -0
- cloakapi/engine.py +922 -0
- cloakapi/errors.py +304 -0
- cloakapi/notices.py +142 -0
- cloakapi/pii/__init__.py +4 -0
- cloakapi/pii/_checksum.py +510 -0
- cloakapi/pii/_ruleset.py +131 -0
- cloakapi/pii/patterns.py +1713 -0
- cloakapi/pii/rulefile.py +738 -0
- cloakapi/pii/tokenizer.py +537 -0
- cloakapi/posture.py +112 -0
- cloakapi/py.typed +1 -0
- cloakapi/receipts.py +605 -0
- cloakapi/resources/__init__.py +32 -0
- cloakapi/resources/admin.py +104 -0
- cloakapi/resources/buffer.py +54 -0
- cloakapi/resources/chat.py +293 -0
- cloakapi/resources/detect.py +115 -0
- cloakapi/resources/messages.py +260 -0
- cloakapi/resources/models.py +58 -0
- cloakapi/resources/receipts.py +252 -0
- cloakapi/resources/reconstruct.py +173 -0
- cloakapi/resources/tokens.py +47 -0
- cloakapi/review.py +112 -0
- cloakapi/streaming.py +446 -0
- cloakapi/surrogate_mode.py +308 -0
- cloakapi/types.py +284 -0
- cloakapi-0.5.1.dist-info/METADATA +441 -0
- cloakapi-0.5.1.dist-info/RECORD +61 -0
- cloakapi-0.5.1.dist-info/WHEEL +4 -0
- cloakapi-0.5.1.dist-info/entry_points.txt +2 -0
- cloakapi-0.5.1.dist-info/licenses/LICENSE +202 -0
- cloakapi-0.5.1.dist-info/licenses/NOTICE +18 -0
- licenses/Apache-2.0.txt +202 -0
- licenses/CC-BY-4.0.txt +104 -0
- licenses/MIT-Faker.txt +21 -0
- licenses/MIT.txt +21 -0
cloakapi/_relay.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""One relay request, from the tokenised body to the bytes that leave.
|
|
2
|
+
|
|
3
|
+
The sync and async clients share every step that does not touch the network:
|
|
4
|
+
what the request declares about itself, which notices it carries, and what the
|
|
5
|
+
reviewer is shown. Two copies of these rules drifted before, so they live here
|
|
6
|
+
once. The clients pass in their own ``engine_available`` and
|
|
7
|
+
``require_pinned_engine_identity`` so each keeps a single place to substitute
|
|
8
|
+
them.
|
|
9
|
+
|
|
10
|
+
What a prepared request always has:
|
|
11
|
+
|
|
12
|
+
* the exact body bytes, serialised once, that the MAC is minted over and that
|
|
13
|
+
are then sent;
|
|
14
|
+
* ``X-Cloak-Coverage`` and ``X-CloakAPI-Tokenized: true;coverage=<coverage>``,
|
|
15
|
+
which state which local layers ran;
|
|
16
|
+
* engine identity headers only when a pinned engine with this SDK's rule set
|
|
17
|
+
ran. A request the structured rules prepared claims no engine.
|
|
18
|
+
|
|
19
|
+
What never happens here: a request is not refused for its content. Reduced
|
|
20
|
+
coverage, a value in a place that must stay as written (an identifier, a tool
|
|
21
|
+
schema literal, a number, a key inside tool arguments) and media this device
|
|
22
|
+
did not read become notices on the request, shown to the reviewer and returned
|
|
23
|
+
with the reply. A detected value used as an object key in free-form data is
|
|
24
|
+
replaced here, where it stands.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from collections.abc import Callable
|
|
30
|
+
from dataclasses import dataclass, field
|
|
31
|
+
from typing import Any
|
|
32
|
+
|
|
33
|
+
from ._http import absolutt_url, serialise_body
|
|
34
|
+
from ._request_strings import assert_no_thai_khmer, bind_tokeniser, settle_structure
|
|
35
|
+
from .coverage import Coverage
|
|
36
|
+
from .degraded import KIND_NO_NER, engine_reason, guard_reduced_coverage
|
|
37
|
+
from .errors import EngineIdentityError, PlaintextRelayNotSupportedError
|
|
38
|
+
from .notices import (
|
|
39
|
+
REDUCED_COVERAGE,
|
|
40
|
+
ReviewInfo,
|
|
41
|
+
SendWarning,
|
|
42
|
+
place_warnings,
|
|
43
|
+
print_unchecked_content,
|
|
44
|
+
reduced_coverage_warning,
|
|
45
|
+
unchecked_content_warning,
|
|
46
|
+
)
|
|
47
|
+
from .review import RelayReview
|
|
48
|
+
|
|
49
|
+
_NO_ENGINE_SENTENCE = "No on-device engine is installed for this platform."
|
|
50
|
+
_ENGINE_REFUSED_SENTENCE = "The on-device engine could not process part of this request."
|
|
51
|
+
_MODEL_MESSAGE = (
|
|
52
|
+
"The name model is not available on this device, so names are checked against "
|
|
53
|
+
"the built-in list only."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class BuiltRelay:
|
|
59
|
+
"""A relay request that is ready for review. Nothing has been sent."""
|
|
60
|
+
|
|
61
|
+
method: str
|
|
62
|
+
path: str
|
|
63
|
+
url: str
|
|
64
|
+
content: bytes
|
|
65
|
+
headers: dict[str, str]
|
|
66
|
+
coverage: Coverage
|
|
67
|
+
warnings: tuple[SendWarning, ...] = ()
|
|
68
|
+
token_map: dict[str, str] = field(default_factory=dict)
|
|
69
|
+
#: The signed receipt the gateway returned with the reply to THIS request, if any.
|
|
70
|
+
receipt: dict[str, Any] | None = None
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def text(self) -> str:
|
|
74
|
+
"""The exact outgoing body, as text."""
|
|
75
|
+
return self.content.decode("utf-8")
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def state(self) -> str:
|
|
79
|
+
return "ok" if self.coverage is Coverage.FULL and not self.warnings else "degraded"
|
|
80
|
+
|
|
81
|
+
def info(self) -> ReviewInfo:
|
|
82
|
+
return ReviewInfo(
|
|
83
|
+
state="ok" if self.state == "ok" else "degraded",
|
|
84
|
+
coverage=self.coverage.value,
|
|
85
|
+
warnings=self.warnings,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def review(self) -> RelayReview:
|
|
89
|
+
return RelayReview.create(self.method, self.url, self.content, self.headers)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def build_relay(
|
|
93
|
+
client: Any,
|
|
94
|
+
body: dict[str, Any],
|
|
95
|
+
extra_headers: dict[str, str],
|
|
96
|
+
should_tokenize: bool,
|
|
97
|
+
*,
|
|
98
|
+
method: str,
|
|
99
|
+
path: str,
|
|
100
|
+
engine_available: Callable[[], bool],
|
|
101
|
+
require_pinned_engine_identity: Callable[[], Any],
|
|
102
|
+
state: dict[str, Any] | None = None,
|
|
103
|
+
) -> BuiltRelay:
|
|
104
|
+
"""Finalise the body and headers of one relay request. No network, no review.
|
|
105
|
+
|
|
106
|
+
``state`` is the per-request state ``_prepare_messages`` returned. When it
|
|
107
|
+
is given, coverage and notices are read from it and not from the client's
|
|
108
|
+
"latest request" attributes, so two overlapping requests on one client
|
|
109
|
+
cannot take each other's coverage or lose each other's notices.
|
|
110
|
+
"""
|
|
111
|
+
if not should_tokenize:
|
|
112
|
+
raise PlaintextRelayNotSupportedError(
|
|
113
|
+
"tokenize=False is not supported for relay requests; enable on-device tokenization."
|
|
114
|
+
)
|
|
115
|
+
notices: list[SendWarning] = []
|
|
116
|
+
own = state if isinstance(state, dict) and "coverage" in state else None
|
|
117
|
+
# Keys, identifiers, schema literals and media: the last pass over the body.
|
|
118
|
+
# It replaces what can be replaced and records the rest on this request.
|
|
119
|
+
settle_tok = bind_tokeniser(client, state if isinstance(state, dict) else None)
|
|
120
|
+
body = settle_structure(body, settle_tok)
|
|
121
|
+
places = list(settle_tok.state.get("unreplaced_places", ())) # type: ignore[attr-defined]
|
|
122
|
+
coverage = (own["coverage"] if own is not None else client._last_coverage) or Coverage.FULL
|
|
123
|
+
if assert_no_thai_khmer(body):
|
|
124
|
+
coverage = Coverage.RULES_ONLY
|
|
125
|
+
client._last_coverage = coverage
|
|
126
|
+
guard_reduced_coverage("tokenise text", kind=KIND_NO_NER)
|
|
127
|
+
notices.append(unchecked_content_warning("unverified_script"))
|
|
128
|
+
value_not_replaced = (
|
|
129
|
+
own.get("value_not_replaced", False)
|
|
130
|
+
if own is not None
|
|
131
|
+
else getattr(client, "_last_value_not_replaced", False)
|
|
132
|
+
)
|
|
133
|
+
if places:
|
|
134
|
+
notices.extend(place_warnings(places))
|
|
135
|
+
elif value_not_replaced:
|
|
136
|
+
notices.append(unchecked_content_warning("value_not_replaced"))
|
|
137
|
+
headers = dict(extra_headers)
|
|
138
|
+
trusted_engine = engine_available()
|
|
139
|
+
ident: dict[str, str] = {}
|
|
140
|
+
if trusted_engine:
|
|
141
|
+
try:
|
|
142
|
+
candidate = require_pinned_engine_identity()
|
|
143
|
+
if isinstance(candidate, dict) and candidate.get("version") and candidate.get("hash"):
|
|
144
|
+
ident = candidate
|
|
145
|
+
else:
|
|
146
|
+
trusted_engine = False
|
|
147
|
+
except EngineIdentityError:
|
|
148
|
+
trusted_engine = False
|
|
149
|
+
# The engine is in use but would not take at least one string of this
|
|
150
|
+
# request, and the structured rules prepared it instead. Such a request was
|
|
151
|
+
# not prepared by the engine: it states no engine identity, and its notice
|
|
152
|
+
# says what ran (not "the name model is missing").
|
|
153
|
+
engine_refused = bool(trusted_engine and own is not None and own.get("rules_prepared"))
|
|
154
|
+
if engine_refused:
|
|
155
|
+
trusted_engine = False
|
|
156
|
+
ident = {}
|
|
157
|
+
if not trusted_engine:
|
|
158
|
+
if coverage is not Coverage.RULES_ONLY:
|
|
159
|
+
guard_reduced_coverage("tokenise text", kind=KIND_NO_NER)
|
|
160
|
+
coverage = Coverage.RULES_ONLY
|
|
161
|
+
client._last_coverage = coverage
|
|
162
|
+
# _prepare_messages may have populated identity headers before the
|
|
163
|
+
# final pin check. Never carry those claims into review or egress when
|
|
164
|
+
# this request cannot prove the loaded engine.
|
|
165
|
+
headers.pop("X-Cloak-Engine-Version", None)
|
|
166
|
+
headers.pop("X-Cloak-Engine-Hash", None)
|
|
167
|
+
if engine_refused:
|
|
168
|
+
notices.insert(0, reduced_coverage_warning("engine_refused", _ENGINE_REFUSED_SENTENCE))
|
|
169
|
+
else:
|
|
170
|
+
reason = engine_reason() or ("engine_missing", _NO_ENGINE_SENTENCE)
|
|
171
|
+
notices.insert(
|
|
172
|
+
0, reduced_coverage_warning(reason[0], reason[1] or _NO_ENGINE_SENTENCE)
|
|
173
|
+
)
|
|
174
|
+
elif coverage is not Coverage.FULL and (
|
|
175
|
+
own is None or own.get("detector_coverage", coverage) is not Coverage.FULL
|
|
176
|
+
):
|
|
177
|
+
# The engine ran; only its name model is missing. Not "rules ran instead".
|
|
178
|
+
# (Not said when the detector reported full coverage and the label was
|
|
179
|
+
# lowered only because a value or a script was left as written: those
|
|
180
|
+
# have their own notice.)
|
|
181
|
+
notices.insert(0, SendWarning(REDUCED_COVERAGE, "model_missing", _MODEL_MESSAGE))
|
|
182
|
+
headers["X-Cloak-Coverage"] = coverage.value
|
|
183
|
+
headers["X-CloakAPI-Tokenized"] = f"true;coverage={coverage.value}"
|
|
184
|
+
content = serialise_body(body)
|
|
185
|
+
last_mode = (own.get("surrogate_mode") if own is not None else None) or client._last_surrogate_mode
|
|
186
|
+
actual_mode = getattr(last_mode, "mode", last_mode)
|
|
187
|
+
headers["X-CloakAPI-Mode"] = (
|
|
188
|
+
"max" if coverage is Coverage.RULES_ONLY and actual_mode == "max" else client.tier
|
|
189
|
+
)
|
|
190
|
+
if ident.get("version") and (coverage is not Coverage.RULES_ONLY or ident.get("hash")):
|
|
191
|
+
headers["X-Cloak-Engine-Version"] = ident["version"]
|
|
192
|
+
if ident.get("hash"):
|
|
193
|
+
headers["X-Cloak-Engine-Hash"] = ident["hash"]
|
|
194
|
+
warnings = tuple(notices)
|
|
195
|
+
client._last_warnings = warnings
|
|
196
|
+
print_unchecked_content(warnings)
|
|
197
|
+
return BuiltRelay(
|
|
198
|
+
method=method,
|
|
199
|
+
path=path,
|
|
200
|
+
url=absolutt_url(client._base_url, path),
|
|
201
|
+
content=content,
|
|
202
|
+
headers=headers,
|
|
203
|
+
coverage=coverage,
|
|
204
|
+
warnings=warnings,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def refuse_plaintext_tier(client: Any) -> None:
|
|
209
|
+
if str(client.tier) == "off": # defense-in-depth for runtime mutation
|
|
210
|
+
raise PlaintextRelayNotSupportedError(
|
|
211
|
+
"tier='off' is a plaintext relay; under Level-1-only the gateway "
|
|
212
|
+
"relays only client-tokenised requests. Use tier 'balanced' or 'max'."
|
|
213
|
+
)
|
cloakapi/_release.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""The release set this package was cut for.
|
|
2
|
+
|
|
3
|
+
One place for the numbers that tie this package to the other CloakAPI packages
|
|
4
|
+
of the same release: the rule set it expects and the native engine files it
|
|
5
|
+
trusts when one is shipped inside a wheel. ``repo/release-set.json`` is the
|
|
6
|
+
source; ``repo/shared/testing/release-set-check.mjs python`` and
|
|
7
|
+
``tests/test_release_set.py`` fail when this file disagrees with it, and
|
|
8
|
+
``hatch_build.py`` refuses to build a wheel around any other engine file.
|
|
9
|
+
|
|
10
|
+
Plain string constants on purpose: the build hook and the lockstep check read
|
|
11
|
+
them without importing the package.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
RELEASE_SET = "R-1.6.1"
|
|
17
|
+
|
|
18
|
+
#: Rule set this SDK's own rules and its native engine must both carry.
|
|
19
|
+
RULESET_VERSION = "1.6.1"
|
|
20
|
+
RULESET_SHA256 = "8660b7abe553154802c9d27888a144863bcceaa3703517b8401cc56c590df2f9"
|
|
21
|
+
|
|
22
|
+
#: SHA-256 of the native engine file per platform (``cloak-engine-ffi`` 0.3.0).
|
|
23
|
+
NATIVE_SHA256_LINUX_X86_64 = "b2aec8d6d90ead4328258270c41a79dda4e1f71efa79025f308ce8e09f080744"
|
|
24
|
+
NATIVE_SHA256_LINUX_AARCH64 = "a8bfb99346cdaf6e47b13214e7f1f872f95e0c2fe040cf6bb3e6a82f1d26ed30"
|
|
25
|
+
|
|
26
|
+
#: Wheel platform arch -> the one engine file a wheel for it may carry.
|
|
27
|
+
NATIVE_ENGINE_SHA256 = {
|
|
28
|
+
"x86_64": NATIVE_SHA256_LINUX_X86_64,
|
|
29
|
+
"aarch64": NATIVE_SHA256_LINUX_AARCH64,
|
|
30
|
+
}
|