@juspay/neurolink 9.88.0 → 9.88.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.
@@ -50,7 +50,7 @@ const ConfigSchema = z.object({
50
50
  sessionToken: z.string().optional(),
51
51
  model: z
52
52
  .string()
53
- .default("arn:aws:bedrock:us-east-2:225681119357:inference-profile/us.anthropic.claude-3-7-sonnet-20250219-v1:0"),
53
+ .default("arn:aws:bedrock:us-east-2:123456789012:inference-profile/us.anthropic.claude-3-7-sonnet-20250219-v1:0"),
54
54
  })
55
55
  .optional(),
56
56
  vertex: z
@@ -582,7 +582,7 @@ export class ConfigManager {
582
582
  type: "input",
583
583
  name: "model",
584
584
  message: "Model ARN:",
585
- default: "arn:aws:bedrock:us-east-2:225681119357:inference-profile/us.anthropic.claude-3-7-sonnet-20250219-v1:0",
585
+ default: "arn:aws:bedrock:us-east-2:123456789012:inference-profile/us.anthropic.claude-3-7-sonnet-20250219-v1:0",
586
586
  },
587
587
  ]);
588
588
  this.config.providers.bedrock = {
@@ -56,6 +56,12 @@ export declare const directAgentTools: {
56
56
  path: string;
57
57
  includeHidden: boolean;
58
58
  }, {
59
+ success: boolean;
60
+ error: string;
61
+ path?: undefined;
62
+ items?: undefined;
63
+ count?: undefined;
64
+ } | {
59
65
  success: boolean;
60
66
  path: string;
61
67
  items: {
@@ -14,6 +14,28 @@ function truncateOutput(output) {
14
14
  }
15
15
  return output;
16
16
  }
17
+ /**
18
+ * Contain a built-in file tool to the process working directory.
19
+ *
20
+ * Returns the resolved absolute path when it lies inside `process.cwd()`, or an
21
+ * `error` string otherwise. This is the sandbox for the default-enabled
22
+ * `readFile` / `writeFile` / `listDirectory` / `analyzeCSV` tools: without it an
23
+ * agent can pass an absolute path (`/etc/passwd`) or `../` traversal and reach
24
+ * arbitrary files. The previous guard (`!resolvedPath.startsWith(cwd) &&
25
+ * !path.isAbsolute(filePath)`) was always false for absolute paths, so it never
26
+ * fired. The `cwd + path.sep` suffix prevents a sibling-prefix bypass
27
+ * (`/home/app` vs `/home/app-evil`).
28
+ */
29
+ function resolveWithinCwd(filePath) {
30
+ const resolvedPath = path.resolve(filePath);
31
+ const cwd = path.resolve(process.cwd());
32
+ if (resolvedPath !== cwd && !resolvedPath.startsWith(cwd + path.sep)) {
33
+ return {
34
+ error: `Access denied: "${filePath}" resolves outside the working directory`,
35
+ };
36
+ }
37
+ return { path: resolvedPath };
38
+ }
17
39
  // Runtime Google Search tool creation - bypasses TypeScript strict typing
18
40
  function createGoogleSearchTools() {
19
41
  const searchTool = {};
@@ -71,15 +93,13 @@ export const directAgentTools = {
71
93
  }),
72
94
  execute: async ({ path: filePath }) => {
73
95
  try {
74
- // Security check - prevent reading outside current directory for relative paths
75
- const resolvedPath = path.resolve(filePath);
76
- const cwd = process.cwd();
77
- if (!resolvedPath.startsWith(cwd) && !path.isAbsolute(filePath)) {
78
- return {
79
- success: false,
80
- error: `Access denied: Cannot read files outside current directory`,
81
- };
96
+ // Sandbox: contain reads to the working directory (blocks absolute-path
97
+ // escape and ../ traversal).
98
+ const guard = resolveWithinCwd(filePath);
99
+ if ("error" in guard) {
100
+ return { success: false, error: guard.error };
82
101
  }
102
+ const resolvedPath = guard.path;
83
103
  const content = fs.readFileSync(resolvedPath, "utf-8");
84
104
  const stats = fs.statSync(resolvedPath);
85
105
  return {
@@ -113,7 +133,12 @@ export const directAgentTools = {
113
133
  }),
114
134
  execute: async ({ path: dirPath, includeHidden }) => {
115
135
  try {
116
- const resolvedPath = path.resolve(dirPath);
136
+ // Sandbox: contain directory listing to the working directory.
137
+ const guard = resolveWithinCwd(dirPath);
138
+ if ("error" in guard) {
139
+ return { success: false, error: guard.error };
140
+ }
141
+ const resolvedPath = guard.path;
117
142
  const items = fs.readdirSync(resolvedPath);
118
143
  const filteredItems = includeHidden
119
144
  ? items
@@ -229,15 +254,13 @@ export const directAgentTools = {
229
254
  }),
230
255
  execute: async ({ path: filePath, content, mode }) => {
231
256
  try {
232
- const resolvedPath = path.resolve(filePath);
233
- const cwd = process.cwd();
234
- // Security check
235
- if (!resolvedPath.startsWith(cwd) && !path.isAbsolute(filePath)) {
236
- return {
237
- success: false,
238
- error: `Access denied: Cannot write files outside current directory`,
239
- };
257
+ // Sandbox: contain writes to the working directory (blocks absolute-path
258
+ // escape and ../ traversal).
259
+ const guard = resolveWithinCwd(filePath);
260
+ if ("error" in guard) {
261
+ return { success: false, error: guard.error };
240
262
  }
263
+ const resolvedPath = guard.path;
241
264
  // Check if file exists for create mode
242
265
  if (mode === "create" && fs.existsSync(resolvedPath)) {
243
266
  return {
@@ -326,11 +349,12 @@ export const directAgentTools = {
326
349
  try {
327
350
  // Resolve file path
328
351
  logger.debug(`[analyzeCSV] Resolving file: ${filePath}`);
329
- const path = await import("path");
330
- // Resolve path (support both relative and absolute)
331
- const resolvedPath = path.isAbsolute(filePath)
332
- ? filePath
333
- : path.resolve(process.cwd(), filePath);
352
+ // Sandbox: contain CSV reads to the working directory.
353
+ const guard = resolveWithinCwd(filePath);
354
+ if ("error" in guard) {
355
+ return { success: false, error: guard.error };
356
+ }
357
+ const resolvedPath = guard.path;
334
358
  logger.debug(`[analyzeCSV] Resolved path: ${resolvedPath}`);
335
359
  // Parse CSV using streaming from disk (memory efficient)
336
360
  logger.info(`[analyzeCSV] Starting CSV parsing (max ${maxRows} rows)...`);
@@ -115,17 +115,25 @@ function isDirectorSegment(segment) {
115
115
  return false;
116
116
  }
117
117
  export function isMultimodalInput(input) {
118
- const maybeInput = input;
119
- return !!(maybeInput?.images?.length ||
120
- maybeInput?.csvFiles?.length ||
121
- maybeInput?.pdfFiles?.length ||
122
- maybeInput?.files?.length ||
123
- maybeInput?.content?.length ||
124
- maybeInput?.audioFiles?.length ||
125
- maybeInput?.videoFiles?.length ||
126
- (maybeInput?.segments?.length &&
127
- Array.isArray(maybeInput.segments) &&
128
- maybeInput.segments.every(isDirectorSegment)));
118
+ if (typeof input !== "object" || input === null) {
119
+ return false;
120
+ }
121
+ const m = input;
122
+ // Each field must be a NON-EMPTY ARRAY. Checking `.length` alone is unsafe:
123
+ // a string value (e.g. `{ images: "oops" }`) has a truthy `.length`, so the
124
+ // old guard would claim a malformed input is multimodal and then crash
125
+ // downstream when the field is iterated as an array.
126
+ const hasItems = (v) => Array.isArray(v) && v.length > 0;
127
+ return !!(hasItems(m.images) ||
128
+ hasItems(m.csvFiles) ||
129
+ hasItems(m.pdfFiles) ||
130
+ hasItems(m.files) ||
131
+ hasItems(m.content) ||
132
+ hasItems(m.audioFiles) ||
133
+ hasItems(m.videoFiles) ||
134
+ (Array.isArray(m.segments) &&
135
+ m.segments.length > 0 &&
136
+ m.segments.every(isDirectorSegment)));
129
137
  }
130
138
  /**
131
139
  * Type guard to check if message content is multimodal (array)
@@ -115,17 +115,25 @@ function isDirectorSegment(segment) {
115
115
  return false;
116
116
  }
117
117
  export function isMultimodalInput(input) {
118
- const maybeInput = input;
119
- return !!(maybeInput?.images?.length ||
120
- maybeInput?.csvFiles?.length ||
121
- maybeInput?.pdfFiles?.length ||
122
- maybeInput?.files?.length ||
123
- maybeInput?.content?.length ||
124
- maybeInput?.audioFiles?.length ||
125
- maybeInput?.videoFiles?.length ||
126
- (maybeInput?.segments?.length &&
127
- Array.isArray(maybeInput.segments) &&
128
- maybeInput.segments.every(isDirectorSegment)));
118
+ if (typeof input !== "object" || input === null) {
119
+ return false;
120
+ }
121
+ const m = input;
122
+ // Each field must be a NON-EMPTY ARRAY. Checking `.length` alone is unsafe:
123
+ // a string value (e.g. `{ images: "oops" }`) has a truthy `.length`, so the
124
+ // old guard would claim a malformed input is multimodal and then crash
125
+ // downstream when the field is iterated as an array.
126
+ const hasItems = (v) => Array.isArray(v) && v.length > 0;
127
+ return !!(hasItems(m.images) ||
128
+ hasItems(m.csvFiles) ||
129
+ hasItems(m.pdfFiles) ||
130
+ hasItems(m.files) ||
131
+ hasItems(m.content) ||
132
+ hasItems(m.audioFiles) ||
133
+ hasItems(m.videoFiles) ||
134
+ (Array.isArray(m.segments) &&
135
+ m.segments.length > 0 &&
136
+ m.segments.every(isDirectorSegment)));
129
137
  }
130
138
  /**
131
139
  * Type guard to check if message content is multimodal (array)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "9.88.0",
3
+ "version": "9.88.2",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (58+ servers), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
6
6
  "author": {