@juspay/neurolink 9.88.2 → 9.88.3
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 +6 -0
- package/dist/browser/neurolink.min.js +357 -357
- package/dist/lib/types/file.d.ts +10 -0
- package/dist/lib/utils/fileDetector.js +47 -11
- package/dist/types/file.d.ts +10 -0
- package/dist/utils/fileDetector.js +47 -11
- package/package.json +1 -1
package/dist/lib/types/file.d.ts
CHANGED
|
@@ -298,6 +298,16 @@ export type FileDetectorOptions = {
|
|
|
298
298
|
maxSize?: number;
|
|
299
299
|
timeout?: number;
|
|
300
300
|
allowedTypes?: FileType[];
|
|
301
|
+
/**
|
|
302
|
+
* When set, local file paths must resolve inside this base directory;
|
|
303
|
+
* anything that escapes it (absolute path, `../` traversal, or a symlink
|
|
304
|
+
* pointing outside) is rejected. Containment is enforced on the real,
|
|
305
|
+
* symlink-resolved path of both the base dir and the target, so a symlink
|
|
306
|
+
* inside the base cannot be used to reach a file outside it. Servers that
|
|
307
|
+
* accept file paths from untrusted callers should set this to sandbox
|
|
308
|
+
* filesystem access; SDK callers loading their own files can omit it.
|
|
309
|
+
*/
|
|
310
|
+
allowedBaseDir?: string;
|
|
301
311
|
audioOptions?: AudioProcessorOptions;
|
|
302
312
|
csvOptions?: CSVProcessorOptions;
|
|
303
313
|
officeOptions?: OfficeProcessorOptions;
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* Centralized file detection for all multimodal file types
|
|
4
4
|
* Uses multi-strategy approach for reliable type identification
|
|
5
5
|
*/
|
|
6
|
-
import { readFile,
|
|
6
|
+
import { open, readFile, realpath } from "fs/promises";
|
|
7
|
+
import { isAbsolute as isAbsolutePath, relative as relativePath, resolve as resolvePath, sep, } from "path";
|
|
7
8
|
import { getGlobalDispatcher, interceptors, request } from "undici";
|
|
8
9
|
// Lazy-loaded processor singletons — avoids loading heavy media deps
|
|
9
10
|
// (mediabunny, fluent-ffmpeg, music-metadata, adm-zip) on every generate() call.
|
|
@@ -585,7 +586,9 @@ export class FileDetector {
|
|
|
585
586
|
return result;
|
|
586
587
|
}
|
|
587
588
|
}
|
|
588
|
-
|
|
589
|
+
// Below-threshold detection is the common case for any file under the
|
|
590
|
+
// ContentHeuristic ceiling — a debug detail, not a warning-worthy anomaly.
|
|
591
|
+
logger.debug(`[FileDetector] Best-effort type below threshold: ${best?.type ?? "unknown"} (${best?.metadata.confidence ?? 0}%, threshold ${confidenceThreshold}%)`);
|
|
589
592
|
return best;
|
|
590
593
|
}
|
|
591
594
|
/**
|
|
@@ -1342,7 +1345,7 @@ export class FileDetector {
|
|
|
1342
1345
|
bodyTimeout: timeout,
|
|
1343
1346
|
});
|
|
1344
1347
|
if (response.statusCode !== 200) {
|
|
1345
|
-
throw new Error(`HTTP ${response.statusCode}`);
|
|
1348
|
+
throw new Error(`HTTP ${response.statusCode} fetching ${url}`);
|
|
1346
1349
|
}
|
|
1347
1350
|
const chunks = [];
|
|
1348
1351
|
let totalSize = 0;
|
|
@@ -1359,16 +1362,49 @@ export class FileDetector {
|
|
|
1359
1362
|
/**
|
|
1360
1363
|
* Load file from filesystem path
|
|
1361
1364
|
*/
|
|
1362
|
-
static async loadFromPath(
|
|
1365
|
+
static async loadFromPath(filePath, options) {
|
|
1363
1366
|
const maxSize = options?.maxSize || 200 * 1024 * 1024; // 200MB default (matches Curator memory-safety cap)
|
|
1364
|
-
|
|
1365
|
-
if (
|
|
1366
|
-
throw new Error("
|
|
1367
|
+
// Reject NUL-byte injection outright (a classic path-truncation vector).
|
|
1368
|
+
if (filePath.includes("\0")) {
|
|
1369
|
+
throw new Error("Invalid file path: contains a null byte");
|
|
1370
|
+
}
|
|
1371
|
+
// Optional sandbox: when a base dir is configured (servers accepting paths
|
|
1372
|
+
// from untrusted callers), reject anything that resolves outside it. Real
|
|
1373
|
+
// paths are resolved (symlinks followed) on BOTH sides so a symlink inside
|
|
1374
|
+
// the base dir pointing outside cannot bypass containment. The
|
|
1375
|
+
// path.relative check (not a string prefix) correctly handles the root dir
|
|
1376
|
+
// ("/") and sibling-prefix ("/app" vs "/app-evil") edge cases.
|
|
1377
|
+
if (options?.allowedBaseDir) {
|
|
1378
|
+
let base;
|
|
1379
|
+
let real;
|
|
1380
|
+
try {
|
|
1381
|
+
base = await realpath(resolvePath(options.allowedBaseDir));
|
|
1382
|
+
real = await realpath(filePath);
|
|
1383
|
+
}
|
|
1384
|
+
catch {
|
|
1385
|
+
throw new Error(`Access denied: "${filePath}" could not be resolved within the allowed base directory`);
|
|
1386
|
+
}
|
|
1387
|
+
const rel = relativePath(base, real);
|
|
1388
|
+
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolutePath(rel)) {
|
|
1389
|
+
throw new Error(`Access denied: "${filePath}" resolves outside the allowed base directory`);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
// Open a handle and stat/read through the SAME descriptor so a symlink
|
|
1393
|
+
// swap between the size check and the read cannot occur (TOCTOU).
|
|
1394
|
+
const handle = await open(filePath, "r");
|
|
1395
|
+
try {
|
|
1396
|
+
const statInfo = await handle.stat();
|
|
1397
|
+
if (!statInfo.isFile()) {
|
|
1398
|
+
throw new Error(`Not a file: ${filePath}`);
|
|
1399
|
+
}
|
|
1400
|
+
if (statInfo.size > maxSize) {
|
|
1401
|
+
throw new Error(`File too large: ${filePath} is ${formatFileSize(statInfo.size)} (max: ${formatFileSize(maxSize)})`);
|
|
1402
|
+
}
|
|
1403
|
+
return await handle.readFile();
|
|
1367
1404
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1405
|
+
finally {
|
|
1406
|
+
await handle.close();
|
|
1370
1407
|
}
|
|
1371
|
-
return await readFile(path);
|
|
1372
1408
|
}
|
|
1373
1409
|
/**
|
|
1374
1410
|
* Load file from data URI
|
|
@@ -1376,7 +1412,7 @@ export class FileDetector {
|
|
|
1376
1412
|
static loadFromDataURI(dataUri) {
|
|
1377
1413
|
const match = dataUri.match(/^data:([^;]+);base64,(.+)$/);
|
|
1378
1414
|
if (!match) {
|
|
1379
|
-
throw new Error(
|
|
1415
|
+
throw new Error(`Invalid data URI format (expected "data:<mime>;base64,<data>"): "${dataUri.slice(0, 32)}…"`);
|
|
1380
1416
|
}
|
|
1381
1417
|
return Buffer.from(match[2], "base64");
|
|
1382
1418
|
}
|
package/dist/types/file.d.ts
CHANGED
|
@@ -298,6 +298,16 @@ export type FileDetectorOptions = {
|
|
|
298
298
|
maxSize?: number;
|
|
299
299
|
timeout?: number;
|
|
300
300
|
allowedTypes?: FileType[];
|
|
301
|
+
/**
|
|
302
|
+
* When set, local file paths must resolve inside this base directory;
|
|
303
|
+
* anything that escapes it (absolute path, `../` traversal, or a symlink
|
|
304
|
+
* pointing outside) is rejected. Containment is enforced on the real,
|
|
305
|
+
* symlink-resolved path of both the base dir and the target, so a symlink
|
|
306
|
+
* inside the base cannot be used to reach a file outside it. Servers that
|
|
307
|
+
* accept file paths from untrusted callers should set this to sandbox
|
|
308
|
+
* filesystem access; SDK callers loading their own files can omit it.
|
|
309
|
+
*/
|
|
310
|
+
allowedBaseDir?: string;
|
|
301
311
|
audioOptions?: AudioProcessorOptions;
|
|
302
312
|
csvOptions?: CSVProcessorOptions;
|
|
303
313
|
officeOptions?: OfficeProcessorOptions;
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* Centralized file detection for all multimodal file types
|
|
4
4
|
* Uses multi-strategy approach for reliable type identification
|
|
5
5
|
*/
|
|
6
|
-
import { readFile,
|
|
6
|
+
import { open, readFile, realpath } from "fs/promises";
|
|
7
|
+
import { isAbsolute as isAbsolutePath, relative as relativePath, resolve as resolvePath, sep, } from "path";
|
|
7
8
|
import { getGlobalDispatcher, interceptors, request } from "undici";
|
|
8
9
|
// Lazy-loaded processor singletons — avoids loading heavy media deps
|
|
9
10
|
// (mediabunny, fluent-ffmpeg, music-metadata, adm-zip) on every generate() call.
|
|
@@ -585,7 +586,9 @@ export class FileDetector {
|
|
|
585
586
|
return result;
|
|
586
587
|
}
|
|
587
588
|
}
|
|
588
|
-
|
|
589
|
+
// Below-threshold detection is the common case for any file under the
|
|
590
|
+
// ContentHeuristic ceiling — a debug detail, not a warning-worthy anomaly.
|
|
591
|
+
logger.debug(`[FileDetector] Best-effort type below threshold: ${best?.type ?? "unknown"} (${best?.metadata.confidence ?? 0}%, threshold ${confidenceThreshold}%)`);
|
|
589
592
|
return best;
|
|
590
593
|
}
|
|
591
594
|
/**
|
|
@@ -1342,7 +1345,7 @@ export class FileDetector {
|
|
|
1342
1345
|
bodyTimeout: timeout,
|
|
1343
1346
|
});
|
|
1344
1347
|
if (response.statusCode !== 200) {
|
|
1345
|
-
throw new Error(`HTTP ${response.statusCode}`);
|
|
1348
|
+
throw new Error(`HTTP ${response.statusCode} fetching ${url}`);
|
|
1346
1349
|
}
|
|
1347
1350
|
const chunks = [];
|
|
1348
1351
|
let totalSize = 0;
|
|
@@ -1359,16 +1362,49 @@ export class FileDetector {
|
|
|
1359
1362
|
/**
|
|
1360
1363
|
* Load file from filesystem path
|
|
1361
1364
|
*/
|
|
1362
|
-
static async loadFromPath(
|
|
1365
|
+
static async loadFromPath(filePath, options) {
|
|
1363
1366
|
const maxSize = options?.maxSize || 200 * 1024 * 1024; // 200MB default (matches Curator memory-safety cap)
|
|
1364
|
-
|
|
1365
|
-
if (
|
|
1366
|
-
throw new Error("
|
|
1367
|
+
// Reject NUL-byte injection outright (a classic path-truncation vector).
|
|
1368
|
+
if (filePath.includes("\0")) {
|
|
1369
|
+
throw new Error("Invalid file path: contains a null byte");
|
|
1370
|
+
}
|
|
1371
|
+
// Optional sandbox: when a base dir is configured (servers accepting paths
|
|
1372
|
+
// from untrusted callers), reject anything that resolves outside it. Real
|
|
1373
|
+
// paths are resolved (symlinks followed) on BOTH sides so a symlink inside
|
|
1374
|
+
// the base dir pointing outside cannot bypass containment. The
|
|
1375
|
+
// path.relative check (not a string prefix) correctly handles the root dir
|
|
1376
|
+
// ("/") and sibling-prefix ("/app" vs "/app-evil") edge cases.
|
|
1377
|
+
if (options?.allowedBaseDir) {
|
|
1378
|
+
let base;
|
|
1379
|
+
let real;
|
|
1380
|
+
try {
|
|
1381
|
+
base = await realpath(resolvePath(options.allowedBaseDir));
|
|
1382
|
+
real = await realpath(filePath);
|
|
1383
|
+
}
|
|
1384
|
+
catch {
|
|
1385
|
+
throw new Error(`Access denied: "${filePath}" could not be resolved within the allowed base directory`);
|
|
1386
|
+
}
|
|
1387
|
+
const rel = relativePath(base, real);
|
|
1388
|
+
if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolutePath(rel)) {
|
|
1389
|
+
throw new Error(`Access denied: "${filePath}" resolves outside the allowed base directory`);
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
// Open a handle and stat/read through the SAME descriptor so a symlink
|
|
1393
|
+
// swap between the size check and the read cannot occur (TOCTOU).
|
|
1394
|
+
const handle = await open(filePath, "r");
|
|
1395
|
+
try {
|
|
1396
|
+
const statInfo = await handle.stat();
|
|
1397
|
+
if (!statInfo.isFile()) {
|
|
1398
|
+
throw new Error(`Not a file: ${filePath}`);
|
|
1399
|
+
}
|
|
1400
|
+
if (statInfo.size > maxSize) {
|
|
1401
|
+
throw new Error(`File too large: ${filePath} is ${formatFileSize(statInfo.size)} (max: ${formatFileSize(maxSize)})`);
|
|
1402
|
+
}
|
|
1403
|
+
return await handle.readFile();
|
|
1367
1404
|
}
|
|
1368
|
-
|
|
1369
|
-
|
|
1405
|
+
finally {
|
|
1406
|
+
await handle.close();
|
|
1370
1407
|
}
|
|
1371
|
-
return await readFile(path);
|
|
1372
1408
|
}
|
|
1373
1409
|
/**
|
|
1374
1410
|
* Load file from data URI
|
|
@@ -1376,7 +1412,7 @@ export class FileDetector {
|
|
|
1376
1412
|
static loadFromDataURI(dataUri) {
|
|
1377
1413
|
const match = dataUri.match(/^data:([^;]+);base64,(.+)$/);
|
|
1378
1414
|
if (!match) {
|
|
1379
|
-
throw new Error(
|
|
1415
|
+
throw new Error(`Invalid data URI format (expected "data:<mime>;base64,<data>"): "${dataUri.slice(0, 32)}…"`);
|
|
1380
1416
|
}
|
|
1381
1417
|
return Buffer.from(match[2], "base64");
|
|
1382
1418
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "9.88.
|
|
3
|
+
"version": "9.88.3",
|
|
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": {
|