@juspay/neurolink 12.51.1 → 12.51.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.
package/CHANGELOG.md CHANGED
@@ -1,8 +1,8 @@
1
- ## [12.51.1](https://github.com/juspay/neurolink/compare/v12.51.0...v12.51.1) (2026-10-12)
1
+ ## [12.51.2](https://github.com/juspay/neurolink/compare/v12.51.1...v12.51.2) (2026-10-12)
2
2
 
3
3
  ### Bug Fixes
4
4
 
5
- - **(workflow):** abort a timed-out model call and hold deadlines at the timer limit ([6315839](https://github.com/juspay/neurolink/commit/63158393d62527f0d0fa70c92b61e95bbd6ffc0a))
5
+ - **(cli):** harden Azure, Vertex and SageMaker setup prompts ([51b8892](https://github.com/juspay/neurolink/commit/51b8892764aa060337e2aff44f0f08fffd8864e5))
6
6
 
7
7
  ## [11.2.3](https://github.com/juspay/neurolink/compare/v11.2.2...v11.2.3) (2026-08-19)
8
8
 
@@ -290,6 +290,19 @@ function validateApiKey(input) {
290
290
  }
291
291
  return true;
292
292
  }
293
+ const AZURE_OPENAI_HOST_SUFFIX = ".openai.azure.com";
294
+ /**
295
+ * True only when the parsed hostname is a subdomain of openai.azure.com.
296
+ * A substring test also accepts `openai.azure.com.evil.example` and
297
+ * `evilopenai.azure.com`, so the leading dot of the suffix is what pins the
298
+ * label boundary. The URL class has already lower-cased the host; a single
299
+ * trailing dot (an absolute DNS name) is the same host and is ignored.
300
+ */
301
+ function isAzureOpenAIHostname(hostname) {
302
+ const host = hostname.endsWith(".") ? hostname.slice(0, -1) : hostname;
303
+ return (host.length > AZURE_OPENAI_HOST_SUFFIX.length &&
304
+ host.endsWith(AZURE_OPENAI_HOST_SUFFIX));
305
+ }
293
306
  /**
294
307
  * Validate Azure OpenAI endpoint URL
295
308
  */
@@ -300,7 +313,7 @@ function validateEndpoint(input) {
300
313
  const trimmed = input.trim();
301
314
  try {
302
315
  const url = new URL(trimmed);
303
- if (!url.hostname.includes("openai.azure.com")) {
316
+ if (!isAzureOpenAIHostname(url.hostname)) {
304
317
  return "Endpoint should be an Azure OpenAI URL (*.openai.azure.com)";
305
318
  }
306
319
  return true;
@@ -469,6 +469,26 @@ function validateServiceAccountJSON(input) {
469
469
  return "Invalid JSON format";
470
470
  }
471
471
  }
472
+ /**
473
+ * Quote a value for a double-quoted .env entry.
474
+ *
475
+ * Line breaks are folded first, whichever way they were typed (a real CR/LF,
476
+ * or the two characters `\n` a key copied out of a service-account JSON file
477
+ * carries), and written back as the `\n` escape the .env loaders expand. That
478
+ * has to come before the backslashes are escaped, or a pasted `\n` would be
479
+ * doubled into `\\n` and the key would stop being a key. Every other backslash
480
+ * is then escaped before the quote is: an unescaped one in front of a `"`
481
+ * cancels the quote's own escape, closes the value early and lets the rest of
482
+ * the input run as part of the .env line.
483
+ */
484
+ function quoteEnvValue(value) {
485
+ const escaped = value
486
+ .replace(/\\r\\n|\\n|\\r|\r\n|\r/g, "\n")
487
+ .replace(/\\/g, "\\\\")
488
+ .replace(/"/g, '\\"')
489
+ .replace(/\n/g, "\\n");
490
+ return `"${escaped}"`;
491
+ }
472
492
  /**
473
493
  * Update .env file with selected authentication method
474
494
  */
@@ -524,10 +544,7 @@ async function updateEnvFile(method, config) {
524
544
  existingVars.set("GOOGLE_AUTH_CLIENT_EMAIL", config.clientEmail);
525
545
  }
526
546
  if (config.privateKey) {
527
- const escaped = config.privateKey
528
- .replace(/\r?\n/g, "\\n")
529
- .replace(/"/g, '\\"');
530
- existingVars.set("GOOGLE_AUTH_PRIVATE_KEY", `"${escaped}"`);
547
+ existingVars.set("GOOGLE_AUTH_PRIVATE_KEY", quoteEnvValue(config.privateKey));
531
548
  }
532
549
  break;
533
550
  }
@@ -1,3 +1,4 @@
1
+ import { randomBytes } from "node:crypto";
1
2
  import chalk from "chalk";
2
3
  import ora from "ora";
3
4
  import inquirer from "inquirer";
@@ -134,7 +135,7 @@ export class SageMakerCommandFactory {
134
135
  * Create secure configuration without exposing credentials to process.env
135
136
  */
136
137
  static createSecureConfiguration(config) {
137
- const sessionId = `sagemaker_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
138
+ const sessionId = `sagemaker_${Date.now()}_${randomBytes(16).toString("hex")}`;
138
139
  const secureConfig = {
139
140
  ...config,
140
141
  sessionId,
@@ -143,9 +144,12 @@ export class SageMakerCommandFactory {
143
144
  // Store temporarily in secure memory store
144
145
  this.secureCredentialStore.set(sessionId, secureConfig);
145
146
  // Auto-cleanup after 5 minutes for security
146
- setTimeout(() => {
147
+ const expiry = setTimeout(() => {
147
148
  this.secureCredentialStore.delete(sessionId);
148
149
  }, 5 * 60 * 1000);
150
+ // Setup also deletes the entry itself when it finishes; the timer is only
151
+ // the backstop and must not hold the CLI open for the rest of its window.
152
+ expiry.unref();
149
153
  return secureConfig;
150
154
  }
151
155
  /**
@@ -505,7 +509,7 @@ export class SageMakerCommandFactory {
505
509
  "šŸ“– Learn more: https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html\n"));
506
510
  spinner.start("Setting up SageMaker configuration...");
507
511
  // Secure credential management without process.env exposure
508
- const secureConfig = this.createSecureConfiguration({
512
+ const secureConfig = SageMakerCommandFactory.createSecureConfiguration({
509
513
  accessKeyId: answers.accessKeyId,
510
514
  secretAccessKey: answers.secretAccessKey,
511
515
  region: answers.region,
@@ -516,7 +520,7 @@ export class SageMakerCommandFactory {
516
520
  // Clear cache and test configuration with secure config
517
521
  clearConfigurationCache();
518
522
  try {
519
- await this.validateSecureConfiguration(secureConfig); // Validate configuration is loadable
523
+ await SageMakerCommandFactory.validateSecureConfiguration(secureConfig); // Validate configuration is loadable
520
524
  spinner.succeed("āœ… Configuration validated successfully");
521
525
  logger.always(chalk.green("\nšŸŽ‰ SageMaker setup complete!"));
522
526
  logger.always(chalk.yellow("\nšŸ’” Next steps:"));
@@ -544,13 +548,13 @@ export class SageMakerCommandFactory {
544
548
  " • Consider using AWS credential rotation policies\n" +
545
549
  " • Monitor AWS CloudTrail for unauthorized access attempts"));
546
550
  // Clear secure credentials from memory after successful setup
547
- this.clearSecureCredentials(secureConfig.sessionId);
551
+ SageMakerCommandFactory.clearSecureCredentials(secureConfig.sessionId);
548
552
  }
549
553
  catch (configError) {
550
554
  spinner.fail("āŒ Configuration validation failed");
551
555
  logger.error(chalk.red(`Error: ${configError instanceof Error ? configError.message : String(configError)}`));
552
556
  // Clear secure credentials from memory on error
553
- this.clearSecureCredentials(secureConfig.sessionId);
557
+ SageMakerCommandFactory.clearSecureCredentials(secureConfig.sessionId);
554
558
  process.exit(1);
555
559
  }
556
560
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "12.51.1",
3
+ "version": "12.51.2",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI Ā· Anthropic Ā· Gemini Ā· Bedrock Ā· Azure Ā· Ollama Ā· TypeSafe Jev and more.",
6
6
  "author": {