@agentxm/extension-content 0.30.2 → 0.31.0

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.
@@ -5,6 +5,7 @@ import { enforceArchiveContentType, enforceArchiveSizeLimit, } from "./ingest-li
5
5
  import { resolveManifest, validateDeclaredManifestAlignment, } from "./manifest-policy.js";
6
6
  import { FilteredPackageError, validateFilteredPackage } from "./filtered-package-validation.js";
7
7
  const LOCAL_FILE_HEADER_SIZE = 30;
8
+ const INFLATE_CHUNK_SIZE = 16 * 1024;
8
9
  export const defaultReadEntry = (archiveBytes, entry) => Effect.gen(function* () {
9
10
  const { localHeaderOffset, compressedSize, compressionMethod, fileName } = entry;
10
11
  if (localHeaderOffset + LOCAL_FILE_HEADER_SIZE > archiveBytes.length) {
@@ -38,11 +39,19 @@ export const defaultReadEntry = (archiveBytes, entry) => Effect.gen(function* ()
38
39
  return compressedData;
39
40
  }
40
41
  if (compressionMethod === 8) {
41
- // Cap actual decompression at the declared (guardrail-validated) size so a
42
- // small deflate stream cannot expand into a memory bomb, and route any
43
- // inflate throw into the typed error channel instead of an uncaught defect.
42
+ const expansionFailure = () => new ArchiveGuardrailError({
43
+ code: "decompression_limit_exceeded",
44
+ message: `Entry "${fileName}" decompresses beyond its declared size of ${entry.uncompressedSize} bytes.`,
45
+ entry: fileName,
46
+ });
47
+ // workerd grows its bounded zlib buffer by whole chunks, including the
48
+ // final partial chunk. Reserve one chunk of codec headroom, then enforce
49
+ // the exact declared size below. Expansion remains bounded during decode.
44
50
  const result = yield* Effect.try({
45
- try: () => inflateRawSync(compressedData, { maxOutputLength: entry.uncompressedSize }),
51
+ try: () => inflateRawSync(compressedData, {
52
+ chunkSize: INFLATE_CHUNK_SIZE,
53
+ maxOutputLength: entry.uncompressedSize + INFLATE_CHUNK_SIZE,
54
+ }),
46
55
  catch: (error) => {
47
56
  const errorCode = typeof error === "object" &&
48
57
  error !== null &&
@@ -50,12 +59,9 @@ export const defaultReadEntry = (archiveBytes, entry) => Effect.gen(function* ()
50
59
  typeof error.code === "string"
51
60
  ? error.code
52
61
  : "";
53
- return errorCode === "ERR_BUFFER_TOO_LARGE"
54
- ? new ArchiveGuardrailError({
55
- code: "decompression_limit_exceeded",
56
- message: `Entry "${fileName}" decompresses beyond its declared size of ${entry.uncompressedSize} bytes.`,
57
- entry: fileName,
58
- })
62
+ return errorCode === "ERR_BUFFER_TOO_LARGE" ||
63
+ (error instanceof RangeError && error.message === "Memory limit exceeded")
64
+ ? expansionFailure()
59
65
  : new ArchiveGuardrailError({
60
66
  code: "malformed_archive",
61
67
  message: `Failed to decompress entry "${fileName}".`,
@@ -63,6 +69,8 @@ export const defaultReadEntry = (archiveBytes, entry) => Effect.gen(function* ()
63
69
  });
64
70
  },
65
71
  });
72
+ if (result.byteLength > entry.uncompressedSize)
73
+ return yield* expansionFailure();
66
74
  return new Uint8Array(result.buffer, result.byteOffset, result.byteLength);
67
75
  }
68
76
  return yield* new ArchiveGuardrailError({
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "url": "https://github.com/agentxm/axm/issues"
5
5
  },
6
6
  "dependencies": {
7
- "@agentxm/extension-model": "^0.30.2",
7
+ "@agentxm/extension-model": "^0.31.0",
8
8
  "effect": "4.0.0-rc.115",
9
9
  "yaml": "^2.9.0"
10
10
  },
@@ -58,5 +58,5 @@
58
58
  },
59
59
  "sideEffects": false,
60
60
  "type": "module",
61
- "version": "0.30.2"
61
+ "version": "0.31.0"
62
62
  }