nyxora 1.6.4 → 1.6.5

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
@@ -1,13 +1,16 @@
1
1
  # Nyxora Agent 🤖
2
2
  **Production-Grade Secure AI Execution Framework for Web3 Agents.**
3
3
 
4
- [![Version](https://img.shields.io/badge/version-1.6.3-blue.svg)](https://github.com/perasyudha/Nyxora)
4
+ [![Version](https://img.shields.io/badge/version-1.6.5-blue.svg)](https://github.com/perasyudha/Nyxora)
5
+ [![MCP Supported](https://img.shields.io/badge/MCP-Supported-blue.svg)](#)
5
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
7
  [![Security: Production-Grade](https://img.shields.io/badge/Security-Production--Grade-blue.svg)](#️-advanced-security-threat-model)
7
8
  [![Execution: Cryptographic Approval](https://img.shields.io/badge/Execution-Cryptographic--Approval-orange.svg)](#️-advanced-security-threat-model)
8
9
  [![Privacy: Local-Only Keys](https://img.shields.io/badge/Privacy-Local--Only--Keys-success.svg)](#️-advanced-security-threat-model)
9
10
 
10
- Nyxora (v1.6.3) is a **secure, non-custodial runtime infrastructure for autonomous onchain agents** built with a robust Monorepo architecture (Node.js & React). Designed for autonomous workflows with a premium Glassmorphism UI dashboard and strict client-side key isolation.
11
+ Nyxora (v1.6.5) is a **secure, non-custodial runtime infrastructure for autonomous onchain agents** built with a robust Monorepo architecture (Node.js & React). Designed for autonomous workflows with a premium Glassmorphism UI dashboard and strict client-side key isolation.
12
+
13
+ **Nyxora now natively supports the Model Context Protocol (MCP)**. You can transform your external AI agents (like Claude Desktop and Cursor) into secure Web3 actors that execute swaps and fetch balances using Nyxora's secure signer vault. [View the MCP Integration Guide](https://perasyudha.github.io/Nyxora/guide/mcp-integration)
11
14
 
12
15
  It operates under an institutional-grade **Cryptographically Bound Human-in-the-Loop** execution model, ensuring that Remote AIs (LLMs) never have unilateral access to your funds.
13
16
 
@@ -15,7 +18,7 @@ It operates under an institutional-grade **Cryptographically Bound Human-in-the-
15
18
 
16
19
  ## 🔥 Key Features
17
20
 
18
- ### Advanced Security Architecture (v1.6.3)
21
+ ### Advanced Security Architecture
19
22
  * **3-Tier IPC Architecture**: Nyxora is split into isolated processes: **Core** (LLM Runtime), **Policy Engine** (Guardrails on port 3001), and **Signer Vault** (Isolated Key Manager on Unix Sockets).
20
23
  * **Cryptographically Bound Approval**: Policy changes and transactions requested by the AI are drafted as hashes (`sha256`). Approval via the UI requires a challenge nonce, preventing Man-in-the-Middle (MITM) attacks.
21
24
  * **Immutable Policy Guardrails**: Transaction limits (e.g. `max_usd_per_tx`) are strictly enforced by the Policy Engine. The LLM has zero write-access to bypass these rules.
@@ -111,5 +114,12 @@ For complete technical deep-dives into our Cryptographic Architecture, please vi
111
114
 
112
115
  *(Includes guides on Secure Wallet Imports, Architecture Blueprints, Troubleshooting, and Custom Skill Development).*
113
116
 
117
+ ---
118
+
119
+ **❤️ Support the Project**
120
+
121
+ Building and maintaining a highly secure, zero-trust architecture takes significant time and resources. If you love what we are building, you can help us keep Nyxora open, secure, and constantly evolving by sending a coffee our way:
122
+ - **EVM:** `0x18a30d5db50d287dba669c5672cd71246cc4c4c6`
123
+
114
124
  ---
115
125
  **License:** MIT License
package/launcher.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { initSafeLogger } from './packages/core/src/utils/safeLogger';
2
+ initSafeLogger();
3
+
1
4
  import { spawn } from 'child_process';
2
5
  import crypto from 'crypto';
3
6
  import fs from 'fs';
@@ -6,6 +9,12 @@ import path from 'path';
6
9
  const INTERNAL_AUTH_TOKEN = crypto.randomBytes(64).toString('hex');
7
10
  console.log(`[Launcher] Generated Internal Auth Token: ${INTERNAL_AUTH_TOKEN.substring(0, 8)}...`);
8
11
 
12
+ const nyxoraDir = path.join(process.env.HOME || process.env.USERPROFILE || '', '.nyxora');
13
+ if (!fs.existsSync(nyxoraDir)) fs.mkdirSync(nyxoraDir, { recursive: true, mode: 0o700 });
14
+ const tokenPath = path.join(nyxoraDir, 'runtime.token');
15
+ fs.writeFileSync(tokenPath, INTERNAL_AUTH_TOKEN, { mode: 0o600 });
16
+ console.log(`[Launcher] Secured runtime token at ${tokenPath} (0600)`);
17
+
9
18
  const env = {
10
19
  ...process.env,
11
20
  INTERNAL_AUTH_TOKEN,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nyxora",
3
- "version": "1.6.4",
3
+ "version": "1.6.5",
4
4
  "license": "MIT",
5
5
  "workspaces": [
6
6
  "packages/*"
@@ -1,6 +1,6 @@
1
1
  {
2
- "name": "@nyxora/core",
3
- "version": "1.6.4",
2
+ "name": "nyxora-agent-core",
3
+ "version": "1.6.5",
4
4
  "private": true,
5
5
  "main": "src/gateway/server.ts",
6
6
  "dependencies": {
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { initSafeLogger } from '../utils/safeLogger';
4
+ initSafeLogger();
5
+
3
6
  import fs from 'fs';
4
7
  import path from 'path';
5
8
  import os from 'os';
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.initSafeLogger = initSafeLogger;
4
+ exports.safeLog = safeLog;
5
+ exports.safeError = safeError;
6
+ exports.safeWarn = safeWarn;
7
+ const PRIVATE_KEY_REGEX = /0x[a-fA-F0-9]{64}/g;
8
+ const JWT_REGEX = /eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g;
9
+ function sanitize(args) {
10
+ return args.map(arg => {
11
+ if (typeof arg === 'string') {
12
+ return arg
13
+ .replace(PRIVATE_KEY_REGEX, '[REDACTED_PRIVATE_KEY]')
14
+ .replace(JWT_REGEX, '[REDACTED_JWT]');
15
+ }
16
+ if (arg instanceof Error) {
17
+ const sanitizedError = new Error(arg.message
18
+ .replace(PRIVATE_KEY_REGEX, '[REDACTED_PRIVATE_KEY]')
19
+ .replace(JWT_REGEX, '[REDACTED_JWT]'));
20
+ sanitizedError.stack = arg.stack?.replace(PRIVATE_KEY_REGEX, '[REDACTED_PRIVATE_KEY]').replace(JWT_REGEX, '[REDACTED_JWT]');
21
+ return sanitizedError;
22
+ }
23
+ if (typeof arg === 'object' && arg !== null) {
24
+ try {
25
+ const str = JSON.stringify(arg);
26
+ const sanitizedStr = str
27
+ .replace(PRIVATE_KEY_REGEX, '[REDACTED_PRIVATE_KEY]')
28
+ .replace(JWT_REGEX, '[REDACTED_JWT]');
29
+ return JSON.parse(sanitizedStr);
30
+ }
31
+ catch (e) {
32
+ return arg;
33
+ }
34
+ }
35
+ return arg;
36
+ });
37
+ }
38
+ const originalLog = console.log.bind(console);
39
+ const originalError = console.error.bind(console);
40
+ const originalWarn = console.warn.bind(console);
41
+ function initSafeLogger() {
42
+ // @ts-ignore
43
+ if (console.__isSafe)
44
+ return;
45
+ console.log = (...args) => originalLog(...sanitize(args));
46
+ console.error = (...args) => originalError(...sanitize(args));
47
+ console.warn = (...args) => originalWarn(...sanitize(args));
48
+ // @ts-ignore
49
+ console.__isSafe = true;
50
+ }
51
+ function safeLog(...args) {
52
+ originalLog(...sanitize(args));
53
+ }
54
+ function safeError(...args) {
55
+ originalError(...sanitize(args));
56
+ }
57
+ function safeWarn(...args) {
58
+ originalWarn(...sanitize(args));
59
+ }
@@ -0,0 +1,60 @@
1
+ const PRIVATE_KEY_REGEX = /0x[a-fA-F0-9]{64}/g;
2
+ const JWT_REGEX = /eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g;
3
+
4
+ function sanitize(args: any[]): any[] {
5
+ return args.map(arg => {
6
+ if (typeof arg === 'string') {
7
+ return arg
8
+ .replace(PRIVATE_KEY_REGEX, '[REDACTED_PRIVATE_KEY]')
9
+ .replace(JWT_REGEX, '[REDACTED_JWT]');
10
+ }
11
+ if (arg instanceof Error) {
12
+ const sanitizedError = new Error(arg.message
13
+ .replace(PRIVATE_KEY_REGEX, '[REDACTED_PRIVATE_KEY]')
14
+ .replace(JWT_REGEX, '[REDACTED_JWT]')
15
+ );
16
+ sanitizedError.stack = arg.stack?.replace(PRIVATE_KEY_REGEX, '[REDACTED_PRIVATE_KEY]').replace(JWT_REGEX, '[REDACTED_JWT]');
17
+ return sanitizedError;
18
+ }
19
+ if (typeof arg === 'object' && arg !== null) {
20
+ try {
21
+ const str = JSON.stringify(arg);
22
+ const sanitizedStr = str
23
+ .replace(PRIVATE_KEY_REGEX, '[REDACTED_PRIVATE_KEY]')
24
+ .replace(JWT_REGEX, '[REDACTED_JWT]');
25
+ return JSON.parse(sanitizedStr);
26
+ } catch (e) {
27
+ return arg;
28
+ }
29
+ }
30
+ return arg;
31
+ });
32
+ }
33
+
34
+ const originalLog = console.log.bind(console);
35
+ const originalError = console.error.bind(console);
36
+ const originalWarn = console.warn.bind(console);
37
+
38
+ export function initSafeLogger() {
39
+ // @ts-ignore
40
+ if (console.__isSafe) return;
41
+
42
+ console.log = (...args: any[]) => originalLog(...sanitize(args));
43
+ console.error = (...args: any[]) => originalError(...sanitize(args));
44
+ console.warn = (...args: any[]) => originalWarn(...sanitize(args));
45
+
46
+ // @ts-ignore
47
+ console.__isSafe = true;
48
+ }
49
+
50
+ export function safeLog(...args: any[]) {
51
+ originalLog(...sanitize(args));
52
+ }
53
+
54
+ export function safeError(...args: any[]) {
55
+ originalError(...sanitize(args));
56
+ }
57
+
58
+ export function safeWarn(...args: any[]) {
59
+ originalWarn(...sanitize(args));
60
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dashboard",
3
3
  "private": true,
4
- "version": "1.6.4",
4
+ "version": "1.6.5",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
@@ -0,0 +1,111 @@
1
+ "use strict";
2
+ // MCP Server uses Stdio, so we avoid global hooks that might write to stdout.
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
8
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
9
+ const zod_1 = require("zod");
10
+ const http_1 = __importDefault(require("http"));
11
+ const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
12
+ const fs_1 = __importDefault(require("fs"));
13
+ const path_1 = __importDefault(require("path"));
14
+ let JWT_SECRET = process.env.INTERNAL_AUTH_TOKEN;
15
+ if (!JWT_SECRET) {
16
+ try {
17
+ const tokenPath = path_1.default.join(process.env.HOME || process.env.USERPROFILE || '', '.nyxora', 'runtime.token');
18
+ JWT_SECRET = fs_1.default.readFileSync(tokenPath, 'utf8').trim();
19
+ }
20
+ catch (e) {
21
+ console.error("Missing INTERNAL_AUTH_TOKEN in mcp-server process and could not read ~/.nyxora/runtime.token");
22
+ process.exit(1);
23
+ }
24
+ }
25
+ const server = new mcp_js_1.McpServer({
26
+ name: "Nyxora MCP Server",
27
+ version: "1.6.5"
28
+ });
29
+ // Helper to make internal requests to Policy Engine
30
+ function callPolicyEngine(path, method, payload) {
31
+ return new Promise((resolve, reject) => {
32
+ const dataString = payload ? JSON.stringify(payload) : '';
33
+ const options = {
34
+ hostname: '127.0.0.1',
35
+ port: 3001,
36
+ path: path,
37
+ method: method,
38
+ headers: {
39
+ 'Content-Type': 'application/json',
40
+ 'Authorization': `Bearer ${jsonwebtoken_1.default.sign({ service: 'mcp-server' }, JWT_SECRET, { expiresIn: '1m' })}`,
41
+ 'Content-Length': Buffer.byteLength(dataString)
42
+ }
43
+ };
44
+ const req = http_1.default.request(options, (res) => {
45
+ let data = '';
46
+ res.on('data', chunk => data += chunk);
47
+ res.on('end', () => {
48
+ try {
49
+ const parsed = JSON.parse(data);
50
+ if (res.statusCode && res.statusCode >= 400) {
51
+ reject(new Error(`Policy Engine Rejected (${res.statusCode}): ${parsed.error || parsed.message || data}`));
52
+ }
53
+ else {
54
+ resolve(parsed);
55
+ }
56
+ }
57
+ catch (e) {
58
+ reject(new Error(`Failed to parse Policy Engine response: ${data}`));
59
+ }
60
+ });
61
+ });
62
+ req.on('error', (e) => reject(new Error(`Policy Engine Connection Error: ${e.message}`)));
63
+ if (dataString) {
64
+ req.write(dataString);
65
+ }
66
+ req.end();
67
+ });
68
+ }
69
+ // Tool: get_wallet_address
70
+ server.tool("get_wallet_address", "Fetch the secure Ethereum wallet address managed by the Nyxora Signer Vault. Use this to know the agent's identity.", {}, async () => {
71
+ try {
72
+ const response = await callPolicyEngine('/address', 'GET');
73
+ return {
74
+ content: [{ type: "text", text: `Wallet Address: ${response.address}` }]
75
+ };
76
+ }
77
+ catch (e) {
78
+ return {
79
+ isError: true,
80
+ content: [{ type: "text", text: e.message }]
81
+ };
82
+ }
83
+ });
84
+ // Tool: request_transaction
85
+ server.tool("request_transaction", "Request an EVM transaction (transfer, swap, bridge). This will be evaluated by the Policy Engine. If approved, it returns a pending Transaction ID.", {
86
+ type: zod_1.z.enum(['transfer', 'swap', 'bridge', 'mint', 'custom']).describe("The type of transaction"),
87
+ chainName: zod_1.z.string().describe("The target blockchain network (e.g. 'ethereum', 'base', 'arbitrum')"),
88
+ details: zod_1.z.any().describe("Detailed payload. For 'transfer': { to: string, amountWei: string }. For 'swap': { tokenIn: string, tokenOut: string, amountInWei: string }.")
89
+ }, async (args) => {
90
+ try {
91
+ const response = await callPolicyEngine('/request-tx', 'POST', args);
92
+ return {
93
+ content: [{ type: "text", text: `Transaction requested successfully. Transaction ID: ${response.id}. Status: ${response.status}` }]
94
+ };
95
+ }
96
+ catch (e) {
97
+ return {
98
+ isError: true,
99
+ content: [{ type: "text", text: `Transaction Request Failed! ${e.message}` }]
100
+ };
101
+ }
102
+ });
103
+ async function main() {
104
+ const transport = new stdio_js_1.StdioServerTransport();
105
+ await server.connect(transport);
106
+ console.error("Nyxora MCP Server running on stdio"); // stdio logs use stderr so stdout is purely for MCP JSON-RPC
107
+ }
108
+ main().catch((error) => {
109
+ console.error("Fatal error in MCP Server:", error);
110
+ process.exit(1);
111
+ });
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "nyxora-mcp-server",
3
+ "version": "1.6.5",
4
+ "description": "Nyxora MCP Server for external AI clients",
5
+ "main": "dist/server.js",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "start": "node dist/server.js",
9
+ "dev": "ts-node src/server.ts"
10
+ },
11
+ "dependencies": {
12
+ "@modelcontextprotocol/sdk": "^1.5.0",
13
+ "zod": "^3.24.2"
14
+ },
15
+ "devDependencies": {
16
+ "typescript": "^5.0.0",
17
+ "@types/node": "^20.0.0"
18
+ }
19
+ }
@@ -0,0 +1,124 @@
1
+ // MCP Server uses Stdio, so we avoid global hooks that might write to stdout.
2
+
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod";
6
+ import http from 'http';
7
+ import jwt from 'jsonwebtoken';
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+
11
+ let JWT_SECRET = process.env.INTERNAL_AUTH_TOKEN;
12
+
13
+ if (!JWT_SECRET) {
14
+ try {
15
+ const tokenPath = path.join(process.env.HOME || process.env.USERPROFILE || '', '.nyxora', 'runtime.token');
16
+ JWT_SECRET = fs.readFileSync(tokenPath, 'utf8').trim();
17
+ } catch (e) {
18
+ console.error("Missing INTERNAL_AUTH_TOKEN in mcp-server process and could not read ~/.nyxora/runtime.token");
19
+ process.exit(1);
20
+ }
21
+ }
22
+
23
+ const server = new McpServer({
24
+ name: "Nyxora MCP Server",
25
+ version: "1.6.5"
26
+ });
27
+
28
+ // Helper to make internal requests to Policy Engine
29
+ function callPolicyEngine(path: string, method: string, payload?: any): Promise<any> {
30
+ return new Promise((resolve, reject) => {
31
+ const dataString = payload ? JSON.stringify(payload) : '';
32
+
33
+ const options = {
34
+ hostname: '127.0.0.1',
35
+ port: 3001,
36
+ path: path,
37
+ method: method,
38
+ headers: {
39
+ 'Content-Type': 'application/json',
40
+ 'Authorization': `Bearer ${jwt.sign({ service: 'mcp-server' }, JWT_SECRET as string, { expiresIn: '1m' })}`,
41
+ 'Content-Length': Buffer.byteLength(dataString)
42
+ }
43
+ };
44
+
45
+ const req = http.request(options, (res) => {
46
+ let data = '';
47
+ res.on('data', chunk => data += chunk);
48
+ res.on('end', () => {
49
+ try {
50
+ const parsed = JSON.parse(data);
51
+ if (res.statusCode && res.statusCode >= 400) {
52
+ reject(new Error(`Policy Engine Rejected (${res.statusCode}): ${parsed.error || parsed.message || data}`));
53
+ } else {
54
+ resolve(parsed);
55
+ }
56
+ } catch (e) {
57
+ reject(new Error(`Failed to parse Policy Engine response: ${data}`));
58
+ }
59
+ });
60
+ });
61
+
62
+ req.on('error', (e) => reject(new Error(`Policy Engine Connection Error: ${e.message}`)));
63
+
64
+ if (dataString) {
65
+ req.write(dataString);
66
+ }
67
+ req.end();
68
+ });
69
+ }
70
+
71
+ // Tool: get_wallet_address
72
+ server.tool(
73
+ "get_wallet_address",
74
+ "Fetch the secure Ethereum wallet address managed by the Nyxora Signer Vault. Use this to know the agent's identity.",
75
+ {},
76
+ async () => {
77
+ try {
78
+ const response = await callPolicyEngine('/address', 'GET');
79
+ return {
80
+ content: [{ type: "text", text: `Wallet Address: ${response.address}` }]
81
+ };
82
+ } catch (e: any) {
83
+ return {
84
+ isError: true,
85
+ content: [{ type: "text", text: e.message }]
86
+ };
87
+ }
88
+ }
89
+ );
90
+
91
+ // Tool: request_transaction
92
+ server.tool(
93
+ "request_transaction",
94
+ "Request an EVM transaction (transfer, swap, bridge). This will be evaluated by the Policy Engine. If approved, it returns a pending Transaction ID.",
95
+ {
96
+ type: z.enum(['transfer', 'swap', 'bridge', 'mint', 'custom']).describe("The type of transaction"),
97
+ chainName: z.string().describe("The target blockchain network (e.g. 'ethereum', 'base', 'arbitrum')"),
98
+ details: z.any().describe("Detailed payload. For 'transfer': { to: string, amountWei: string }. For 'swap': { tokenIn: string, tokenOut: string, amountInWei: string }.")
99
+ },
100
+ async (args) => {
101
+ try {
102
+ const response = await callPolicyEngine('/request-tx', 'POST', args);
103
+ return {
104
+ content: [{ type: "text", text: `Transaction requested successfully. Transaction ID: ${response.id}. Status: ${response.status}` }]
105
+ };
106
+ } catch (e: any) {
107
+ return {
108
+ isError: true,
109
+ content: [{ type: "text", text: `Transaction Request Failed! ${e.message}` }]
110
+ };
111
+ }
112
+ }
113
+ );
114
+
115
+ async function main() {
116
+ const transport = new StdioServerTransport();
117
+ await server.connect(transport);
118
+ console.error("Nyxora MCP Server running on stdio"); // stdio logs use stderr so stdout is purely for MCP JSON-RPC
119
+ }
120
+
121
+ main().catch((error) => {
122
+ console.error("Fatal error in MCP Server:", error);
123
+ process.exit(1);
124
+ });
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "outDir": "./dist",
11
+ "rootDir": "./src"
12
+ },
13
+ "include": ["src/**/*"],
14
+ "exclude": ["node_modules", "dist"]
15
+ }
@@ -0,0 +1 @@
1
+ {"root":["./src/server.ts"],"version":"6.0.3"}
@@ -1,6 +1,6 @@
1
1
  {
2
- "name": "@nyxora/policy",
3
- "version": "1.6.4",
2
+ "name": "nyxora-agent-policy",
3
+ "version": "1.6.5",
4
4
  "private": true,
5
5
  "main": "src/server.ts",
6
6
  "dependencies": {
@@ -1,3 +1,6 @@
1
+ import { initSafeLogger } from '../../core/src/utils/safeLogger';
2
+ initSafeLogger();
3
+
1
4
  import express from 'express';
2
5
  import jwt from 'jsonwebtoken';
3
6
  import fs from 'fs';
@@ -1,6 +1,6 @@
1
1
  {
2
- "name": "@nyxora/signer",
3
- "version": "1.6.4",
2
+ "name": "nyxora-agent-signer",
3
+ "version": "1.6.5",
4
4
  "private": true,
5
5
  "main": "src/server.ts",
6
6
  "dependencies": {
@@ -1,3 +1,6 @@
1
+ import { initSafeLogger } from '../../core/src/utils/safeLogger';
2
+ initSafeLogger();
3
+
1
4
  import express from 'express';
2
5
  import fs from 'fs';
3
6
  import jwt from 'jsonwebtoken';
@@ -43,6 +46,18 @@ async function loadPrivateKey() {
43
46
  // Fallback to vault.key
44
47
  const vaultPath = path.join(os.homedir(), '.nyxora', 'vault.key');
45
48
  if (fs.existsSync(vaultPath)) {
49
+ const stats = fs.statSync(vaultPath);
50
+ const mode = stats.mode & 0o777;
51
+ if (os.platform() !== 'win32' && mode !== 0o600) {
52
+ console.error(`\n======================================================`);
53
+ console.error(`FATAL: Insecure permissions detected on vault.key`);
54
+ console.error(`File permissions must be strictly 0600 (-rw-------)`);
55
+ console.error(`Current permissions: 0${mode.toString(8)}`);
56
+ console.error(`Refusing to start. Please run: chmod 600 ${vaultPath}`);
57
+ console.error(`======================================================\n`);
58
+ process.exit(1);
59
+ }
60
+
46
61
  const content = fs.readFileSync(vaultPath, 'utf8');
47
62
  const match = content.match(/PRIVATE_KEY=(.+)/);
48
63
  if (match && match[1]) {