n8n-nodes-nilai 0.1.1 → 0.1.3

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.
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.NilAi = void 0;
4
4
  exports.verifyNilaiSignature = verifyNilaiSignature;
5
+ const n8n_workflow_1 = require("n8n-workflow");
5
6
  const crypto_1 = require("crypto");
6
7
  const attestation_1 = require("./attestation");
7
8
  const SPKI_SECP256K1_COMPRESSED_PREFIX = Buffer.from('3036301006072a8648ce3d020106052b8104000a032200', 'hex');
@@ -37,6 +38,7 @@ class NilAi {
37
38
  version: 1,
38
39
  subtitle: '={{$parameter["model"]}}',
39
40
  description: 'Private, verifiable AI inference inside a Trusted Execution Environment (TEE), powered by Nillion nilAI.',
41
+ usableAsTool: true,
40
42
  defaults: {
41
43
  name: 'nilAI',
42
44
  },
@@ -50,14 +52,14 @@ class NilAi {
50
52
  ],
51
53
  properties: [
52
54
  {
53
- displayName: 'Model',
55
+ displayName: 'Model Name or ID',
54
56
  name: 'model',
55
57
  type: 'options',
56
58
  typeOptions: {
57
59
  loadOptionsMethod: 'getModels',
58
60
  },
59
- default: 'google/gemma-4-26B-A4B-it',
60
- description: 'The model to run inside the nilAI TEE. Loaded live from your nilAI endpoint.',
61
+ default: '',
62
+ description: 'The model to run inside the nilAI TEE. Loaded live from your nilAI endpoint. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>.',
61
63
  },
62
64
  {
63
65
  displayName: 'Instructions',
@@ -67,7 +69,7 @@ class NilAi {
67
69
  rows: 2,
68
70
  },
69
71
  default: '',
70
- description: "What you want the model to do, e.g. 'Summarise this in 3 sentences and 3-5 key bullet points'.",
72
+ description: 'What you want the model to do, e.g. \'Summarise this in 3 sentences and 3-5 key bullet points\'',
71
73
  hint: 'Plain text — the task only. Keep your data out of here (put that in Input). Leave empty to send just the Input as-is.',
72
74
  },
73
75
  {
@@ -87,7 +89,7 @@ class NilAi {
87
89
  name: 'simplify',
88
90
  type: 'boolean',
89
91
  default: true,
90
- description: "Whether to return a clean result instead of nilAI's full raw response object.",
92
+ description: 'Whether to return a clean result instead of nilAI\'s full raw response object',
91
93
  hint: 'On: tidy output — just the answer, the verification results and token usage. Off: the complete raw nilAI response (every field), handy for debugging.',
92
94
  },
93
95
  {
@@ -158,7 +160,7 @@ class NilAi {
158
160
  let teeVerified = null;
159
161
  if (doVerify) {
160
162
  if (publicKeyB64 === undefined) {
161
- publicKeyB64 = (await this.helpers.httpRequest({
163
+ publicKeyB64 = (await this.helpers.httpRequestWithAuthentication.call(this, 'nilAiApi', {
162
164
  method: 'GET',
163
165
  url: `${baseUrl}/v1/public_key`,
164
166
  json: true,
@@ -200,7 +202,7 @@ class NilAi {
200
202
  });
201
203
  continue;
202
204
  }
203
- throw error;
205
+ throw new n8n_workflow_1.NodeOperationError(this.getNode(), error);
204
206
  }
205
207
  }
206
208
  return [returnData];
@@ -5,44 +5,12 @@ exports.verifyTcbExtensions = verifyTcbExtensions;
5
5
  exports.checkReportDataBinding = checkReportDataBinding;
6
6
  exports.verifyEnclaveAttestation = verifyEnclaveAttestation;
7
7
  const crypto_1 = require("crypto");
8
- const tls_1 = require("tls");
9
- const fs_1 = require("fs");
10
- const path_1 = require("path");
11
- const os_1 = require("os");
12
8
  const KDS_DOMAIN = 'kdsintf.amd.com';
13
9
  const KNOWN_MEASUREMENTS = {
14
10
  '0.3.0': '981bcaa62bcd63cb03c7b93a67d1a8c17ff63e45e6d16323a8784bacbfcb254313741bcf32d0bccc795d6ea0e6ac1481',
15
11
  };
16
12
  const certChainCache = {};
17
13
  const vcekCache = {};
18
- function certCacheDir() {
19
- for (const base of [(0, path_1.join)((0, os_1.homedir)(), '.n8n'), (0, os_1.tmpdir)()]) {
20
- try {
21
- const dir = (0, path_1.join)(base, '.nilai-cert-cache');
22
- (0, fs_1.mkdirSync)(dir, { recursive: true });
23
- return dir;
24
- }
25
- catch {
26
- }
27
- }
28
- return (0, os_1.tmpdir)();
29
- }
30
- function readCertCache(name) {
31
- try {
32
- const p = (0, path_1.join)(certCacheDir(), name);
33
- return (0, fs_1.existsSync)(p) ? (0, fs_1.readFileSync)(p) : null;
34
- }
35
- catch {
36
- return null;
37
- }
38
- }
39
- function writeCertCache(name, data) {
40
- try {
41
- (0, fs_1.writeFileSync)((0, path_1.join)(certCacheDir(), name), data);
42
- }
43
- catch {
44
- }
45
- }
46
14
  function detectProcessor(family, model) {
47
15
  if (family === 0x19) {
48
16
  if (model <= 0x0f)
@@ -125,40 +93,6 @@ function parseCertChain(pem) {
125
93
  return null;
126
94
  }
127
95
  }
128
- async function getServerCertDer(host) {
129
- return new Promise((resolve) => {
130
- let settled = false;
131
- let socket;
132
- const finish = (v) => {
133
- if (settled)
134
- return;
135
- settled = true;
136
- clearTimeout(timer);
137
- try {
138
- socket?.destroy();
139
- }
140
- catch {
141
- }
142
- resolve(v);
143
- };
144
- const timer = setTimeout(() => finish(null), 7000);
145
- try {
146
- socket = (0, tls_1.connect)({ host, port: 443, servername: host, rejectUnauthorized: true }, () => {
147
- try {
148
- const peer = socket.getPeerCertificate();
149
- finish(peer && peer.raw ? peer.raw : null);
150
- }
151
- catch {
152
- finish(null);
153
- }
154
- });
155
- socket.on('error', () => finish(null));
156
- }
157
- catch {
158
- finish(null);
159
- }
160
- });
161
- }
162
96
  function checkReportDataBinding(reportDataHex, certDer) {
163
97
  try {
164
98
  const spki = new crypto_1.X509Certificate(certDer).publicKey.export({ type: 'spki', format: 'der' });
@@ -177,7 +111,8 @@ async function verifyEnclaveAttestation(ctx, baseUrl) {
177
111
  const reportResp = (await ctx.helpers.httpRequest({
178
112
  method: 'GET',
179
113
  url: `${baseUrl}/nilcc/api/v2/report`,
180
- json: true, timeout: 15000,
114
+ json: true,
115
+ timeout: 15000,
181
116
  }));
182
117
  const report = reportResp.report;
183
118
  const raw = Buffer.from(reportResp.raw_report, 'hex');
@@ -203,50 +138,28 @@ async function verifyEnclaveAttestation(ctx, baseUrl) {
203
138
  }
204
139
  const pad2 = (n) => String(n).padStart(2, '0');
205
140
  if (!certChainCache[processor]) {
206
- const chainCacheName = `certchain_${processor}.pem`;
207
- const cachedChain = readCertCache(chainCacheName);
208
- let chain = cachedChain ? parseCertChain(cachedChain.toString('utf8')) : null;
209
- if (!chain) {
210
- const chainPem = (await ctx.helpers.httpRequest({
211
- method: 'GET',
212
- url: `https://${KDS_DOMAIN}/vcek/v1/${processor}/cert_chain`,
213
- encoding: 'text',
214
- timeout: 15000,
215
- }));
216
- chain = parseCertChain(chainPem);
217
- if (!chain)
218
- throw new Error('AMD KDS returned an unparseable certificate chain');
219
- writeCertCache(chainCacheName, Buffer.from(chainPem, 'utf8'));
220
- }
141
+ const chainPem = (await ctx.helpers.httpRequest({
142
+ method: 'GET',
143
+ url: `https://${KDS_DOMAIN}/vcek/v1/${processor}/cert_chain`,
144
+ encoding: 'text',
145
+ timeout: 15000,
146
+ }));
147
+ const chain = parseCertChain(chainPem);
148
+ if (!chain)
149
+ throw new Error('AMD KDS returned an unparseable certificate chain');
221
150
  certChainCache[processor] = chain;
222
151
  }
223
152
  const { ark, ask } = certChainCache[processor];
224
153
  const vcekUrl = `https://${KDS_DOMAIN}/vcek/v1/${processor}/${chipIdHex}` +
225
154
  `?blSPL=${pad2(tcb.bootloader)}&teeSPL=${pad2(tcb.tee)}&snpSPL=${pad2(tcb.snp)}&ucodeSPL=${pad2(tcb.microcode)}`;
226
155
  if (!vcekCache[vcekUrl]) {
227
- const vcekCacheName = `vcek_${chipIdHex}_${tcb.bootloader}_${tcb.tee}_${tcb.snp}_${tcb.microcode}.der`;
228
- const cachedDer = readCertCache(vcekCacheName);
229
- let vcekCert = null;
230
- if (cachedDer) {
231
- try {
232
- vcekCert = new crypto_1.X509Certificate(cachedDer);
233
- }
234
- catch {
235
- vcekCert = null;
236
- }
237
- }
238
- if (!vcekCert) {
239
- const fetched = (await ctx.helpers.httpRequest({
240
- method: 'GET',
241
- url: vcekUrl,
242
- encoding: 'arraybuffer',
243
- timeout: 15000,
244
- }));
245
- const der = Buffer.from(fetched);
246
- vcekCert = new crypto_1.X509Certificate(der);
247
- writeCertCache(vcekCacheName, der);
248
- }
249
- vcekCache[vcekUrl] = vcekCert;
156
+ const fetched = (await ctx.helpers.httpRequest({
157
+ method: 'GET',
158
+ url: vcekUrl,
159
+ encoding: 'arraybuffer',
160
+ timeout: 15000,
161
+ }));
162
+ vcekCache[vcekUrl] = new crypto_1.X509Certificate(Buffer.from(fetched));
250
163
  }
251
164
  const vcek = vcekCache[vcekUrl];
252
165
  const arkSelfSigned = ark.verify(ark.publicKey);
@@ -269,23 +182,12 @@ async function verifyEnclaveAttestation(ctx, baseUrl) {
269
182
  const measurementMatches = known ? measurement === known : null;
270
183
  const reportData = raw.subarray(0x50, 0x50 + 64).toString('hex');
271
184
  const tcbOk = verifyTcbExtensions(vcek.raw, tcb, chipIdHex);
272
- const host = (() => {
273
- try {
274
- return new URL(baseUrl).hostname;
275
- }
276
- catch {
277
- return '';
278
- }
279
- })();
280
- const serverCertDer = host ? await getServerCertDer(host) : null;
281
- const tlsBound = serverCertDer ? checkReportDataBinding(reportData, serverCertDer) : false;
282
185
  const checks = {
283
186
  ark_self_signed: arkSelfSigned,
284
187
  ask_signed_by_ark: askByArk,
285
188
  vcek_signed_by_ask: vcekByAsk,
286
189
  report_signature_valid: sigValid,
287
190
  vcek_tcb_matches_report: tcbOk,
288
- tls_session_bound: tlsBound,
289
191
  debug_disabled: !debugAllowed,
290
192
  };
291
193
  const attestation_verified = Object.values(checks).every(Boolean) && measurementMatches !== false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-nilai",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Use Nillion nilAI — private LLM inference in a TEE, OpenAI-compatible — directly in n8n, with built-in cryptographic verification of every response.",
5
5
  "keywords": [
6
6
  "n8n-community-node-package",