@zeke-02/tinfoil 0.0.2

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 (65) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +169 -0
  3. package/dist/__tests__/test-utils.d.ts +1 -0
  4. package/dist/__tests__/test-utils.js +44 -0
  5. package/dist/ai-sdk-provider.d.ts +7 -0
  6. package/dist/ai-sdk-provider.js +23 -0
  7. package/dist/config.d.ts +17 -0
  8. package/dist/config.js +20 -0
  9. package/dist/encrypted-body-fetch.d.ts +8 -0
  10. package/dist/encrypted-body-fetch.js +93 -0
  11. package/dist/env.d.ts +5 -0
  12. package/dist/env.js +20 -0
  13. package/dist/esm/__tests__/test-utils.d.ts +1 -0
  14. package/dist/esm/__tests__/test-utils.js +38 -0
  15. package/dist/esm/ai-sdk-provider.d.ts +7 -0
  16. package/dist/esm/ai-sdk-provider.js +20 -0
  17. package/dist/esm/config.d.ts +17 -0
  18. package/dist/esm/config.js +17 -0
  19. package/dist/esm/encrypted-body-fetch.d.ts +8 -0
  20. package/dist/esm/encrypted-body-fetch.js +86 -0
  21. package/dist/esm/env.d.ts +5 -0
  22. package/dist/esm/env.js +17 -0
  23. package/dist/esm/fetch-adapter.d.ts +21 -0
  24. package/dist/esm/fetch-adapter.js +23 -0
  25. package/dist/esm/index.browser.d.ts +7 -0
  26. package/dist/esm/index.browser.js +8 -0
  27. package/dist/esm/index.d.ts +8 -0
  28. package/dist/esm/index.js +12 -0
  29. package/dist/esm/pinned-tls-fetch.d.ts +1 -0
  30. package/dist/esm/pinned-tls-fetch.js +110 -0
  31. package/dist/esm/secure-client.d.ts +20 -0
  32. package/dist/esm/secure-client.js +123 -0
  33. package/dist/esm/secure-fetch.browser.d.ts +1 -0
  34. package/dist/esm/secure-fetch.browser.js +10 -0
  35. package/dist/esm/secure-fetch.d.ts +1 -0
  36. package/dist/esm/secure-fetch.js +22 -0
  37. package/dist/esm/tinfoilai.d.ts +54 -0
  38. package/dist/esm/tinfoilai.js +134 -0
  39. package/dist/esm/unverified-client.d.ts +18 -0
  40. package/dist/esm/unverified-client.js +33 -0
  41. package/dist/esm/verifier.d.ts +141 -0
  42. package/dist/esm/verifier.js +741 -0
  43. package/dist/esm/wasm-exec.js +668 -0
  44. package/dist/fetch-adapter.d.ts +21 -0
  45. package/dist/fetch-adapter.js +27 -0
  46. package/dist/index.browser.d.ts +7 -0
  47. package/dist/index.browser.js +29 -0
  48. package/dist/index.d.ts +8 -0
  49. package/dist/index.js +49 -0
  50. package/dist/pinned-tls-fetch.d.ts +1 -0
  51. package/dist/pinned-tls-fetch.js +116 -0
  52. package/dist/secure-client.d.ts +20 -0
  53. package/dist/secure-client.js +127 -0
  54. package/dist/secure-fetch.browser.d.ts +1 -0
  55. package/dist/secure-fetch.browser.js +13 -0
  56. package/dist/secure-fetch.d.ts +1 -0
  57. package/dist/secure-fetch.js +25 -0
  58. package/dist/tinfoilai.d.ts +54 -0
  59. package/dist/tinfoilai.js +141 -0
  60. package/dist/unverified-client.d.ts +18 -0
  61. package/dist/unverified-client.js +37 -0
  62. package/dist/verifier.d.ts +141 -0
  63. package/dist/verifier.js +781 -0
  64. package/dist/wasm-exec.js +668 -0
  65. package/package.json +97 -0
package/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # Tinfoil Node Client
2
+
3
+ [![Build Status](https://github.com/tinfoilsh/tinfoil-node/actions/workflows/test.yml/badge.svg)](https://github.com/tinfoilsh/tinfoil-node/actions)
4
+ [![NPM version](https://img.shields.io/npm/v/tinfoil.svg)](https://npmjs.org/package/tinfoil)
5
+
6
+ This client library provides secure and convenient access to the Tinfoil Priavate Inference endpoints from TypeScript or JavaScript.
7
+
8
+ It is a wrapper around the OpenAI client that verifies enclave attestation and routes traffic to the Tinfoil Private Inference endpoints through an [EHBP](https://github.com/tinfoilsh/encrypted-http-body-protocol)-secured transport. EHBP encrypts all payloads directly to an attested enclave using [HPKE (RFC 9180)](https://www.rfc-editor.org/rfc/rfc9180.html).
9
+
10
+ ## Installation
11
+
12
+ ```bash
13
+ npm install tinfoil
14
+ ```
15
+
16
+ ## Requirements
17
+
18
+ Node 20+.
19
+
20
+ ## Quick Start
21
+
22
+ ```typescript
23
+ import { TinfoilAI } from "tinfoil";
24
+
25
+ const client = new TinfoilAI({
26
+ apiKey: "<YOUR_API_KEY>", // or use TINFOIL_API_KEY env var
27
+ });
28
+
29
+ // Uses identical method calls as the OpenAI client
30
+ const completion = await client.chat.completions.create({
31
+ messages: [{ role: "user", content: "Hello!" }],
32
+ model: "llama3-3-70b",
33
+ });
34
+ ```
35
+
36
+ ## Browser Support
37
+
38
+ The SDK supports browser environments. This allows you to use the secure enclave-backed OpenAI API directly from web applications.
39
+
40
+ ### ⚠️ Security Warning
41
+
42
+ Using API keys directly in the browser exposes them to anyone who can view your page source.
43
+ For production applications, always use a backend server to handle API keys.
44
+
45
+ ### Browser Usage
46
+
47
+ ```javascript
48
+ import { TinfoilAI } from 'tinfoil';
49
+
50
+ const client = new TinfoilAI({
51
+ apiKey: 'your-api-key',
52
+ dangerouslyAllowBrowser: true // Required for browser usage
53
+ });
54
+
55
+ // Optional: pre-initialize; you can also call APIs directly
56
+ await client.ready();
57
+
58
+ const completion = await client.chat.completions.create({
59
+ model: 'llama3-3-70b',
60
+ messages: [{ role: 'user', content: 'Hello!' }]
61
+ });
62
+ ```
63
+
64
+ ### Browser Requirements
65
+
66
+ - Modern browsers with ES2020 support
67
+ - WebAssembly support for enclave verification
68
+
69
+
70
+ ## Verification helpers
71
+
72
+ This package exposes verification helpers that load the Go-based WebAssembly verifier once per process and provide structured, stepwise attestation results you can use in applications (e.g., to show progress, log transitions, or gate features).
73
+
74
+ The verification functionality is contained in `verifier.ts`.
75
+
76
+
77
+ ### Core Verifier API
78
+
79
+ ```typescript
80
+ import { Verifier } from "tinfoil";
81
+
82
+ const verifier = new Verifier();
83
+
84
+ // Perform runtime attestation
85
+ const runtime = await verifier.verifyEnclave("enclave.host.com");
86
+ // Returns: { measurement: AttestationMeasurement, tlsPublicKeyFingerprint: string, hpkePublicKey: string }
87
+
88
+ // Perform code attestation
89
+ const code = await verifier.verifyCode("tinfoilsh/repo", "digest-hash");
90
+ // Returns: { measurement: AttestationMeasurement }
91
+
92
+ // Fetch latest digest from GitHub releases
93
+ const digest = await verifier.fetchLatestDigest("tinfoilsh/repo");
94
+ ```
95
+
96
+ ### Verification Document
97
+
98
+ The `SecureClient` provides access to a comprehensive verification document that tracks all verification steps, including failures:
99
+
100
+ ```typescript
101
+ import { SecureClient } from "tinfoil";
102
+
103
+ const client = new SecureClient();
104
+
105
+ try {
106
+ await client.ready();
107
+ const response = await client.fetch('https://api.example.com/data');
108
+ } catch (error) {
109
+ // Even on error, you can access the verification document
110
+ const doc = await client.getVerificationDocument();
111
+
112
+ // The document contains detailed step information:
113
+ // - fetchDigest: GitHub release digest retrieval
114
+ // - verifyCode: Code measurement verification
115
+ // - verifyEnclave: Runtime attestation verification
116
+ // - compareMeasurements: Code vs runtime measurement comparison
117
+ // - createTransport: Transport initialization (optional)
118
+ // - verifyHPKEKey: HPKE key verification (optional)
119
+ // - otherError: Catch-all for unexpected errors (optional)
120
+
121
+ console.log('Security verified:', doc.securityVerified);
122
+
123
+ // Check individual steps
124
+ if (doc.steps.verifyEnclave.status === 'failed') {
125
+ console.log('Enclave verification failed:', doc.steps.verifyEnclave.error);
126
+ }
127
+ }
128
+ ```
129
+
130
+ ## Testing
131
+
132
+ The project includes both unit tests and integration tests:
133
+
134
+ ### Running Unit Tests
135
+
136
+ ```bash
137
+ npm test
138
+ ```
139
+
140
+ ### Running Integration Tests
141
+
142
+ ```bash
143
+ RUN_TINFOIL_INTEGRATION=true npm test
144
+ ```
145
+
146
+ This runs the full test suite including integration tests that:
147
+ - Make actual network requests to Tinfoil services
148
+ - Perform real enclave attestation verification
149
+ - Test end-to-end functionality with live services
150
+
151
+ Integration tests are skipped by default to keep the test suite fast and avoid network dependencies during development.
152
+
153
+ ## Running examples
154
+
155
+ See [examples/README.md](https://github.com/tinfoilsh/tinfoil-node/blob/main/examples/README.md).
156
+
157
+ ## API Documentation
158
+
159
+ This library mirrors the official OpenAI Node.js client for common endpoints (e.g., chat, images, embeddings) and types, and is designed to feel familiar. Some less commonly used surfaces may not be fully covered. See the [OpenAI client](https://github.com/openai/openai-node) for complete API usage and documentation.
160
+
161
+ ## Reporting Vulnerabilities
162
+
163
+ Please report security vulnerabilities by either:
164
+
165
+ - Emailing [security@tinfoil.sh](mailto:security@tinfoil.sh)
166
+
167
+ - Opening an issue on GitHub on this repository
168
+
169
+ We aim to respond to security reports within 24 hours and will keep you updated on our progress.
@@ -0,0 +1 @@
1
+ export declare function withMockedModules(mocks: Record<string, unknown>, modulesToReload: string[], run: () => Promise<void>): Promise<void>;
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.withMockedModules = withMockedModules;
7
+ const module_1 = __importDefault(require("module"));
8
+ const testRequire = module_1.default.createRequire(__filename);
9
+ async function withMockedModules(mocks, modulesToReload, run) {
10
+ const moduleAny = module_1.default;
11
+ const originalLoad = moduleAny._load;
12
+ moduleAny._load = function (request, parent, isMain) {
13
+ if (Object.prototype.hasOwnProperty.call(mocks, request)) {
14
+ return mocks[request];
15
+ }
16
+ // eslint-disable-next-line prefer-rest-params
17
+ return originalLoad.apply(this, arguments);
18
+ };
19
+ const restoredCache = [];
20
+ for (const specifier of modulesToReload) {
21
+ try {
22
+ const resolved = testRequire.resolve(specifier);
23
+ restoredCache.push({ path: resolved, cached: require.cache[resolved] });
24
+ delete require.cache[resolved];
25
+ }
26
+ catch {
27
+ // Module not yet cached; nothing to remove.
28
+ }
29
+ }
30
+ try {
31
+ await run();
32
+ }
33
+ finally {
34
+ moduleAny._load = originalLoad;
35
+ for (const entry of restoredCache) {
36
+ if (entry.cached) {
37
+ require.cache[entry.path] = entry.cached;
38
+ }
39
+ else {
40
+ delete require.cache[entry.path];
41
+ }
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,7 @@
1
+ interface CreateTinfoilAIOptions {
2
+ baseURL?: string;
3
+ enclaveURL?: string;
4
+ configRepo?: string;
5
+ }
6
+ export declare function createTinfoilAI(apiKey: string, options?: CreateTinfoilAIOptions): Promise<import("@ai-sdk/openai-compatible").OpenAICompatibleProvider<string, string, string, string>>;
7
+ export {};
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createTinfoilAI = createTinfoilAI;
4
+ const openai_compatible_1 = require("@ai-sdk/openai-compatible");
5
+ const config_1 = require("./config");
6
+ const secure_client_1 = require("./secure-client");
7
+ async function createTinfoilAI(apiKey, options = {}) {
8
+ const baseURL = options.baseURL || config_1.TINFOIL_CONFIG.INFERENCE_BASE_URL;
9
+ const enclaveURL = options.enclaveURL || config_1.TINFOIL_CONFIG.ENCLAVE_URL;
10
+ const configRepo = options.configRepo || config_1.TINFOIL_CONFIG.INFERENCE_PROXY_REPO;
11
+ const secureClient = new secure_client_1.SecureClient({
12
+ baseURL,
13
+ enclaveURL,
14
+ configRepo,
15
+ });
16
+ await secureClient.ready();
17
+ return (0, openai_compatible_1.createOpenAICompatible)({
18
+ name: "tinfoil",
19
+ baseURL: baseURL.replace(/\/$/, ""),
20
+ apiKey: apiKey,
21
+ fetch: secureClient.fetch,
22
+ });
23
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Configuration constants for the Tinfoil Node SDK
3
+ */
4
+ export declare const TINFOIL_CONFIG: {
5
+ /**
6
+ * The base URL for the Tinfoil router API
7
+ */
8
+ readonly INFERENCE_BASE_URL: "https://router.inf6.tinfoil.sh/v1/";
9
+ /**
10
+ * The URL for enclave key discovery and attestation endpoints
11
+ */
12
+ readonly ENCLAVE_URL: "https://router.inf6.tinfoil.sh";
13
+ /**
14
+ * The GitHub repository for code attestation verification
15
+ */
16
+ readonly INFERENCE_PROXY_REPO: "tinfoilsh/confidential-model-router";
17
+ };
package/dist/config.js ADDED
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TINFOIL_CONFIG = void 0;
4
+ /**
5
+ * Configuration constants for the Tinfoil Node SDK
6
+ */
7
+ exports.TINFOIL_CONFIG = {
8
+ /**
9
+ * The base URL for the Tinfoil router API
10
+ */
11
+ INFERENCE_BASE_URL: "https://router.inf6.tinfoil.sh/v1/",
12
+ /**
13
+ * The URL for enclave key discovery and attestation endpoints
14
+ */
15
+ ENCLAVE_URL: "https://router.inf6.tinfoil.sh",
16
+ /**
17
+ * The GitHub repository for code attestation verification
18
+ */
19
+ INFERENCE_PROXY_REPO: "tinfoilsh/confidential-model-router",
20
+ };
@@ -0,0 +1,8 @@
1
+ export declare function getHPKEKey(enclaveURL: string): Promise<CryptoKey>;
2
+ export declare function normalizeEncryptedBodyRequestArgs(input: RequestInfo | URL, init?: RequestInit): {
3
+ url: string;
4
+ init?: RequestInit;
5
+ };
6
+ export declare function encryptedBodyRequest(input: RequestInfo | URL, hpkePublicKey?: string, init?: RequestInit, enclaveURL?: string): Promise<Response>;
7
+ export declare function createEncryptedBodyFetch(baseURL: string, hpkePublicKey?: string, enclaveURL?: string): typeof fetch;
8
+ export declare function resetTransport(): void;
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getHPKEKey = getHPKEKey;
4
+ exports.normalizeEncryptedBodyRequestArgs = normalizeEncryptedBodyRequestArgs;
5
+ exports.encryptedBodyRequest = encryptedBodyRequest;
6
+ exports.createEncryptedBodyFetch = createEncryptedBodyFetch;
7
+ exports.resetTransport = resetTransport;
8
+ const ehbp_1 = require("@zeke-02/ehbp");
9
+ const fetch_adapter_1 = require("./fetch-adapter");
10
+ let transport = null;
11
+ // Public API
12
+ async function getHPKEKey(enclaveURL) {
13
+ const keysURL = new URL(ehbp_1.PROTOCOL.KEYS_PATH, enclaveURL);
14
+ if (keysURL.protocol !== "https:") {
15
+ throw new Error(`HTTPS is required for remote key retrieval. Invalid protocol: ${keysURL.protocol}`);
16
+ }
17
+ const fetchFn = (0, fetch_adapter_1.getFetch)();
18
+ const response = await fetchFn(keysURL.toString());
19
+ if (!response.ok) {
20
+ throw new Error(`Failed to get server public key: ${response.status}`);
21
+ }
22
+ const contentType = response.headers.get("content-type");
23
+ if (contentType !== ehbp_1.PROTOCOL.KEYS_MEDIA_TYPE) {
24
+ throw new Error(`Invalid content type: ${contentType}`);
25
+ }
26
+ const keysData = new Uint8Array(await response.arrayBuffer());
27
+ const serverIdentity = await ehbp_1.Identity.unmarshalPublicConfig(keysData);
28
+ return serverIdentity.getPublicKey();
29
+ }
30
+ function normalizeEncryptedBodyRequestArgs(input, init) {
31
+ if (typeof input === "string") {
32
+ return { url: input, init };
33
+ }
34
+ if (input instanceof URL) {
35
+ return { url: input.toString(), init };
36
+ }
37
+ const request = input;
38
+ const cloned = request.clone();
39
+ const derivedInit = {
40
+ method: cloned.method,
41
+ headers: new Headers(cloned.headers),
42
+ body: cloned.body ?? undefined,
43
+ signal: cloned.signal,
44
+ };
45
+ return {
46
+ url: cloned.url,
47
+ init: { ...derivedInit, ...init },
48
+ };
49
+ }
50
+ async function encryptedBodyRequest(input, hpkePublicKey, init, enclaveURL) {
51
+ const { url: requestUrl, init: requestInit } = normalizeEncryptedBodyRequestArgs(input, init);
52
+ const u = new URL(requestUrl);
53
+ const { origin } = u;
54
+ const keyOrigin = enclaveURL ? new URL(enclaveURL).origin : origin;
55
+ if (!transport) {
56
+ transport = getTransportForOrigin(origin, keyOrigin);
57
+ }
58
+ const transportInstance = await transport;
59
+ if (hpkePublicKey) {
60
+ const transportKeyHash = await transportInstance.getServerPublicKeyHex();
61
+ if (transportKeyHash !== hpkePublicKey) {
62
+ transport = null;
63
+ throw new Error(`HPKE public key mismatch. Expected: ${hpkePublicKey}, Got: ${transportKeyHash}`);
64
+ }
65
+ }
66
+ return transportInstance.request(requestUrl, requestInit);
67
+ }
68
+ function createEncryptedBodyFetch(baseURL, hpkePublicKey, enclaveURL) {
69
+ return (async (input, init) => {
70
+ const normalized = normalizeEncryptedBodyRequestArgs(input, init);
71
+ const targetUrl = new URL(normalized.url, baseURL);
72
+ return encryptedBodyRequest(targetUrl.toString(), hpkePublicKey, normalized.init, enclaveURL);
73
+ });
74
+ }
75
+ function resetTransport() {
76
+ transport = null;
77
+ }
78
+ async function getTransportForOrigin(origin, keyOrigin) {
79
+ if (typeof globalThis !== "undefined") {
80
+ const isSecure = globalThis.isSecureContext !== false;
81
+ const hasSubtle = !!(globalThis.crypto && globalThis.crypto.subtle);
82
+ if (!isSecure || !hasSubtle) {
83
+ const reason = !isSecure
84
+ ? "insecure context (use HTTPS or localhost)"
85
+ : "missing WebCrypto SubtleCrypto";
86
+ throw new Error(`EHBP requires a secure browser context: ${reason}`);
87
+ }
88
+ }
89
+ const clientIdentity = await ehbp_1.Identity.generate();
90
+ const serverPublicKey = await getHPKEKey(keyOrigin);
91
+ const requestHost = new URL(origin).host;
92
+ return new ehbp_1.Transport(clientIdentity, requestHost, serverPublicKey);
93
+ }
package/dist/env.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Detects if the code is running in a real browser environment.
3
+ * Returns false for Node.js environments, even with WASM loaded.
4
+ */
5
+ export declare function isRealBrowser(): boolean;
package/dist/env.js ADDED
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isRealBrowser = isRealBrowser;
4
+ /**
5
+ * Detects if the code is running in a real browser environment.
6
+ * Returns false for Node.js environments, even with WASM loaded.
7
+ */
8
+ function isRealBrowser() {
9
+ if (typeof process !== "undefined" &&
10
+ process.versions &&
11
+ process.versions.node) {
12
+ return false;
13
+ }
14
+ if (typeof window !== "undefined" && typeof window.document !== "undefined") {
15
+ if (typeof navigator !== "undefined" && navigator.userAgent) {
16
+ return true;
17
+ }
18
+ }
19
+ return false;
20
+ }
@@ -0,0 +1 @@
1
+ export declare function withMockedModules(mocks: Record<string, unknown>, modulesToReload: string[], run: () => Promise<void>): Promise<void>;
@@ -0,0 +1,38 @@
1
+ import Module from "module";
2
+ const testRequire = Module.createRequire(__filename);
3
+ export async function withMockedModules(mocks, modulesToReload, run) {
4
+ const moduleAny = Module;
5
+ const originalLoad = moduleAny._load;
6
+ moduleAny._load = function (request, parent, isMain) {
7
+ if (Object.prototype.hasOwnProperty.call(mocks, request)) {
8
+ return mocks[request];
9
+ }
10
+ // eslint-disable-next-line prefer-rest-params
11
+ return originalLoad.apply(this, arguments);
12
+ };
13
+ const restoredCache = [];
14
+ for (const specifier of modulesToReload) {
15
+ try {
16
+ const resolved = testRequire.resolve(specifier);
17
+ restoredCache.push({ path: resolved, cached: require.cache[resolved] });
18
+ delete require.cache[resolved];
19
+ }
20
+ catch {
21
+ // Module not yet cached; nothing to remove.
22
+ }
23
+ }
24
+ try {
25
+ await run();
26
+ }
27
+ finally {
28
+ moduleAny._load = originalLoad;
29
+ for (const entry of restoredCache) {
30
+ if (entry.cached) {
31
+ require.cache[entry.path] = entry.cached;
32
+ }
33
+ else {
34
+ delete require.cache[entry.path];
35
+ }
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,7 @@
1
+ interface CreateTinfoilAIOptions {
2
+ baseURL?: string;
3
+ enclaveURL?: string;
4
+ configRepo?: string;
5
+ }
6
+ export declare function createTinfoilAI(apiKey: string, options?: CreateTinfoilAIOptions): Promise<import("@ai-sdk/openai-compatible").OpenAICompatibleProvider<string, string, string, string>>;
7
+ export {};
@@ -0,0 +1,20 @@
1
+ import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
2
+ import { TINFOIL_CONFIG } from "./config";
3
+ import { SecureClient } from "./secure-client";
4
+ export async function createTinfoilAI(apiKey, options = {}) {
5
+ const baseURL = options.baseURL || TINFOIL_CONFIG.INFERENCE_BASE_URL;
6
+ const enclaveURL = options.enclaveURL || TINFOIL_CONFIG.ENCLAVE_URL;
7
+ const configRepo = options.configRepo || TINFOIL_CONFIG.INFERENCE_PROXY_REPO;
8
+ const secureClient = new SecureClient({
9
+ baseURL,
10
+ enclaveURL,
11
+ configRepo,
12
+ });
13
+ await secureClient.ready();
14
+ return createOpenAICompatible({
15
+ name: "tinfoil",
16
+ baseURL: baseURL.replace(/\/$/, ""),
17
+ apiKey: apiKey,
18
+ fetch: secureClient.fetch,
19
+ });
20
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Configuration constants for the Tinfoil Node SDK
3
+ */
4
+ export declare const TINFOIL_CONFIG: {
5
+ /**
6
+ * The base URL for the Tinfoil router API
7
+ */
8
+ readonly INFERENCE_BASE_URL: "https://router.inf6.tinfoil.sh/v1/";
9
+ /**
10
+ * The URL for enclave key discovery and attestation endpoints
11
+ */
12
+ readonly ENCLAVE_URL: "https://router.inf6.tinfoil.sh";
13
+ /**
14
+ * The GitHub repository for code attestation verification
15
+ */
16
+ readonly INFERENCE_PROXY_REPO: "tinfoilsh/confidential-model-router";
17
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Configuration constants for the Tinfoil Node SDK
3
+ */
4
+ export const TINFOIL_CONFIG = {
5
+ /**
6
+ * The base URL for the Tinfoil router API
7
+ */
8
+ INFERENCE_BASE_URL: "https://router.inf6.tinfoil.sh/v1/",
9
+ /**
10
+ * The URL for enclave key discovery and attestation endpoints
11
+ */
12
+ ENCLAVE_URL: "https://router.inf6.tinfoil.sh",
13
+ /**
14
+ * The GitHub repository for code attestation verification
15
+ */
16
+ INFERENCE_PROXY_REPO: "tinfoilsh/confidential-model-router",
17
+ };
@@ -0,0 +1,8 @@
1
+ export declare function getHPKEKey(enclaveURL: string): Promise<CryptoKey>;
2
+ export declare function normalizeEncryptedBodyRequestArgs(input: RequestInfo | URL, init?: RequestInit): {
3
+ url: string;
4
+ init?: RequestInit;
5
+ };
6
+ export declare function encryptedBodyRequest(input: RequestInfo | URL, hpkePublicKey?: string, init?: RequestInit, enclaveURL?: string): Promise<Response>;
7
+ export declare function createEncryptedBodyFetch(baseURL: string, hpkePublicKey?: string, enclaveURL?: string): typeof fetch;
8
+ export declare function resetTransport(): void;
@@ -0,0 +1,86 @@
1
+ import { Identity, Transport, PROTOCOL } from "@zeke-02/ehbp";
2
+ import { getFetch } from "./fetch-adapter";
3
+ let transport = null;
4
+ // Public API
5
+ export async function getHPKEKey(enclaveURL) {
6
+ const keysURL = new URL(PROTOCOL.KEYS_PATH, enclaveURL);
7
+ if (keysURL.protocol !== "https:") {
8
+ throw new Error(`HTTPS is required for remote key retrieval. Invalid protocol: ${keysURL.protocol}`);
9
+ }
10
+ const fetchFn = getFetch();
11
+ const response = await fetchFn(keysURL.toString());
12
+ if (!response.ok) {
13
+ throw new Error(`Failed to get server public key: ${response.status}`);
14
+ }
15
+ const contentType = response.headers.get("content-type");
16
+ if (contentType !== PROTOCOL.KEYS_MEDIA_TYPE) {
17
+ throw new Error(`Invalid content type: ${contentType}`);
18
+ }
19
+ const keysData = new Uint8Array(await response.arrayBuffer());
20
+ const serverIdentity = await Identity.unmarshalPublicConfig(keysData);
21
+ return serverIdentity.getPublicKey();
22
+ }
23
+ export function normalizeEncryptedBodyRequestArgs(input, init) {
24
+ if (typeof input === "string") {
25
+ return { url: input, init };
26
+ }
27
+ if (input instanceof URL) {
28
+ return { url: input.toString(), init };
29
+ }
30
+ const request = input;
31
+ const cloned = request.clone();
32
+ const derivedInit = {
33
+ method: cloned.method,
34
+ headers: new Headers(cloned.headers),
35
+ body: cloned.body ?? undefined,
36
+ signal: cloned.signal,
37
+ };
38
+ return {
39
+ url: cloned.url,
40
+ init: { ...derivedInit, ...init },
41
+ };
42
+ }
43
+ export async function encryptedBodyRequest(input, hpkePublicKey, init, enclaveURL) {
44
+ const { url: requestUrl, init: requestInit } = normalizeEncryptedBodyRequestArgs(input, init);
45
+ const u = new URL(requestUrl);
46
+ const { origin } = u;
47
+ const keyOrigin = enclaveURL ? new URL(enclaveURL).origin : origin;
48
+ if (!transport) {
49
+ transport = getTransportForOrigin(origin, keyOrigin);
50
+ }
51
+ const transportInstance = await transport;
52
+ if (hpkePublicKey) {
53
+ const transportKeyHash = await transportInstance.getServerPublicKeyHex();
54
+ if (transportKeyHash !== hpkePublicKey) {
55
+ transport = null;
56
+ throw new Error(`HPKE public key mismatch. Expected: ${hpkePublicKey}, Got: ${transportKeyHash}`);
57
+ }
58
+ }
59
+ return transportInstance.request(requestUrl, requestInit);
60
+ }
61
+ export function createEncryptedBodyFetch(baseURL, hpkePublicKey, enclaveURL) {
62
+ return (async (input, init) => {
63
+ const normalized = normalizeEncryptedBodyRequestArgs(input, init);
64
+ const targetUrl = new URL(normalized.url, baseURL);
65
+ return encryptedBodyRequest(targetUrl.toString(), hpkePublicKey, normalized.init, enclaveURL);
66
+ });
67
+ }
68
+ export function resetTransport() {
69
+ transport = null;
70
+ }
71
+ async function getTransportForOrigin(origin, keyOrigin) {
72
+ if (typeof globalThis !== "undefined") {
73
+ const isSecure = globalThis.isSecureContext !== false;
74
+ const hasSubtle = !!(globalThis.crypto && globalThis.crypto.subtle);
75
+ if (!isSecure || !hasSubtle) {
76
+ const reason = !isSecure
77
+ ? "insecure context (use HTTPS or localhost)"
78
+ : "missing WebCrypto SubtleCrypto";
79
+ throw new Error(`EHBP requires a secure browser context: ${reason}`);
80
+ }
81
+ }
82
+ const clientIdentity = await Identity.generate();
83
+ const serverPublicKey = await getHPKEKey(keyOrigin);
84
+ const requestHost = new URL(origin).host;
85
+ return new Transport(clientIdentity, requestHost, serverPublicKey);
86
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Detects if the code is running in a real browser environment.
3
+ * Returns false for Node.js environments, even with WASM loaded.
4
+ */
5
+ export declare function isRealBrowser(): boolean;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Detects if the code is running in a real browser environment.
3
+ * Returns false for Node.js environments, even with WASM loaded.
4
+ */
5
+ export function isRealBrowser() {
6
+ if (typeof process !== "undefined" &&
7
+ process.versions &&
8
+ process.versions.node) {
9
+ return false;
10
+ }
11
+ if (typeof window !== "undefined" && typeof window.document !== "undefined") {
12
+ if (typeof navigator !== "undefined" && navigator.userAgent) {
13
+ return true;
14
+ }
15
+ }
16
+ return false;
17
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Fetch adapter for Tauri v2
3
+ *
4
+ * This module provides a centralized fetch implementation for Tauri v2.
5
+ * For testing purposes, the fetch function can be overridden by setting
6
+ * the global __TINFOIL_TEST_FETCH__ property.
7
+ */
8
+ import { fetch as tauriFetch } from "@tauri-apps/plugin-http";
9
+ declare global {
10
+ var __TINFOIL_TEST_FETCH__: typeof fetch | undefined;
11
+ }
12
+ /**
13
+ * Get the fetch implementation to use.
14
+ * In tests, this can be overridden by setting globalThis.__TINFOIL_TEST_FETCH__
15
+ */
16
+ export declare function getFetch(): typeof tauriFetch;
17
+ /**
18
+ * The fetch function to use throughout the application.
19
+ * Uses Tauri's fetch by default, but can be mocked for testing.
20
+ */
21
+ export declare const fetch: typeof tauriFetch;