fraim 2.0.326 → 2.0.328

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.
@@ -62,6 +62,7 @@ const crypto_1 = require("crypto");
62
62
  const axios_1 = __importDefault(require("axios"));
63
63
  const provider_utils_1 = require("../core/utils/provider-utils");
64
64
  const object_utils_1 = require("../core/utils/object-utils");
65
+ const redact_secrets_1 = require("../core/utils/redact-secrets");
65
66
  const pack_home_1 = require("../cli/utils/pack-home");
66
67
  const local_registry_resolver_1 = require("../core/utils/local-registry-resolver");
67
68
  const ai_mentor_1 = require("../core/ai-mentor");
@@ -553,7 +554,7 @@ class FraimLocalMCPServer {
553
554
  }
554
555
  this.log('🚀 FRAIM Local MCP Server starting... [DEBUG-PROXY-V3]');
555
556
  this.log(`📡 Remote server: ${this.remoteUrl}`);
556
- this.log(`🔑 API key: ${this.apiKey.substring(0, 10)}...`);
557
+ this.log(`🔑 API key: ${(0, redact_secrets_1.redactApiKey)(this.apiKey)}`);
557
558
  this.log(`Local MCP version: ${this.localVersion}`);
558
559
  this.log(`🔍 DEBUG BUILD: Machine detection v2 active`);
559
560
  // Initialize usage collector
@@ -626,23 +627,35 @@ class FraimLocalMCPServer {
626
627
  this.log(`Could not persist userEmail: ${e instanceof Error ? e.message : String(e)}`);
627
628
  }
628
629
  }
630
+ /**
631
+ * Non-reversible reference for this proxy's key (issue #1701). Every line the
632
+ * proxy writes carries it, and the proxy mirrors those lines into a temp file
633
+ * that survives the process, so the key itself must never appear here. The
634
+ * reference matches the one the server logs for the same key, so a user's local
635
+ * log still correlates with the server-side log.
636
+ */
637
+ keyRef() {
638
+ return this.apiKey ? (0, redact_secrets_1.redactApiKey)(this.apiKey) : 'MISSING_API_KEY';
639
+ }
629
640
  log(message) {
630
641
  // Log to stderr (stdout is reserved for MCP protocol)
631
- const key = this.apiKey || 'MISSING_API_KEY';
632
- console.error(`[FRAIM key:${key}] ${message}`);
642
+ const keyRef = this.keyRef();
643
+ // Messages are assembled from remote responses and caught errors, either of
644
+ // which can carry a credential this proxy did not author.
645
+ const safeMessage = (0, redact_secrets_1.redactSecretsInText)(message);
646
+ console.error(`[FRAIM key:${keyRef}] ${safeMessage}`);
633
647
  // Also log to file for debugging
634
648
  try {
635
649
  const fs = require('fs');
636
650
  const logFile = require('path').join(require('os').tmpdir(), 'fraim-mcp-proxy.log');
637
- fs.appendFileSync(logFile, `${new Date().toISOString()} [${key}] ${message}\n`);
651
+ fs.appendFileSync(logFile, `${new Date().toISOString()} [${keyRef}] ${safeMessage}\n`);
638
652
  }
639
653
  catch (e) {
640
654
  // Ignore file logging errors
641
655
  }
642
656
  }
643
657
  logError(message) {
644
- const key = this.apiKey || 'MISSING_API_KEY';
645
- console.error(`[FRAIM ERROR key:${key}] ${message}`);
658
+ console.error(`[FRAIM ERROR key:${this.keyRef()}] ${(0, redact_secrets_1.redactSecretsInText)(message)}`);
646
659
  }
647
660
  detectLocalVersion() {
648
661
  const candidates = [
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AuthMiddleware = void 0;
4
+ const redact_secrets_1 = require("../core/utils/redact-secrets");
4
5
  const fs_1 = require("fs");
5
6
  const path_1 = require("path");
6
7
  const cookie_service_1 = require("../services/cookie-service");
@@ -219,7 +220,7 @@ class AuthMiddleware {
219
220
  try {
220
221
  const apiKeyData = await this.dbService.verifyApiKey(apiKey);
221
222
  if (!apiKeyData) {
222
- console.error(`❌ FRAIM AUTH: Invalid API key: ${apiKey}`);
223
+ console.error(`❌ FRAIM AUTH: Invalid API key: ${(0, redact_secrets_1.redactApiKey)(apiKey)}`);
223
224
  setMcpAuthenticateChallenge();
224
225
  return res.status(401).json({
225
226
  jsonrpc: '2.0',
@@ -230,7 +231,7 @@ class AuthMiddleware {
230
231
  // Check if key is suspended
231
232
  if (apiKeyData.status === 'suspended') {
232
233
  const reason = apiKeyData.suspensionReason || 'payment failure';
233
- console.error(`❌ FRAIM AUTH: Suspended API key: ${apiKey} (reason: ${reason})`);
234
+ console.error(`❌ FRAIM AUTH: Suspended API key: ${(0, redact_secrets_1.redactApiKey)(apiKey)} (reason: ${reason})`);
234
235
  return res.status(402).json({
235
236
  jsonrpc: '2.0',
236
237
  error: {
@@ -255,7 +256,7 @@ class AuthMiddleware {
255
256
  const message = tier === 'trial'
256
257
  ? 'Your 14-day free trial has expired. Please upgrade to continue using FRAIM.'
257
258
  : 'Your subscription has expired. Please renew to continue using FRAIM.';
258
- console.error(`❌ FRAIM AUTH: Expired API key: ${apiKey} (tier: ${tier})`);
259
+ console.error(`❌ FRAIM AUTH: Expired API key: ${(0, redact_secrets_1.redactApiKey)(apiKey)} (tier: ${tier})`);
259
260
  return res.status(402).json({
260
261
  jsonrpc: '2.0',
261
262
  error: {
@@ -35,38 +35,21 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.requestLogger = requestLogger;
37
37
  const crypto_1 = require("crypto");
38
+ const redact_secrets_1 = require("../core/utils/redact-secrets");
38
39
  const fs = __importStar(require("fs"));
39
40
  const path = __importStar(require("path"));
40
41
  const LOG_FILE = path.join(process.cwd(), 'server-direct.log');
41
- /**
42
- * Extracts API key from request for logging purposes
43
- */
44
- function getApiKeyForLogging(req) {
45
- const fromContext = req.apiKeyData?.key;
46
- if (typeof fromContext === 'string' && fromContext.trim().length > 0) {
47
- return fromContext;
48
- }
49
- const fromHeader = Array.isArray(req.headers['x-api-key'])
50
- ? req.headers['x-api-key'][0]
51
- : req.headers['x-api-key'];
52
- if (typeof fromHeader === 'string' && fromHeader.trim().length > 0) {
53
- return fromHeader;
54
- }
55
- const fromQuery = req.query?.['api-key'];
56
- if (typeof fromQuery === 'string' && fromQuery.trim().length > 0) {
57
- return fromQuery;
58
- }
59
- return 'ANONYMOUS';
60
- }
61
42
  function getBodyForLogging(req) {
62
43
  if (req.path === '/api/ai-hub/brand' && req.body && typeof req.body === 'object') {
63
44
  const body = { ...req.body };
64
45
  if (typeof body.logo === 'string' && body.logo.length > 0) {
65
46
  body.logo = `[redacted logo payload, ${body.logo.length} chars]`;
66
47
  }
67
- return JSON.stringify(body);
48
+ return (0, redact_secrets_1.redactSecretsInText)(JSON.stringify(body));
68
49
  }
69
- return JSON.stringify(req.body);
50
+ // Bodies are logged verbatim; a caller that puts a credential in one must not
51
+ // have it land in the log in the clear (issue #1701).
52
+ return (0, redact_secrets_1.redactSecretsInText)(JSON.stringify(req.body));
70
53
  }
71
54
  /**
72
55
  * Middleware to log all requests
@@ -118,8 +101,8 @@ function requestLogger(req, res, next) {
118
101
  res.on('finish', () => {
119
102
  const duration = Date.now() - start;
120
103
  const status = res.statusCode;
121
- const keyStr = getApiKeyForLogging(req);
122
- const logMsg = `[req:${requestId}] [local:${localMcpVersion}] ${keyStr} END ${method} ${path} ${status} ${duration}ms`;
104
+ const keyRef = (0, redact_secrets_1.apiKeyRefFromRequest)(req);
105
+ const logMsg = `[req:${requestId}] [local:${localMcpVersion}] ${keyRef} END ${method} ${path} ${status} ${duration}ms`;
123
106
  logEntry(logMsg);
124
107
  });
125
108
  next();
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TelemetryMiddleware = void 0;
4
+ const redact_secrets_1 = require("../core/utils/redact-secrets");
4
5
  class TelemetryMiddleware {
5
6
  constructor(sessionManager) {
6
7
  this.sessionManager = sessionManager;
@@ -125,7 +126,7 @@ After successful fraim_connect, all other FRAIM tools will work. This is require
125
126
  id: req.body?.id || null
126
127
  });
127
128
  }
128
- console.log(`Telemetry active for ${apiKey}`);
129
+ console.log(`Telemetry active for ${(0, redact_secrets_1.redactApiKey)(apiKey)}`);
129
130
  return next();
130
131
  }
131
132
  catch (error) {
@@ -1,26 +1,9 @@
1
1
  "use strict";
2
2
  /**
3
- * InstallerService - Generates installer scripts and dashboard HTML for the Get Started flow.
4
- * Scripts are loaded from templates in scripts/installer/ and populated with token/baseUrl.
3
+ * InstallerService - Generates dashboard HTML for the Get Started flow.
5
4
  */
6
5
  Object.defineProperty(exports, "__esModule", { value: true });
7
6
  exports.InstallerService = void 0;
8
- const fs_1 = require("fs");
9
- const path_1 = require("path");
10
- function getScriptsDir() {
11
- // When running from dist: dist/services/ -> dist/../scripts = scripts
12
- // When running from src with tsx: src/services/ -> src/../scripts = scripts
13
- const candidates = [
14
- (0, path_1.join)(process.cwd(), 'scripts', 'installer'),
15
- (0, path_1.join)(__dirname, '..', '..', 'scripts', 'installer'),
16
- (0, path_1.join)(__dirname, '..', '..', '..', 'scripts', 'installer')
17
- ];
18
- for (const dir of candidates) {
19
- if ((0, fs_1.existsSync)(dir))
20
- return dir;
21
- }
22
- return null;
23
- }
24
7
  /**
25
8
  * Default marketing site base URL. All asset references and nav links in the
26
9
  * layout shell resolve against this host. Overridable via FRAIM_MARKETING_URL.
@@ -55,15 +38,6 @@ function buildLayoutShell(marketingBaseUrl) {
55
38
  <script src="${m}/js/script.js"></script></body></html>`;
56
39
  }
57
40
  class InstallerService {
58
- constructor() {
59
- this.scriptsDir = getScriptsDir();
60
- }
61
- requireScriptsDir() {
62
- if (!this.scriptsDir) {
63
- throw new Error('Installer scripts directory not found. Installer download is unavailable.');
64
- }
65
- return this.scriptsDir;
66
- }
67
41
  /**
68
42
  * Build the HTML layout shell (nav, styles, theme) wrapping body content.
69
43
  *
@@ -105,27 +79,11 @@ class InstallerService {
105
79
  <h1 style="color:var(--error-color);margin-bottom:12px">${title}</h1><p style="color:var(--text-secondary)">${message}</p>
106
80
  <p style="margin-top:16px"><a href="${linkHref}" class="btn-primary">${linkText}</a></p></div></div>`;
107
81
  }
108
- /**
109
- * Generate the Mac installer script (.command) with the API key embedded.
110
- */
111
- generateMacScript(key, _baseUrl) {
112
- const fraimCmd = `npx -y fraim@latest first-run --key=${key}`;
113
- const templatePath = (0, path_1.join)(this.requireScriptsDir(), 'fraim-install-mac.template.sh');
114
- const template = (0, fs_1.readFileSync)(templatePath, 'utf-8');
115
- return template
116
- .replace('{{FRAIM_CMD}}', fraimCmd)
117
- .split('{{KEY}}').join(key);
82
+ generateMacScript(_key, _baseUrl) {
83
+ throw new Error('Shell installer fallbacks are retired; download FRAIM-Setup.dmg.');
118
84
  }
119
- /**
120
- * Generate the Windows installer script (.cmd) with the API key embedded.
121
- */
122
- generateWindowsScript(key, _baseUrl) {
123
- const fraimCmd = `npx -y fraim@latest first-run --key=${key}`;
124
- const templatePath = (0, path_1.join)(this.requireScriptsDir(), 'fraim-install-win.template.cmd');
125
- const template = (0, fs_1.readFileSync)(templatePath, 'utf-8');
126
- return template
127
- .replace('{{FRAIM_CMD}}', fraimCmd)
128
- .split('{{KEY}}').join(key);
85
+ generateWindowsScript(_key, _baseUrl) {
86
+ throw new Error('Command installer fallbacks are retired; download FRAIM-Setup.exe.');
129
87
  }
130
88
  }
131
89
  exports.InstallerService = InstallerService;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SessionManager = void 0;
4
+ const redact_secrets_1 = require("../core/utils/redact-secrets");
4
5
  class SessionManager {
5
6
  constructor(dbService) {
6
7
  this.sessions = new Map();
@@ -42,7 +43,7 @@ class SessionManager {
42
43
  : await this.dbService.getActiveSessionByApiKey(apiKey);
43
44
  if (dbSession) {
44
45
  sessionKey = this.getSessionKey(apiKey, dbSession.sessionId);
45
- console.log(`Re-hydrating session for API Key: ${apiKey}, sessionId: ${dbSession.sessionId}`);
46
+ console.log(`Re-hydrating session for API Key: ${(0, redact_secrets_1.redactApiKey)(apiKey)}, sessionId: ${dbSession.sessionId}`);
46
47
  session = {
47
48
  sessionId: dbSession.sessionId,
48
49
  lastWrite: now,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim",
3
- "version": "2.0.326",
3
+ "version": "2.0.328",
4
4
  "description": "FRAIM core CLI and MCP package.",
5
5
  "main": "index.js",
6
6
  "bin": {