@juspay/neurolink 10.12.0 → 10.12.1
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.
|
@@ -162,6 +162,17 @@ const SINGLE_STREAM_TOOLS = {
|
|
|
162
162
|
xz: "xz",
|
|
163
163
|
zst: "zstd",
|
|
164
164
|
};
|
|
165
|
+
/**
|
|
166
|
+
* Whether a zlib rejection is the output bound firing rather than bad input.
|
|
167
|
+
*
|
|
168
|
+
* `maxOutputLength` aborts an inflate the moment its output would pass the cap,
|
|
169
|
+
* which is the whole point — but it surfaces as a plain `RangeError`, and a
|
|
170
|
+
* bomb reported as "failed to decompress" reads as a corrupt upload and invites
|
|
171
|
+
* the user to send it again. It will fail identically every time.
|
|
172
|
+
*
|
|
173
|
+
* Keyed on `code`, not the message: the message embeds a byte count.
|
|
174
|
+
*/
|
|
175
|
+
const isDecompressionBoundExceeded = (error) => error?.code === "ERR_BUFFER_TOO_LARGE";
|
|
165
176
|
/** File extensions recognized as archive formats */
|
|
166
177
|
const SUPPORTED_ARCHIVE_EXTENSIONS = [".zip", ".tar", ".gz", ".tgz", ".bz2", ".tbz2", ".jar", ".xz", ".txz", ".zst", ".tzst"];
|
|
167
178
|
// =============================================================================
|
|
@@ -761,19 +772,16 @@ export class ArchiveProcessor extends BaseFileProcessor {
|
|
|
761
772
|
const zlib = await import("zlib");
|
|
762
773
|
const { promisify } = await import("util");
|
|
763
774
|
const gunzip = promisify(zlib.gunzip);
|
|
764
|
-
|
|
775
|
+
// Bounded at the decoder, matching the zstd path. Checking the length
|
|
776
|
+
// afterwards only reports a bomb once it has already been paid for: 40KB
|
|
777
|
+
// of gzip inflates to 40MB, and the allocation is the damage, not the
|
|
778
|
+
// number. `maxOutputLength` abandons the inflate at the cap instead, so
|
|
779
|
+
// the ceiling on memory is the limit rather than whatever the attacker
|
|
780
|
+
// chose. The overflow is classified in the catch below.
|
|
781
|
+
const decompressed = await gunzip(buffer, {
|
|
782
|
+
maxOutputLength: ARCHIVE_SECURITY.MAX_DECOMPRESSED_SIZE,
|
|
783
|
+
});
|
|
765
784
|
const tarBuffer = Buffer.from(decompressed);
|
|
766
|
-
// Security: check decompressed size
|
|
767
|
-
if (tarBuffer.length > ARCHIVE_SECURITY.MAX_DECOMPRESSED_SIZE) {
|
|
768
|
-
return {
|
|
769
|
-
success: false,
|
|
770
|
-
entries: [],
|
|
771
|
-
securityWarnings: [],
|
|
772
|
-
error: this.createError(FileErrorCode.SECURITY_VALIDATION_FAILED, {
|
|
773
|
-
reason: `Decompressed TAR size (${this.formatSizeMB(tarBuffer.length)} MB) exceeds limit (${this.formatSizeMB(ARCHIVE_SECURITY.MAX_DECOMPRESSED_SIZE)} MB)`,
|
|
774
|
-
}),
|
|
775
|
-
};
|
|
776
|
-
}
|
|
777
785
|
// Security: check compression ratio
|
|
778
786
|
if (buffer.length > 0) {
|
|
779
787
|
const ratio = tarBuffer.length / buffer.length;
|
|
@@ -793,6 +801,16 @@ export class ArchiveProcessor extends BaseFileProcessor {
|
|
|
793
801
|
return await this.parseTarStream(tarStream, tarBuffer);
|
|
794
802
|
}
|
|
795
803
|
catch (error) {
|
|
804
|
+
if (isDecompressionBoundExceeded(error)) {
|
|
805
|
+
return {
|
|
806
|
+
success: false,
|
|
807
|
+
entries: [],
|
|
808
|
+
securityWarnings: [],
|
|
809
|
+
error: this.createError(FileErrorCode.SECURITY_VALIDATION_FAILED, {
|
|
810
|
+
reason: `Decompressed TAR size exceeds limit (${this.formatSizeMB(ARCHIVE_SECURITY.MAX_DECOMPRESSED_SIZE)} MB)`,
|
|
811
|
+
}),
|
|
812
|
+
};
|
|
813
|
+
}
|
|
796
814
|
// Check if the error is one we already created (security validation)
|
|
797
815
|
if (error &&
|
|
798
816
|
typeof error === "object" &&
|
|
@@ -976,18 +994,11 @@ export class ArchiveProcessor extends BaseFileProcessor {
|
|
|
976
994
|
const zlib = await import("zlib");
|
|
977
995
|
const { promisify } = await import("util");
|
|
978
996
|
const gunzip = promisify(zlib.gunzip);
|
|
979
|
-
|
|
980
|
-
//
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
entries: [],
|
|
985
|
-
securityWarnings: [],
|
|
986
|
-
error: this.createError(FileErrorCode.SECURITY_VALIDATION_FAILED, {
|
|
987
|
-
reason: `Decompressed size (${this.formatSizeMB(decompressed.length)} MB) exceeds limit (${this.formatSizeMB(ARCHIVE_SECURITY.MAX_DECOMPRESSED_SIZE)} MB)`,
|
|
988
|
-
}),
|
|
989
|
-
};
|
|
990
|
-
}
|
|
997
|
+
// Bounded at the decoder — see the matching call in extractTarGzEntries.
|
|
998
|
+
// The overflow is classified in the catch below.
|
|
999
|
+
const decompressed = await gunzip(buffer, {
|
|
1000
|
+
maxOutputLength: ARCHIVE_SECURITY.MAX_DECOMPRESSED_SIZE,
|
|
1001
|
+
});
|
|
991
1002
|
// Security: compression ratio
|
|
992
1003
|
if (buffer.length > 0) {
|
|
993
1004
|
const ratio = decompressed.length / buffer.length;
|
|
@@ -1031,6 +1042,16 @@ export class ArchiveProcessor extends BaseFileProcessor {
|
|
|
1031
1042
|
return { success: true, entries, securityWarnings, contents };
|
|
1032
1043
|
}
|
|
1033
1044
|
catch (error) {
|
|
1045
|
+
if (isDecompressionBoundExceeded(error)) {
|
|
1046
|
+
return {
|
|
1047
|
+
success: false,
|
|
1048
|
+
entries: [],
|
|
1049
|
+
securityWarnings: [],
|
|
1050
|
+
error: this.createError(FileErrorCode.SECURITY_VALIDATION_FAILED, {
|
|
1051
|
+
reason: `Decompressed size exceeds limit (${this.formatSizeMB(ARCHIVE_SECURITY.MAX_DECOMPRESSED_SIZE)} MB)`,
|
|
1052
|
+
}),
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1034
1055
|
return {
|
|
1035
1056
|
success: false,
|
|
1036
1057
|
entries: [],
|
|
@@ -162,6 +162,17 @@ const SINGLE_STREAM_TOOLS = {
|
|
|
162
162
|
xz: "xz",
|
|
163
163
|
zst: "zstd",
|
|
164
164
|
};
|
|
165
|
+
/**
|
|
166
|
+
* Whether a zlib rejection is the output bound firing rather than bad input.
|
|
167
|
+
*
|
|
168
|
+
* `maxOutputLength` aborts an inflate the moment its output would pass the cap,
|
|
169
|
+
* which is the whole point — but it surfaces as a plain `RangeError`, and a
|
|
170
|
+
* bomb reported as "failed to decompress" reads as a corrupt upload and invites
|
|
171
|
+
* the user to send it again. It will fail identically every time.
|
|
172
|
+
*
|
|
173
|
+
* Keyed on `code`, not the message: the message embeds a byte count.
|
|
174
|
+
*/
|
|
175
|
+
const isDecompressionBoundExceeded = (error) => error?.code === "ERR_BUFFER_TOO_LARGE";
|
|
165
176
|
/** File extensions recognized as archive formats */
|
|
166
177
|
const SUPPORTED_ARCHIVE_EXTENSIONS = [".zip", ".tar", ".gz", ".tgz", ".bz2", ".tbz2", ".jar", ".xz", ".txz", ".zst", ".tzst"];
|
|
167
178
|
// =============================================================================
|
|
@@ -761,19 +772,16 @@ export class ArchiveProcessor extends BaseFileProcessor {
|
|
|
761
772
|
const zlib = await import("zlib");
|
|
762
773
|
const { promisify } = await import("util");
|
|
763
774
|
const gunzip = promisify(zlib.gunzip);
|
|
764
|
-
|
|
775
|
+
// Bounded at the decoder, matching the zstd path. Checking the length
|
|
776
|
+
// afterwards only reports a bomb once it has already been paid for: 40KB
|
|
777
|
+
// of gzip inflates to 40MB, and the allocation is the damage, not the
|
|
778
|
+
// number. `maxOutputLength` abandons the inflate at the cap instead, so
|
|
779
|
+
// the ceiling on memory is the limit rather than whatever the attacker
|
|
780
|
+
// chose. The overflow is classified in the catch below.
|
|
781
|
+
const decompressed = await gunzip(buffer, {
|
|
782
|
+
maxOutputLength: ARCHIVE_SECURITY.MAX_DECOMPRESSED_SIZE,
|
|
783
|
+
});
|
|
765
784
|
const tarBuffer = Buffer.from(decompressed);
|
|
766
|
-
// Security: check decompressed size
|
|
767
|
-
if (tarBuffer.length > ARCHIVE_SECURITY.MAX_DECOMPRESSED_SIZE) {
|
|
768
|
-
return {
|
|
769
|
-
success: false,
|
|
770
|
-
entries: [],
|
|
771
|
-
securityWarnings: [],
|
|
772
|
-
error: this.createError(FileErrorCode.SECURITY_VALIDATION_FAILED, {
|
|
773
|
-
reason: `Decompressed TAR size (${this.formatSizeMB(tarBuffer.length)} MB) exceeds limit (${this.formatSizeMB(ARCHIVE_SECURITY.MAX_DECOMPRESSED_SIZE)} MB)`,
|
|
774
|
-
}),
|
|
775
|
-
};
|
|
776
|
-
}
|
|
777
785
|
// Security: check compression ratio
|
|
778
786
|
if (buffer.length > 0) {
|
|
779
787
|
const ratio = tarBuffer.length / buffer.length;
|
|
@@ -793,6 +801,16 @@ export class ArchiveProcessor extends BaseFileProcessor {
|
|
|
793
801
|
return await this.parseTarStream(tarStream, tarBuffer);
|
|
794
802
|
}
|
|
795
803
|
catch (error) {
|
|
804
|
+
if (isDecompressionBoundExceeded(error)) {
|
|
805
|
+
return {
|
|
806
|
+
success: false,
|
|
807
|
+
entries: [],
|
|
808
|
+
securityWarnings: [],
|
|
809
|
+
error: this.createError(FileErrorCode.SECURITY_VALIDATION_FAILED, {
|
|
810
|
+
reason: `Decompressed TAR size exceeds limit (${this.formatSizeMB(ARCHIVE_SECURITY.MAX_DECOMPRESSED_SIZE)} MB)`,
|
|
811
|
+
}),
|
|
812
|
+
};
|
|
813
|
+
}
|
|
796
814
|
// Check if the error is one we already created (security validation)
|
|
797
815
|
if (error &&
|
|
798
816
|
typeof error === "object" &&
|
|
@@ -976,18 +994,11 @@ export class ArchiveProcessor extends BaseFileProcessor {
|
|
|
976
994
|
const zlib = await import("zlib");
|
|
977
995
|
const { promisify } = await import("util");
|
|
978
996
|
const gunzip = promisify(zlib.gunzip);
|
|
979
|
-
|
|
980
|
-
//
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
entries: [],
|
|
985
|
-
securityWarnings: [],
|
|
986
|
-
error: this.createError(FileErrorCode.SECURITY_VALIDATION_FAILED, {
|
|
987
|
-
reason: `Decompressed size (${this.formatSizeMB(decompressed.length)} MB) exceeds limit (${this.formatSizeMB(ARCHIVE_SECURITY.MAX_DECOMPRESSED_SIZE)} MB)`,
|
|
988
|
-
}),
|
|
989
|
-
};
|
|
990
|
-
}
|
|
997
|
+
// Bounded at the decoder — see the matching call in extractTarGzEntries.
|
|
998
|
+
// The overflow is classified in the catch below.
|
|
999
|
+
const decompressed = await gunzip(buffer, {
|
|
1000
|
+
maxOutputLength: ARCHIVE_SECURITY.MAX_DECOMPRESSED_SIZE,
|
|
1001
|
+
});
|
|
991
1002
|
// Security: compression ratio
|
|
992
1003
|
if (buffer.length > 0) {
|
|
993
1004
|
const ratio = decompressed.length / buffer.length;
|
|
@@ -1031,6 +1042,16 @@ export class ArchiveProcessor extends BaseFileProcessor {
|
|
|
1031
1042
|
return { success: true, entries, securityWarnings, contents };
|
|
1032
1043
|
}
|
|
1033
1044
|
catch (error) {
|
|
1045
|
+
if (isDecompressionBoundExceeded(error)) {
|
|
1046
|
+
return {
|
|
1047
|
+
success: false,
|
|
1048
|
+
entries: [],
|
|
1049
|
+
securityWarnings: [],
|
|
1050
|
+
error: this.createError(FileErrorCode.SECURITY_VALIDATION_FAILED, {
|
|
1051
|
+
reason: `Decompressed size exceeds limit (${this.formatSizeMB(ARCHIVE_SECURITY.MAX_DECOMPRESSED_SIZE)} MB)`,
|
|
1052
|
+
}),
|
|
1053
|
+
};
|
|
1054
|
+
}
|
|
1034
1055
|
return {
|
|
1035
1056
|
success: false,
|
|
1036
1057
|
entries: [],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juspay/neurolink",
|
|
3
|
-
"version": "10.12.
|
|
3
|
+
"version": "10.12.1",
|
|
4
4
|
"packageManager": "pnpm@10.15.1",
|
|
5
5
|
"description": "TypeScript AI SDK with 24+ LLM providers behind one consistent API. MCP-native (connect any MCP server), voice TTS/STT/realtime, RAG, agents, memory, context compaction. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · DeepSeek · NVIDIA NIM and more.",
|
|
6
6
|
"author": {
|