@tangle-network/agent-provider-tangle 0.14.0 → 0.14.1

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/README.md CHANGED
@@ -195,6 +195,13 @@ const provider = createTangleProvider({
195
195
  })
196
196
  ```
197
197
 
198
+ The provider stores the raw report in `ConfidentialAttestation.quote` through
199
+ `encodeTangleConfidentialAttestationQuote`.
200
+ The quote is a versioned canonical JSON object with base64url byte fields.
201
+ Use `decodeTangleConfidentialAttestationQuote` to recover a report and reject
202
+ legacy numeric-array quotes, unknown fields, malformed bytes, and oversized
203
+ reports before verification.
204
+
198
205
  ## Environment observation
199
206
 
200
207
  `environment.observe()` returns the normalized `AgentEnvironmentObservation`.
package/dist/index.d.ts CHANGED
@@ -4,4 +4,6 @@ export { createTangleProvider } from "./tangle-provider.js";
4
4
  export { defaultTangleSandboxCapabilities } from "./tangle-capabilities.js";
5
5
  export { createTangleWorkspaceBranching, supportsWorkspaceBranching, } from "./tangle-workspace-branching.js";
6
6
  export type { TangleWorkspaceBranchingOptions } from "./tangle-workspace-branching.js";
7
+ export { decodeTangleConfidentialAttestationQuote, encodeTangleConfidentialAttestationQuote, MAX_TEE_EVIDENCE_BYTES, MAX_TEE_MEASUREMENT_BYTES, TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND, TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION, } from "./tangle-confidential-attestation.js";
8
+ export type { TangleConfidentialAttestationReport } from "./tangle-confidential-attestation.js";
7
9
  export { safeEndpointFromConnection } from "./tangle-observation.js";
package/dist/index.js CHANGED
@@ -2,4 +2,5 @@ export * from "./tangle-types.js";
2
2
  export { createTangleProvider } from "./tangle-provider.js";
3
3
  export { defaultTangleSandboxCapabilities } from "./tangle-capabilities.js";
4
4
  export { createTangleWorkspaceBranching, supportsWorkspaceBranching, } from "./tangle-workspace-branching.js";
5
+ export { decodeTangleConfidentialAttestationQuote, encodeTangleConfidentialAttestationQuote, MAX_TEE_EVIDENCE_BYTES, MAX_TEE_MEASUREMENT_BYTES, TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND, TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION, } from "./tangle-confidential-attestation.js";
5
6
  export { safeEndpointFromConnection } from "./tangle-observation.js";
@@ -0,0 +1,31 @@
1
+ import type { SandboxTeeAttestationReportLike } from "./tangle-types.js";
2
+ /** Version of the canonical opaque quote carried by a confidential attestation. */
3
+ export declare const TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION: 1;
4
+ /** Kind discriminator prevents this codec from accepting another quote format. */
5
+ export declare const TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND: "tangle-sandbox-tee";
6
+ /** Raw evidence bound before it can enter a portable attestation. */
7
+ export declare const MAX_TEE_EVIDENCE_BYTES = 16384;
8
+ /** Raw measurement bound before it can enter a portable attestation. */
9
+ export declare const MAX_TEE_MEASUREMENT_BYTES = 256;
10
+ /** Read-only report shape accepted by the quote encoder. */
11
+ export interface TangleConfidentialAttestationReport {
12
+ readonly tee_type: string;
13
+ readonly evidence: readonly number[];
14
+ readonly measurement: readonly number[];
15
+ readonly timestamp: number;
16
+ }
17
+ /**
18
+ * Encode a Sandbox TEE report without decimal JSON byte arrays.
19
+ *
20
+ * The returned JSON uses a fixed key order and unpadded base64url byte fields.
21
+ * `undefined` means the report is not a valid bounded report or the quote does
22
+ * not fit the shared confidential-attestation field.
23
+ */
24
+ export declare function encodeTangleConfidentialAttestationQuote(report: TangleConfidentialAttestationReport): string | undefined;
25
+ /**
26
+ * Decode and canonicalize a provider quote.
27
+ *
28
+ * The parser rejects old numeric-array quotes, unknown fields, non-canonical
29
+ * JSON, malformed base64url, and reports outside the provider bounds.
30
+ */
31
+ export declare function decodeTangleConfidentialAttestationQuote(quote: unknown): SandboxTeeAttestationReportLike | undefined;
@@ -0,0 +1,165 @@
1
+ import { CONTRACT_MAX_CONFIDENTIAL_ATTESTATION_QUOTE_LENGTH, } from "@tangle-network/agent-interface";
2
+ /** Version of the canonical opaque quote carried by a confidential attestation. */
3
+ export const TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION = 1;
4
+ /** Kind discriminator prevents this codec from accepting another quote format. */
5
+ export const TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND = "tangle-sandbox-tee";
6
+ /** Raw evidence bound before it can enter a portable attestation. */
7
+ export const MAX_TEE_EVIDENCE_BYTES = 16_384;
8
+ /** Raw measurement bound before it can enter a portable attestation. */
9
+ export const MAX_TEE_MEASUREMENT_BYTES = 256;
10
+ const MAX_TEE_TYPE_LENGTH = 64;
11
+ const BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/u;
12
+ const QUOTE_KEYS = [
13
+ "version",
14
+ "kind",
15
+ "tee_type",
16
+ "evidence",
17
+ "measurement",
18
+ "timestamp",
19
+ ];
20
+ /**
21
+ * Encode a Sandbox TEE report without decimal JSON byte arrays.
22
+ *
23
+ * The returned JSON uses a fixed key order and unpadded base64url byte fields.
24
+ * `undefined` means the report is not a valid bounded report or the quote does
25
+ * not fit the shared confidential-attestation field.
26
+ */
27
+ export function encodeTangleConfidentialAttestationQuote(report) {
28
+ if (!validReportShape(report))
29
+ return undefined;
30
+ const document = {
31
+ version: TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION,
32
+ kind: TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND,
33
+ tee_type: report.tee_type,
34
+ evidence: bytesToBase64url(report.evidence),
35
+ measurement: bytesToBase64url(report.measurement),
36
+ timestamp: report.timestamp,
37
+ };
38
+ const quote = JSON.stringify(document);
39
+ return quote.length <= CONTRACT_MAX_CONFIDENTIAL_ATTESTATION_QUOTE_LENGTH
40
+ ? quote
41
+ : undefined;
42
+ }
43
+ /**
44
+ * Decode and canonicalize a provider quote.
45
+ *
46
+ * The parser rejects old numeric-array quotes, unknown fields, non-canonical
47
+ * JSON, malformed base64url, and reports outside the provider bounds.
48
+ */
49
+ export function decodeTangleConfidentialAttestationQuote(quote) {
50
+ if (typeof quote !== "string" ||
51
+ quote.length === 0 ||
52
+ quote.length > CONTRACT_MAX_CONFIDENTIAL_ATTESTATION_QUOTE_LENGTH) {
53
+ return undefined;
54
+ }
55
+ let parsed;
56
+ try {
57
+ parsed = JSON.parse(quote);
58
+ }
59
+ catch {
60
+ return undefined;
61
+ }
62
+ const document = quoteDocument(parsed);
63
+ if (document === undefined)
64
+ return undefined;
65
+ const report = reportFromDocument(document);
66
+ if (report === undefined)
67
+ return undefined;
68
+ return encodeTangleConfidentialAttestationQuote(report) === quote
69
+ ? report
70
+ : undefined;
71
+ }
72
+ function validReportShape(report) {
73
+ if (!isPlainObject(report))
74
+ return false;
75
+ if (!hasExactKeys(report, ["tee_type", "evidence", "measurement", "timestamp"])) {
76
+ return false;
77
+ }
78
+ return (validTeeType(report.tee_type) &&
79
+ validBytes(report.evidence, MAX_TEE_EVIDENCE_BYTES) &&
80
+ validBytes(report.measurement, MAX_TEE_MEASUREMENT_BYTES) &&
81
+ Number.isSafeInteger(report.timestamp) &&
82
+ report.timestamp > 0);
83
+ }
84
+ function quoteDocument(value) {
85
+ if (!isPlainObject(value) || !hasExactKeys(value, QUOTE_KEYS))
86
+ return undefined;
87
+ const candidate = value;
88
+ const teeType = candidate.tee_type;
89
+ const evidenceValue = candidate.evidence;
90
+ const measurementValue = candidate.measurement;
91
+ const timestamp = candidate.timestamp;
92
+ if (candidate.version !== TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION ||
93
+ candidate.kind !== TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND ||
94
+ !validTeeType(teeType) ||
95
+ typeof evidenceValue !== "string" ||
96
+ typeof measurementValue !== "string" ||
97
+ typeof timestamp !== "number" ||
98
+ !Number.isSafeInteger(timestamp) ||
99
+ timestamp <= 0) {
100
+ return undefined;
101
+ }
102
+ const evidence = base64urlToBytes(evidenceValue, MAX_TEE_EVIDENCE_BYTES);
103
+ const measurement = base64urlToBytes(measurementValue, MAX_TEE_MEASUREMENT_BYTES);
104
+ if (evidence === undefined || measurement === undefined)
105
+ return undefined;
106
+ return {
107
+ version: TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_VERSION,
108
+ kind: TANGLE_CONFIDENTIAL_ATTESTATION_QUOTE_KIND,
109
+ tee_type: teeType,
110
+ evidence: evidenceValue,
111
+ measurement: measurementValue,
112
+ timestamp,
113
+ };
114
+ }
115
+ function reportFromDocument(document) {
116
+ const evidence = base64urlToBytes(document.evidence, MAX_TEE_EVIDENCE_BYTES);
117
+ const measurement = base64urlToBytes(document.measurement, MAX_TEE_MEASUREMENT_BYTES);
118
+ if (evidence === undefined || measurement === undefined)
119
+ return undefined;
120
+ const report = {
121
+ tee_type: document.tee_type,
122
+ evidence: Array.from(evidence),
123
+ measurement: Array.from(measurement),
124
+ timestamp: document.timestamp,
125
+ };
126
+ return validReportShape(report) ? report : undefined;
127
+ }
128
+ function validTeeType(value) {
129
+ return (typeof value === "string" &&
130
+ value.length > 0 &&
131
+ value.length <= MAX_TEE_TYPE_LENGTH &&
132
+ value.trim() === value &&
133
+ !/[\u0000-\u001f\u007f]/u.test(value));
134
+ }
135
+ function validBytes(value, maxLength) {
136
+ return (Array.isArray(value) &&
137
+ value.length > 0 &&
138
+ value.length <= maxLength &&
139
+ value.every((byte) => Number.isInteger(byte) && byte >= 0 && byte <= 255));
140
+ }
141
+ function bytesToBase64url(bytes) {
142
+ return Buffer.from(Uint8Array.from(bytes)).toString("base64url");
143
+ }
144
+ function base64urlToBytes(value, maxLength) {
145
+ if (value.length === 0 || !BASE64URL_PATTERN.test(value))
146
+ return undefined;
147
+ const bytes = Buffer.from(value, "base64url");
148
+ if (bytes.length === 0 ||
149
+ bytes.length > maxLength ||
150
+ bytes.toString("base64url") !== value) {
151
+ return undefined;
152
+ }
153
+ return bytes;
154
+ }
155
+ function isPlainObject(value) {
156
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
157
+ return false;
158
+ }
159
+ const prototype = Object.getPrototypeOf(value);
160
+ return prototype === Object.prototype || prototype === null;
161
+ }
162
+ function hasExactKeys(value, expected) {
163
+ const keys = Object.keys(value);
164
+ return keys.length === expected.length && keys.every((key) => expected.includes(key));
165
+ }
@@ -1,5 +1,6 @@
1
1
  import { ConfidentialAttestationSchema, ConfidentialExecutionRequestSchema, ForkedEnvironmentRefSchema, WorkspaceCheckpointRefSchema, WorkspaceCheckpointRequestSchema, WorkspaceCleanupAcknowledgementSchema, WorkspaceCleanupRequestSchema, WorkspaceForkRequestSchema, WorkspaceOperationLookupRequestSchema, WorkspaceCheckpointResultSchema, WorkspaceCheckpointLookupResultSchema, WorkspaceForkResultSchema, WorkspaceForkLookupResultSchema, canonicalCandidateDigest, confidentialExecutionVerified, sha256Bytes, } from "@tangle-network/agent-interface";
2
2
  import { awaitWithSignal, boundedIdentifier, boundedString, assertBoundedJson, MAX_LIST_RESULTS, MAX_STRING_LENGTH, SANDBOX_LIST_PAGE_SIZE, } from "./tangle-contract-safety.js";
3
+ import { encodeTangleConfidentialAttestationQuote, MAX_TEE_EVIDENCE_BYTES, MAX_TEE_MEASUREMENT_BYTES, } from "./tangle-confidential-attestation.js";
3
4
  /**
4
5
  * Namespace used for provider recovery metadata.
5
6
  *
@@ -532,7 +533,7 @@ async function confidentialAttestationForChild(request, child, provider, verifie
532
533
  return undefined;
533
534
  }
534
535
  const measurement = sha256Bytes(Uint8Array.from(response.attestation.measurement));
535
- const quote = encodeJson(response.attestation);
536
+ const quote = encodeTangleConfidentialAttestationQuote(response.attestation);
536
537
  if (quote === undefined)
537
538
  return undefined;
538
539
  let verifiedAt;
@@ -610,10 +611,10 @@ function validTeeReport(report) {
610
611
  return !!report &&
611
612
  safeString(report.tee_type) !== undefined &&
612
613
  Array.isArray(report.evidence) &&
613
- report.evidence.length <= MAX_STRING_LENGTH &&
614
+ report.evidence.length <= MAX_TEE_EVIDENCE_BYTES &&
614
615
  report.evidence.every((value) => Number.isInteger(value) && value >= 0 && value <= 255) &&
615
616
  Array.isArray(report.measurement) &&
616
- report.measurement.length <= MAX_STRING_LENGTH &&
617
+ report.measurement.length <= MAX_TEE_MEASUREMENT_BYTES &&
617
618
  report.measurement.every((value) => Number.isInteger(value) && value >= 0 && value <= 255) &&
618
619
  Number.isFinite(report.timestamp) &&
619
620
  report.timestamp > 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/agent-provider-tangle",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
4
4
  "description": "AgentEnvironmentProvider adapter for Tangle sandboxes",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -83,11 +83,13 @@
83
83
  "dist/tangle-types.js",
84
84
  "dist/tangle-workspace-branching.d.ts",
85
85
  "dist/tangle-workspace-branching.js",
86
+ "dist/tangle-confidential-attestation.d.ts",
87
+ "dist/tangle-confidential-attestation.js",
86
88
  "README.md",
87
89
  "LICENSE"
88
90
  ],
89
91
  "dependencies": {
90
- "@tangle-network/agent-interface": "^1.7.0"
92
+ "@tangle-network/agent-interface": "^1.7.1"
91
93
  },
92
94
  "peerDependencies": {
93
95
  "@tangle-network/sandbox": ">=0.33.1 <1.0.0"