From 76703e2e3e5e5783757330f67045d756b48d7df2 Mon Sep 17 00:00:00 2001 From: alpha-nerd-nomyo Date: Wed, 1 Apr 2026 13:38:45 +0200 Subject: [PATCH] fix: base_url port feat: add types for reasoning_content and _metadata fix: key format incompatibility --- .gitignore | 1 + src/api/SecureChatCompletion.ts | 2 +- src/core/SecureCompletionClient.ts | 6 +++--- src/core/crypto/keys.ts | 34 +++++++++++++++++++++++++++--- src/types/api.ts | 18 ++++++++++++++++ 5 files changed, 54 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 8355d64..c0cc4df 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ coverage/ .nyc_output/ build/ *.node +settings.json \ No newline at end of file diff --git a/src/api/SecureChatCompletion.ts b/src/api/SecureChatCompletion.ts index 685100d..9f04c47 100644 --- a/src/api/SecureChatCompletion.ts +++ b/src/api/SecureChatCompletion.ts @@ -13,7 +13,7 @@ export class SecureChatCompletion { constructor(config: ChatCompletionConfig = {}) { const { - baseUrl = 'https://api.nomyo.ai:12434', + baseUrl = 'https://api.nomyo.ai:12435', allowHttp = false, apiKey, secureMemory = true, diff --git a/src/core/SecureCompletionClient.ts b/src/core/SecureCompletionClient.ts index 8e32850..30e83f1 100644 --- a/src/core/SecureCompletionClient.ts +++ b/src/core/SecureCompletionClient.ts @@ -64,9 +64,9 @@ export class SecureCompletionClient { private secureMemoryImpl = createSecureMemory(); private readonly keySize: 2048 | 4096; - constructor(config: ClientConfig = { routerUrl: 'https://api.nomyo.ai:12434' }) { + constructor(config: ClientConfig = { routerUrl: 'https://api.nomyo.ai:12435' }) { const { - routerUrl = 'https://api.nomyo.ai:12434', + routerUrl = 'https://api.nomyo.ai:12435', allowHttp = false, secureMemory = true, keySize = 4096, @@ -174,7 +174,7 @@ export class SecureCompletionClient { throw new SecurityError( 'Server public key must be fetched over HTTPS to prevent MITM attacks. ' + 'For local development, initialize with allowHttp=true: ' + - 'new SecureChatCompletion({ baseUrl: "http://localhost:12434", allowHttp: true })' + 'new SecureChatCompletion({ baseUrl: "http://localhost:12435", allowHttp: true })' ); } else { console.warn('Fetching key over HTTP (local development mode)'); diff --git a/src/core/crypto/keys.ts b/src/core/crypto/keys.ts index ae54de2..376b42d 100644 --- a/src/core/crypto/keys.ts +++ b/src/core/crypto/keys.ts @@ -7,6 +7,7 @@ */ import { RSAOperations } from './rsa'; +import { getCrypto } from './utils'; import { KeyGenOptions, KeyPaths } from '../../types/client'; export class KeyManager { @@ -66,8 +67,25 @@ export class KeyManager { const path = require('path'); // Load private key - const privateKeyPem = await fs.readFile(paths.privateKeyPath, 'utf-8'); - this.privateKey = await this.rsa.importPrivateKey(privateKeyPem, password); + const privateKeyPem = await fs.readFile(paths.privateKeyPath, 'utf-8') as string; + + if (password && privateKeyPem.includes('BEGIN ENCRYPTED PRIVATE KEY')) { + // Standard PKCS#8 encrypted format (produced by Python/OpenSSL) + const { createPrivateKey } = require('crypto') as typeof import('crypto'); + const keyObject = createPrivateKey({ key: privateKeyPem, format: 'pem', passphrase: password }); + const pkcs8Der = keyObject.export({ type: 'pkcs8', format: 'der' }) as Buffer; + const subtle = getCrypto(); + this.privateKey = await subtle.importKey( + 'pkcs8', + pkcs8Der, + { name: 'RSA-OAEP', hash: 'SHA-256' }, + true, + ['decrypt'] + ); + } else { + // Unencrypted PKCS#8 or legacy JS custom-encrypted format + this.privateKey = await this.rsa.importPrivateKey(privateKeyPem, password); + } // Validate private key size (minimum 2048 bits) const privAlgorithm = this.privateKey.algorithm as RsaHashedKeyAlgorithm; @@ -120,7 +138,17 @@ export class KeyManager { await fs.mkdir(directory, { recursive: true }); // Export and save private key - const privateKeyPem = await this.rsa.exportPrivateKey(this.privateKey, password); + let privateKeyPem: string; + if (password) { + // Use standard PKCS#8 encrypted format (compatible with Python/OpenSSL) + const { createPrivateKey } = require('crypto') as typeof import('crypto'); + const subtle = getCrypto(); + const pkcs8Der = await subtle.exportKey('pkcs8', this.privateKey); + const keyObject = createPrivateKey({ key: Buffer.from(pkcs8Der), format: 'der', type: 'pkcs8' }); + privateKeyPem = keyObject.export({ type: 'pkcs8', format: 'pem', cipher: 'aes-256-cbc', passphrase: password }) as string; + } else { + privateKeyPem = await this.rsa.exportPrivateKey(this.privateKey); + } const privateKeyPath = path.join(directory, 'private_key.pem'); await fs.writeFile(privateKeyPath, privateKeyPem, 'utf-8'); diff --git a/src/types/api.ts b/src/types/api.ts index 27442ef..3d10b9e 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -9,6 +9,8 @@ export interface Message { name?: string; tool_calls?: ToolCall[]; tool_call_id?: string; + /** Thinking-model reasoning output (Qwen3, DeepSeek-R1, etc.) */ + reasoning_content?: string; } export interface ToolCall { @@ -70,12 +72,28 @@ export interface Choice { logprobs?: unknown; } +export interface MemoryProtectionInfo { + enabled: boolean; + platform: string; + protection_level: string; + has_memory_locking: boolean; + has_secure_zeroing: boolean; + supports_full_protection: boolean; + page_size?: number; +} + export interface ResponseMetadata { payload_id: string; processed_at: number; is_encrypted: boolean; encryption_algorithm: string; response_status: string; + /** Hardware routing tier used for this request */ + security_tier?: string; + /** Server-side memory protection details */ + memory_protection?: MemoryProtectionInfo; + /** CUDA device ID used for inference (if applicable) */ + cuda_device?: string | number; } export interface ChatCompletionResponse {