@burnt-labs/expo-satya-attest 3.11.1 → 3.12.0
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.
- package/CHANGELOG.md +26 -0
- package/PROVIDER_CONFIG.md +7 -2
- package/PUBLISHING.md +12 -10
- package/README.md +41 -3
- package/android/build.gradle +2 -2
- package/android/src/main/java/expo/modules/satyaattest/SatyaAttestModule.kt +26 -17
- package/android/src/main/java/expo/modules/satyaattest/SatyaCompositeReplay.kt +5 -5
- package/android/src/main/java/uniffi/satya_ffi/satya_ffi.kt +65 -0
- package/android/src/main/jniLibs/arm64-v8a/libsatya_ffi.so +0 -0
- package/android/src/main/jniLibs/armeabi-v7a/libsatya_ffi.so +0 -0
- package/android/src/main/jniLibs/x86_64/libsatya_ffi.so +0 -0
- package/build/SatyaMobileClient.d.ts.map +1 -1
- package/build/SatyaMobileClient.js +3 -0
- package/build/SatyaMobileClient.js.map +1 -1
- package/build/components/providerAttest/backendVerifier.d.ts.map +1 -1
- package/build/components/providerAttest/backendVerifier.js +12 -14
- package/build/components/providerAttest/backendVerifier.js.map +1 -1
- package/build/components/providerAttest/useNativeReplayRunner.d.ts.map +1 -1
- package/build/components/providerAttest/useNativeReplayRunner.js +10 -11
- package/build/components/providerAttest/useNativeReplayRunner.js.map +1 -1
- package/build/sdkMetadata.generated.d.ts +1 -1
- package/build/sdkMetadata.generated.js +1 -1
- package/build/sdkMetadata.generated.js.map +1 -1
- package/build/tee/claims.d.ts.map +1 -1
- package/build/tee/claims.js +22 -10
- package/build/tee/claims.js.map +1 -1
- package/build/tee/client.d.ts +2 -1
- package/build/tee/client.d.ts.map +1 -1
- package/build/tee/client.js +29 -4
- package/build/tee/client.js.map +1 -1
- package/build/utils/dataLimits.d.ts +12 -0
- package/build/utils/dataLimits.d.ts.map +1 -0
- package/build/utils/dataLimits.js +73 -0
- package/build/utils/dataLimits.js.map +1 -0
- package/build/utils/streamingFetch.d.ts +2 -0
- package/build/utils/streamingFetch.d.ts.map +1 -0
- package/build/utils/streamingFetch.js +4 -0
- package/build/utils/streamingFetch.js.map +1 -0
- package/build/utils/streamingFetch.native.d.ts +2 -0
- package/build/utils/streamingFetch.native.d.ts.map +1 -0
- package/build/utils/streamingFetch.native.js +6 -0
- package/build/utils/streamingFetch.native.js.map +1 -0
- package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64/Headers/satya_ffi.swift +44 -0
- package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64/Headers/satya_ffiFFI.h +22 -0
- package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64/libsatya_ffi.a +2 -2
- package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64_x86_64-simulator/Headers/satya_ffi.swift +44 -0
- package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64_x86_64-simulator/Headers/satya_ffiFFI.h +22 -0
- package/ios/Frameworks/libsatya_ffi-rs.xcframework/ios-arm64_x86_64-simulator/libsatya_ffi.a +2 -2
- package/ios/Generated/satya_ffi.swift +44 -0
- package/ios/SatyaAttest.podspec +1 -1
- package/ios/SatyaAttestModule.swift +77 -37
- package/package.json +1 -1
- package/src/SatyaMobileClient.ts +3 -0
- package/src/components/providerAttest/backendVerifier.ts +15 -14
- package/src/components/providerAttest/useNativeReplayRunner.ts +10 -11
- package/src/sdkMetadata.generated.ts +1 -1
- package/src/tee/claims.ts +24 -10
- package/src/tee/client.ts +43 -4
- package/src/utils/dataLimits.ts +69 -0
- package/src/utils/streamingFetch.native.ts +6 -0
- package/src/utils/streamingFetch.ts +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.12.0 - 2026-10-07
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Support claims up to 8 MiB in native and TEE replay, replacing the 64/256 KiB
|
|
8
|
+
restrictions without requiring provider opt-in.
|
|
9
|
+
- Enforce a shared 80 MiB budget for revealed claim values across endpoints and a
|
|
10
|
+
separate 80 MiB serialized attestation limit. Exceeding either fails the proof,
|
|
11
|
+
including partial-completion plans. Unrevealed response bytes are not charged
|
|
12
|
+
to the disclosure budget; bounded response parsing remains enforced.
|
|
13
|
+
- Bound verifier/proxy uploads, response reads, canonical JSON processing and
|
|
14
|
+
large-job concurrency while preserving signature and policy validation.
|
|
15
|
+
- Share bounded reveal-regex execution across Android, iOS and TEE. Ordinary
|
|
16
|
+
patterns use a linear matcher; advanced patterns require inputs of at most 1 KiB.
|
|
17
|
+
|
|
18
|
+
### Compatibility and rollout
|
|
19
|
+
|
|
20
|
+
- Rebuild consuming iOS and Android apps and deploy the matching verifier/proxy
|
|
21
|
+
and measured TEE service before relying on the larger limits. Approve the new
|
|
22
|
+
TEE measurements in the verifier policy.
|
|
23
|
+
- Artifact metadata, JSON escaping and TEE base64 encoding count toward the
|
|
24
|
+
serialized limit, so usable claim capacity may be below 80 MiB.
|
|
25
|
+
- Buffered iOS replay retains a conservative 16 MiB raw-response cap (128 MiB
|
|
26
|
+
per run); Android and TEE allow 96/768 MiB. Raw-response guards are independent
|
|
27
|
+
of the 8/80 MiB disclosure limits and can reject large unrevealed fields.
|
|
28
|
+
|
|
3
29
|
## 3.11.1 - 2026-10-07
|
|
4
30
|
|
|
5
31
|
### Fixed
|
package/PROVIDER_CONFIG.md
CHANGED
|
@@ -357,8 +357,13 @@ The three `params.type` values:
|
|
|
357
357
|
case-insensitively across all reveals.
|
|
358
358
|
|
|
359
359
|
Native replay applies these to the pinned response and emits each match as a claim. The
|
|
360
|
-
body is size-bounded (
|
|
361
|
-
|
|
360
|
+
body is size-bounded before regex runs (16 MiB on iOS; 96 MiB on Android and TEE);
|
|
361
|
+
a revealed claim is capped at 8 MiB of serialized JSON. Native and TEE replay share a
|
|
362
|
+
linear matcher for ordinary patterns (at most 4096 UTF-8 bytes). Advanced patterns,
|
|
363
|
+
including lookarounds/backreferences, allow at most 256 pattern bytes and 1024 input
|
|
364
|
+
bytes with bounded backtracking; violations fail the proof. Use `jsonRegex` to select
|
|
365
|
+
a small field first. Patterns come only from the trusted registry, never from page content.
|
|
366
|
+
See §10 for regex authoring tips.
|
|
362
367
|
|
|
363
368
|
### 5.1 Claim presence and empty account states
|
|
364
369
|
|
package/PUBLISHING.md
CHANGED
|
@@ -12,10 +12,12 @@ for a plug-and-play Expo SDK.
|
|
|
12
12
|
|
|
13
13
|
## Release preparation
|
|
14
14
|
|
|
15
|
-
The next prepared
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
The next prepared release is **3.12.0**. It raises individual claims to 8 MiB
|
|
16
|
+
and enforces separate 80 MiB budgets for revealed values and serialized artifacts.
|
|
17
|
+
Rebuild consuming iOS and Android apps. Deploy the matching verifier/proxy and
|
|
18
|
+
measured TEE service, then approve its measurements in the trusted verifier policy
|
|
19
|
+
before clients rely on the larger limits. No provider opt-in is required. Version
|
|
20
|
+
3.12.0 was unused on npm when checked on 2026-10-07; confirm again before publishing.
|
|
19
21
|
|
|
20
22
|
Observability v2 was introduced in 3.11.0; its existing rollout requirements follow.
|
|
21
23
|
|
|
@@ -61,7 +63,7 @@ Local cookie cleanup does not reset remote hosted TeeLS sessions.
|
|
|
61
63
|
The package check requires both native session managers, the WebView snapshot
|
|
62
64
|
helper, adapter script, Android keep rules and public JS/declarations. Host tests
|
|
63
65
|
use synthetic stores and adapter fixtures; they are not substitutes for device
|
|
64
|
-
cookie isolation tests. Until the app builds and device matrix pass, treat 3.
|
|
66
|
+
cookie isolation tests. Until the app builds and device matrix pass, treat 3.12.0
|
|
65
67
|
as release preparation, not a validated stable release.
|
|
66
68
|
|
|
67
69
|
## TEE release requirements
|
|
@@ -269,7 +271,7 @@ internal/module_scripts/
|
|
|
269
271
|
After the release gates pass and publication is authorized, use the consolidated
|
|
270
272
|
`publish-expo-satya-attest.yml` workflow. A stable tag must match the committed
|
|
271
273
|
package version and point to a commit on `main`; for this prepared release the
|
|
272
|
-
package-specific tag is `expo-satya-attest-v3.
|
|
274
|
+
package-specific tag is `expo-satya-attest-v3.12.0`. Creating/pushing that tag starts
|
|
273
275
|
publication. Manual workflow dispatch is a dry-run.
|
|
274
276
|
|
|
275
277
|
The workflow builds native artifacts, runs the release checks, and publishes
|
|
@@ -287,13 +289,13 @@ Every push to `main` publishes a snapshot package to the public npm registry wit
|
|
|
287
289
|
unique prerelease version:
|
|
288
290
|
|
|
289
291
|
```text
|
|
290
|
-
3.
|
|
292
|
+
3.12.1-snapshot.<run>.<attempt>.sha-<commit>
|
|
291
293
|
```
|
|
292
294
|
|
|
293
295
|
The snapshot job (in `publish-expo-satya-attest.yml`) increments the package's patch base
|
|
294
|
-
in CI before publishing. For example, if `package.json` is `3.
|
|
295
|
-
is based on `3.
|
|
296
|
-
`3.
|
|
296
|
+
in CI before publishing. For example, if `package.json` is `3.12.0`, the snapshot version
|
|
297
|
+
is based on `3.12.1`:
|
|
298
|
+
`3.12.1-snapshot.<run>.<attempt>.sha-<commit>`. Snapshots are development builds and
|
|
297
299
|
do not imply completion of the physical-device gates.
|
|
298
300
|
|
|
299
301
|
Snapshots use the `snapshot` dist-tag so test apps do not need to know the generated
|
package/README.md
CHANGED
|
@@ -5,8 +5,9 @@ the Rust `satya-sdk` / `satya-ffi` pipeline for artifact construction and verifi
|
|
|
5
5
|
policy, while Swift and Kotlin provide platform-specific WebView capture, native HTTPS
|
|
6
6
|
replay, hardware-backed signing, and platform attestation evidence.
|
|
7
7
|
|
|
8
|
-
**3.
|
|
9
|
-
Rebuild
|
|
8
|
+
**3.12.0:** Supports 8 MiB claims with shared 80 MiB disclosure and artifact budgets.
|
|
9
|
+
Rebuild consuming native apps and deploy the matching verifier/TEE services;
|
|
10
|
+
provider configuration is unchanged.
|
|
10
11
|
Observability v2 remains opt-in. See the [changelog](./CHANGELOG.md),
|
|
11
12
|
[observability rollout guide](./TEELS_OBSERVABILITY.md) and
|
|
12
13
|
[release requirements](./PUBLISHING.md#release-preparation).
|
|
@@ -996,7 +997,44 @@ disabled and only 2xx responses accepted. `onProgress` reports `proofPlanId`, `e
|
|
|
996
997
|
|
|
997
998
|
- **1–8 endpoints** per plan, ordered; the order is order-sensitive and participates in
|
|
998
999
|
`policyHash`.
|
|
999
|
-
- **
|
|
1000
|
+
- **8 MiB per claim value**, measured as UTF-8 serialized JSON, including quotes and
|
|
1001
|
+
escapes; native uses its existing Rust serialization and TEE preserves exact numbers.
|
|
1002
|
+
- **80 MiB of revealed claim values across all endpoints** and a separate **80 MiB
|
|
1003
|
+
serialized attestation** limit, including metadata and signatures. Each logical claim
|
|
1004
|
+
counts, including overlapping reveals. Raw unrevealed fields do not debit the claim
|
|
1005
|
+
budget. An overflow aborts the proof and stops subsequent replay endpoints, including
|
|
1006
|
+
plans that otherwise allow partial completion. These limits apply automatically.
|
|
1007
|
+
- For example, ten values of exactly 8 MiB exhaust the logical disclosure budget;
|
|
1008
|
+
the full attestation cap can reject earlier because it also includes overhead.
|
|
1009
|
+
A TEE submission includes base64, leaving less than 60 MiB for inner artifact bytes.
|
|
1010
|
+
- Separate raw-response guards allow **16 MiB per endpoint / 128 MiB per run on
|
|
1011
|
+
iOS**, whose buffered extraction makes full-body copies. Android and TEE allow
|
|
1012
|
+
**96 MiB per endpoint / 768 MiB per run**, with bounded decompression. Large
|
|
1013
|
+
unrevealed fields can hit these guards before the disclosure budget.
|
|
1014
|
+
The compatible verifier reply carries plain and
|
|
1015
|
+
exact base64 values and is read with a **240 MiB wire cap**; this does not enlarge
|
|
1016
|
+
the 80 MiB disclosure budget.
|
|
1017
|
+
- Rust JSON processing bounds depth, number tokens and structural work (4,259,840
|
|
1018
|
+
units per document/proof: one per value plus three per object member). This covers
|
|
1019
|
+
TEE response parsing and native selected claims/artifacts, including duplicated
|
|
1020
|
+
disclosure representations. Android and iOS parse raw JSON before selecting claims;
|
|
1021
|
+
their raw-body byte caps do not bound native object counts. Dense unrevealed fields
|
|
1022
|
+
can therefore still amplify native memory use. Capture, endpoint-count and freshness
|
|
1023
|
+
guards remain in force.
|
|
1024
|
+
- Native and TEE reveals share a bounded Rust regex matcher. Ordinary patterns use
|
|
1025
|
+
a linear engine; advanced patterns (such as lookarounds/backreferences) are limited
|
|
1026
|
+
to 256 pattern bytes, 1 KiB inputs and bounded backtracking. Invalid or over-budget
|
|
1027
|
+
patterns fail the proof. Use `jsonRegex` to select a small field before advanced matching.
|
|
1028
|
+
- Large server jobs are admitted automatically above the previous small-data ceilings
|
|
1029
|
+
(2 MiB verifier uploads; 8 MiB raw replay input or 512 KiB retained replay output),
|
|
1030
|
+
with one large job and up to 16 active jobs per process; overload fails explicitly without replay retries.
|
|
1031
|
+
Verifier uploads require 4 KiB/s progress with at most 10 seconds of startup/burst
|
|
1032
|
+
allowance, plus 10-second idle / 120-second total deadlines. At most 12 uploads
|
|
1033
|
+
may wait for data, leaving four slots for already-buffered requests. These process
|
|
1034
|
+
limits complement edge DDoS controls. Release qualification still requires
|
|
1035
|
+
physical-device memory tests and deployed mixed-load tests. Native verifier/TEE
|
|
1036
|
+
responses use Expo streaming fetch. Custom TEE fetch overrides must provide
|
|
1037
|
+
streaming bodies; non-streaming responses are rejected.
|
|
1000
1038
|
- Capture selection runs for at most the plan's `maxDurationMs` (an integer in
|
|
1001
1039
|
`[1000, 120000]` ms). A readiness signal starts the window when received; otherwise the
|
|
1002
1040
|
first accepted capture starts it. Later readiness never resets an earlier start.
|
package/android/build.gradle
CHANGED
|
@@ -4,14 +4,14 @@ plugins {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
group = 'expo.modules.satyaattest'
|
|
7
|
-
version = '3.
|
|
7
|
+
version = '3.12.0'
|
|
8
8
|
|
|
9
9
|
android {
|
|
10
10
|
namespace "expo.modules.satyaattest"
|
|
11
11
|
defaultConfig {
|
|
12
12
|
consumerProguardFiles 'consumer-rules.pro'
|
|
13
13
|
versionCode 1
|
|
14
|
-
versionName "3.
|
|
14
|
+
versionName "3.12.0"
|
|
15
15
|
// StrongBox key attestation needs API 28+; basic Keystore attestation API 24+.
|
|
16
16
|
minSdkVersion 24
|
|
17
17
|
}
|
|
@@ -64,6 +64,8 @@ import org.json.JSONTokener
|
|
|
64
64
|
import uniffi.satya_ffi.WitnessStatementSigner
|
|
65
65
|
import uniffi.satya_ffi.generateNativeReplayBundle
|
|
66
66
|
import uniffi.satya_ffi.generateNativeReplayObservation
|
|
67
|
+
import uniffi.satya_ffi.measureRevealClaimValue
|
|
68
|
+
import uniffi.satya_ffi.matchRevealRegex
|
|
67
69
|
import uniffi.satya_ffi.prepareNativeReplayBundle
|
|
68
70
|
import uniffi.satya_ffi.satyaCoreVersion
|
|
69
71
|
import uniffi.satya_ffi.verifyKaggleArtifact
|
|
@@ -71,7 +73,17 @@ import uniffi.satya_ffi.verifyKaggleArtifact
|
|
|
71
73
|
private const val KEY_ALIAS_PREFIX = "satya-w-witness-"
|
|
72
74
|
|
|
73
75
|
// Cap replay bodies before JSON/regex claim extraction.
|
|
74
|
-
private const val MAX_RESPONSE_BODY_BYTES =
|
|
76
|
+
private const val MAX_RESPONSE_BODY_BYTES = 96L * 1024 * 1024
|
|
77
|
+
|
|
78
|
+
private class NativeRevealBudget {
|
|
79
|
+
private var used = 0L
|
|
80
|
+
fun add(value: Any) {
|
|
81
|
+
val wrapped = JSONArray().put(value).toString()
|
|
82
|
+
val bytes = measureRevealClaimValue(wrapped.substring(1, wrapped.length - 1)).toLong()
|
|
83
|
+
require(bytes <= 80L * 1024 * 1024 - used) { "revealed claim values exceed 80 MiB" }
|
|
84
|
+
used += bytes
|
|
85
|
+
}
|
|
86
|
+
}
|
|
75
87
|
|
|
76
88
|
private const val PLAY_INTEGRITY_BIND_DOMAIN = "satya-w-playintegrity-bind-v1"
|
|
77
89
|
|
|
@@ -762,6 +774,7 @@ class SatyaAttestModule : Module() {
|
|
|
762
774
|
"composite replay start plan=$proofPlanId endpoints=${orderedRequests.size}",
|
|
763
775
|
)
|
|
764
776
|
var totalResponseBytes = 0L
|
|
777
|
+
val claimBudget = NativeRevealBudget()
|
|
765
778
|
val observations = ArrayList<CompositeExchangeObservation>(orderedRequests.size)
|
|
766
779
|
try {
|
|
767
780
|
for (exchangeRequest in orderedRequests) {
|
|
@@ -782,6 +795,7 @@ class SatyaAttestModule : Module() {
|
|
|
782
795
|
trustPolicy = trustPolicy,
|
|
783
796
|
cookieJar = cookieJar,
|
|
784
797
|
priorResponseBytes = totalResponseBytes,
|
|
798
|
+
claimBudget = claimBudget,
|
|
785
799
|
deadlineAtMonotonicMs = deadlineAtMonotonicMs,
|
|
786
800
|
)
|
|
787
801
|
}
|
|
@@ -852,6 +866,7 @@ class SatyaAttestModule : Module() {
|
|
|
852
866
|
trustPolicy: String,
|
|
853
867
|
cookieJar: ProofCookieJar,
|
|
854
868
|
priorResponseBytes: Long,
|
|
869
|
+
claimBudget: NativeRevealBudget,
|
|
855
870
|
deadlineAtMonotonicMs: Long,
|
|
856
871
|
): CompositeExchangeObservation {
|
|
857
872
|
val endpointMethod = endpoint.optString("method", "GET")
|
|
@@ -1003,7 +1018,7 @@ class SatyaAttestModule : Module() {
|
|
|
1003
1018
|
|
|
1004
1019
|
// Extract ONLY this endpoint's governed reveals.
|
|
1005
1020
|
val reveals = compositeRevealRules(endpoint)
|
|
1006
|
-
val claimsJson = extractRevealClaims(responseBody.text, reveals)
|
|
1021
|
+
val claimsJson = extractRevealClaims(responseBody.text, reveals, claimBudget)
|
|
1007
1022
|
val claims = (0 until claimsJson.length()).map { index ->
|
|
1008
1023
|
val claim = claimsJson.getJSONObject(index)
|
|
1009
1024
|
CompositeClaim(
|
|
@@ -1752,10 +1767,12 @@ class SatyaAttestModule : Module() {
|
|
|
1752
1767
|
aliasesByPath: Map<String, String>,
|
|
1753
1768
|
): JSONArray {
|
|
1754
1769
|
val root = JSONObject(body)
|
|
1770
|
+
val budget = NativeRevealBudget()
|
|
1755
1771
|
val claims = JSONArray()
|
|
1756
1772
|
for (index in 0 until jsonPaths.length()) {
|
|
1757
1773
|
val path = jsonPaths.getString(index)
|
|
1758
1774
|
val value = jsonPathGet(root, path) ?: continue
|
|
1775
|
+
budget.add(value)
|
|
1759
1776
|
claims.put(
|
|
1760
1777
|
JSONObject().apply {
|
|
1761
1778
|
put("name", aliasesByPath[path] ?: claimName(path))
|
|
@@ -1768,7 +1785,7 @@ class SatyaAttestModule : Module() {
|
|
|
1768
1785
|
}
|
|
1769
1786
|
|
|
1770
1787
|
// Structured JSON/regex claim extraction.
|
|
1771
|
-
private fun extractRevealClaims(body: String, reveals: JSONArray): JSONArray {
|
|
1788
|
+
private fun extractRevealClaims(body: String, reveals: JSONArray, budget: NativeRevealBudget = NativeRevealBudget()): JSONArray {
|
|
1772
1789
|
val claims = JSONArray()
|
|
1773
1790
|
val seenAliases = mutableMapOf<String, String>()
|
|
1774
1791
|
var jsonRoot: Any? = null
|
|
@@ -1804,6 +1821,7 @@ class SatyaAttestModule : Module() {
|
|
|
1804
1821
|
else -> null
|
|
1805
1822
|
}
|
|
1806
1823
|
if (value == null || value == JSONObject.NULL) continue
|
|
1824
|
+
budget.add(value)
|
|
1807
1825
|
// Regex-only reveals use the alias as the disclosure path.
|
|
1808
1826
|
val sourcePath = when (type) {
|
|
1809
1827
|
"json", "jsonRegex" -> path
|
|
@@ -1821,20 +1839,11 @@ class SatyaAttestModule : Module() {
|
|
|
1821
1839
|
}
|
|
1822
1840
|
|
|
1823
1841
|
private fun applyRevealRegex(input: String, pattern: String, group: Any?): String? {
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
.
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
if (namedGroup != null) {
|
|
1830
|
-
return runCatching { matcher.group(namedGroup) }.getOrNull()
|
|
1831
|
-
}
|
|
1832
|
-
val groupIndex = when (group) {
|
|
1833
|
-
is Number -> group.toInt()
|
|
1834
|
-
is String -> group.toIntOrNull() ?: 1
|
|
1835
|
-
else -> if (matcher.groupCount() >= 1) 1 else 0
|
|
1836
|
-
}
|
|
1837
|
-
return runCatching { matcher.group(groupIndex) }.getOrNull()
|
|
1842
|
+
return matchRevealRegex(input, pattern, when (group) {
|
|
1843
|
+
is String -> group
|
|
1844
|
+
is Number -> group.toInt().toString()
|
|
1845
|
+
else -> null
|
|
1846
|
+
})
|
|
1838
1847
|
}
|
|
1839
1848
|
|
|
1840
1849
|
// Buffer one byte past the cap so oversize bodies fail closed.
|
|
@@ -470,16 +470,16 @@ data class CompositeExchangeObservation(
|
|
|
470
470
|
)
|
|
471
471
|
|
|
472
472
|
/**
|
|
473
|
-
* Per-response (
|
|
473
|
+
* Per-response (96 MiB) and aggregate (768 MiB) response-byte caps for the
|
|
474
474
|
* composite bundle, mirroring the Rust `MAX_EXCHANGE_RESPONSE_BYTES` /
|
|
475
475
|
* `MAX_TOTAL_RESPONSE_BYTES`. Pure so the accounting can be unit-tested without a
|
|
476
476
|
* live OkHttp call.
|
|
477
477
|
*/
|
|
478
478
|
object CompositeByteBudget {
|
|
479
|
-
const val MAX_EXCHANGE_RESPONSE_BYTES: Long =
|
|
480
|
-
const val MAX_BUNDLE_RESPONSE_BYTES: Long =
|
|
479
|
+
const val MAX_EXCHANGE_RESPONSE_BYTES: Long = 96L * 1024 * 1024
|
|
480
|
+
const val MAX_BUNDLE_RESPONSE_BYTES: Long = 8 * MAX_EXCHANGE_RESPONSE_BYTES
|
|
481
481
|
|
|
482
|
-
/** Rejects a single exchange whose response exceeds
|
|
482
|
+
/** Rejects a single exchange whose response exceeds 96 MiB; returns the count. */
|
|
483
483
|
fun requireExchangeWithinCap(byteCount: Long): Long {
|
|
484
484
|
require(byteCount in 0..MAX_EXCHANGE_RESPONSE_BYTES) {
|
|
485
485
|
"composite replay response exceeds $MAX_EXCHANGE_RESPONSE_BYTES bytes"
|
|
@@ -489,7 +489,7 @@ object CompositeByteBudget {
|
|
|
489
489
|
|
|
490
490
|
/**
|
|
491
491
|
* Adds one exchange's bytes to the running total, enforcing both the
|
|
492
|
-
* per-exchange
|
|
492
|
+
* per-exchange 96 MiB cap and the aggregate 768 MiB cap. Returns the new total.
|
|
493
493
|
*/
|
|
494
494
|
fun accumulate(priorTotal: Long, exchangeBytes: Long): Long {
|
|
495
495
|
requireExchangeWithinCap(exchangeBytes)
|
|
@@ -661,6 +661,10 @@ internal object IntegrityCheckingUniffiLib {
|
|
|
661
661
|
): Short
|
|
662
662
|
external fun uniffi_satya_ffi_checksum_func_generate_native_replay_observation(
|
|
663
663
|
): Short
|
|
664
|
+
external fun uniffi_satya_ffi_checksum_func_match_reveal_regex(
|
|
665
|
+
): Short
|
|
666
|
+
external fun uniffi_satya_ffi_checksum_func_measure_reveal_claim_value(
|
|
667
|
+
): Short
|
|
664
668
|
external fun uniffi_satya_ffi_checksum_func_prepare_native_replay_bundle(
|
|
665
669
|
): Short
|
|
666
670
|
external fun uniffi_satya_ffi_checksum_func_satya_core_version(
|
|
@@ -689,6 +693,10 @@ internal object UniffiLib {
|
|
|
689
693
|
): RustBuffer.ByValue
|
|
690
694
|
external fun uniffi_satya_ffi_fn_func_generate_native_replay_observation(`replayJson`: RustBuffer.ByValue,`evidenceJson`: RustBuffer.ByValue,`signer`: Long,uniffi_out_err: UniffiRustCallStatus,
|
|
691
695
|
): RustBuffer.ByValue
|
|
696
|
+
external fun uniffi_satya_ffi_fn_func_match_reveal_regex(`input`: RustBuffer.ByValue,`pattern`: RustBuffer.ByValue,`group`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus,
|
|
697
|
+
): RustBuffer.ByValue
|
|
698
|
+
external fun uniffi_satya_ffi_fn_func_measure_reveal_claim_value(`valueJson`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus,
|
|
699
|
+
): Long
|
|
692
700
|
external fun uniffi_satya_ffi_fn_func_prepare_native_replay_bundle(`replayBundleJson`: RustBuffer.ByValue,uniffi_out_err: UniffiRustCallStatus,
|
|
693
701
|
): RustBuffer.ByValue
|
|
694
702
|
external fun uniffi_satya_ffi_fn_func_satya_core_version(uniffi_out_err: UniffiRustCallStatus,
|
|
@@ -820,6 +828,12 @@ private fun uniffiCheckApiChecksums(lib: IntegrityCheckingUniffiLib) {
|
|
|
820
828
|
if (lib.uniffi_satya_ffi_checksum_func_generate_native_replay_observation() != 992.toShort()) {
|
|
821
829
|
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
|
|
822
830
|
}
|
|
831
|
+
if (lib.uniffi_satya_ffi_checksum_func_match_reveal_regex() != 15030.toShort()) {
|
|
832
|
+
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
|
|
833
|
+
}
|
|
834
|
+
if (lib.uniffi_satya_ffi_checksum_func_measure_reveal_claim_value() != 26745.toShort()) {
|
|
835
|
+
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
|
|
836
|
+
}
|
|
823
837
|
if (lib.uniffi_satya_ffi_checksum_func_prepare_native_replay_bundle() != 12159.toShort()) {
|
|
824
838
|
throw RuntimeException("UniFFI API checksum mismatch: try cleaning and rebuilding your project")
|
|
825
839
|
}
|
|
@@ -956,6 +970,29 @@ public abstract class FfiConverterCallbackInterface<CallbackInterface: Any>: Ffi
|
|
|
956
970
|
}
|
|
957
971
|
}
|
|
958
972
|
|
|
973
|
+
/**
|
|
974
|
+
* @suppress
|
|
975
|
+
*/
|
|
976
|
+
public object FfiConverterULong: FfiConverter<ULong, Long> {
|
|
977
|
+
override fun lift(value: Long): ULong {
|
|
978
|
+
return value.toULong()
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
override fun read(buf: ByteBuffer): ULong {
|
|
982
|
+
return lift(buf.getLong())
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
override fun lower(value: ULong): Long {
|
|
986
|
+
return value.toLong()
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
override fun allocationSize(value: ULong) = 8UL
|
|
990
|
+
|
|
991
|
+
override fun write(value: ULong, buf: ByteBuffer) {
|
|
992
|
+
buf.putLong(value.toLong())
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
|
|
959
996
|
/**
|
|
960
997
|
* @suppress
|
|
961
998
|
*/
|
|
@@ -1364,6 +1401,34 @@ public object FfiConverterOptionalString: FfiConverterRustBuffer<kotlin.String?>
|
|
|
1364
1401
|
}
|
|
1365
1402
|
|
|
1366
1403
|
|
|
1404
|
+
/**
|
|
1405
|
+
* Match native reveals with the same bounded engine as TEE replay.
|
|
1406
|
+
*/
|
|
1407
|
+
@Throws(FfiException::class) fun `matchRevealRegex`(`input`: kotlin.String, `pattern`: kotlin.String, `group`: kotlin.String?): kotlin.String? {
|
|
1408
|
+
return FfiConverterOptionalString.lift(
|
|
1409
|
+
uniffiRustCallWithError(FfiException) { _status ->
|
|
1410
|
+
UniffiLib.uniffi_satya_ffi_fn_func_match_reveal_regex(
|
|
1411
|
+
|
|
1412
|
+
FfiConverterString.lower(`input`),FfiConverterString.lower(`pattern`),FfiConverterOptionalString.lower(`group`),_status)
|
|
1413
|
+
}
|
|
1414
|
+
)
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
|
|
1418
|
+
/**
|
|
1419
|
+
* Count with the same serializer as native commitments, including quotes and escapes.
|
|
1420
|
+
*/
|
|
1421
|
+
@Throws(FfiException::class) fun `measureRevealClaimValue`(`valueJson`: kotlin.String): kotlin.ULong {
|
|
1422
|
+
return FfiConverterULong.lift(
|
|
1423
|
+
uniffiRustCallWithError(FfiException) { _status ->
|
|
1424
|
+
UniffiLib.uniffi_satya_ffi_fn_func_measure_reveal_claim_value(
|
|
1425
|
+
|
|
1426
|
+
FfiConverterString.lower(`valueJson`),_status)
|
|
1427
|
+
}
|
|
1428
|
+
)
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
|
|
1367
1432
|
/**
|
|
1368
1433
|
* Phase one of the composite flow: parse and fully validate a trusted-native
|
|
1369
1434
|
* replay bundle, fix the session id / nonce / issuance window, and return the
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SatyaMobileClient.d.ts","sourceRoot":"","sources":["../src/SatyaMobileClient.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,wBAAwB,EACxB,iCAAiC,EAClC,MAAM,mCAAmC,CAAC;AAS3C,OAAO,EACL,aAAa,EACb,uBAAuB,EAGvB,qBAAqB,EACrB,aAAa,EACb,oBAAoB,EACpB,YAAY,EACb,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"SatyaMobileClient.d.ts","sourceRoot":"","sources":["../src/SatyaMobileClient.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,wBAAwB,EACxB,iCAAiC,EAClC,MAAM,mCAAmC,CAAC;AAS3C,OAAO,EACL,aAAa,EACb,uBAAuB,EAGvB,qBAAqB,EACrB,aAAa,EACb,oBAAoB,EACpB,YAAY,EACb,MAAM,SAAS,CAAC;AASjB,KAAK,gBAAgB,GAAG,CAAC,QAAQ,EAAE,aAAa,KAAK,IAAI,CAAC;AA0B1D,MAAM,WAAW,sBAAsB;IACrC,gBAAgB,EAAE,qBAAqB,CAAC;IACxC,eAAe,EAAE,oBAAoB,CAAC;IACtC,eAAe,CAAC,EAAE,uBAAuB,GAAG,IAAI,CAAC;IACjD,oEAAoE;IACpE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,4BAA4B;IAC3C,gBAAgB,EAAE,qBAAqB,CAAC;IACxC,eAAe,EAAE,oBAAoB,CAAC;IACtC,iEAAiE;IACjE,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAC;IAC1D,+EAA+E;IAC/E,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,oEAAoE;IACpE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAOD,oFAAoF;AACpF,MAAM,MAAM,sBAAsB,GAAG,aAAa,GAAG,iCAAiC,CAAC;AAEvF,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,aAAa,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC;IACrB,2EAA2E;IAC3E,aAAa,CAAC,EAAE,wBAAwB,GAAG,IAAI,CAAC;CACjD;AAcD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA+B;IAEzD,iBAAiB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,MAAM,IAAI;IAOzD,OAAO,CAAC,IAAI;IAYZ,+EAA+E;IACzE,YAAY,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC;IAmBjF,+EAA+E;IACzE,kBAAkB,CAAC,IAAI,EAAE,4BAA4B,GAAG,OAAO,CAAC,sBAAsB,CAAC;YAyB/E,YAAY;IAiEpB,kBAAkB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,aAAa,CAAC;IAyI9E;;;;;OAKG;IACG,wBAAwB,CAAC,IAAI,EAAE,4BAA4B,GAAG,OAAO,CAAC,aAAa,CAAC;IA2SpF,MAAM,CACV,YAAY,EAAE,MAAM,EACpB,eAAe,CAAC,EAAE,oBAAoB,GACrC,OAAO,CAAC,YAAY,CAAC;IAmDxB,WAAW,IAAI,MAAM;IAIrB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAGzB"}
|
|
@@ -6,6 +6,7 @@ import { nativeReplayBundleArgs } from './nativeReplayArgs';
|
|
|
6
6
|
import { reportProviderIngest } from './providerMonitor';
|
|
7
7
|
import { getSatyaRuntimeConfig, isPartialEndpointsAllowedForProvider, resolveAppId, } from './satyaConfig';
|
|
8
8
|
import { SatyaErrorCode, asSatyaError, satyaError, } from './types/error.types';
|
|
9
|
+
import { assertArtifactSize } from './utils/dataLimits';
|
|
9
10
|
const REPLAY_PROGRESS = [
|
|
10
11
|
['witnessEnrollment', 'Enrolling witness key', 0.12],
|
|
11
12
|
['sessionChallenge', 'Creating session challenge', 0.24],
|
|
@@ -166,6 +167,7 @@ export class SatyaMobileClient {
|
|
|
166
167
|
try {
|
|
167
168
|
artifact = attachArtifactProducer(JSON.parse(raw), this.coreVersion());
|
|
168
169
|
raw = JSON.stringify(artifact);
|
|
170
|
+
assertArtifactSize(raw);
|
|
169
171
|
}
|
|
170
172
|
catch (cause) {
|
|
171
173
|
const error = satyaError(SatyaErrorCode.ARTIFACT_VERIFICATION_FAILED, 'native replay returned invalid artifact JSON', {
|
|
@@ -350,6 +352,7 @@ export class SatyaMobileClient {
|
|
|
350
352
|
try {
|
|
351
353
|
artifact = attachArtifactProducer(JSON.parse(raw), this.coreVersion());
|
|
352
354
|
raw = JSON.stringify(artifact);
|
|
355
|
+
assertArtifactSize(raw);
|
|
353
356
|
}
|
|
354
357
|
catch (cause) {
|
|
355
358
|
const error = satyaError(SatyaErrorCode.ARTIFACT_VERIFICATION_FAILED, 'native replay bundle returned invalid artifact JSON', {
|