@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
|
+
## [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
|
-
- **(
|
|
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
|
|
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
|
-
|
|
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()}_${
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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": {
|