Skip to content

Security: Unbounded memory consumption when buffering S3 object streams#312

Open
tuanaiseo wants to merge 1 commit into
jeremydaly:mainfrom
tuanaiseo:contribai/fix/security/unbounded-memory-consumption-when-buffer
Open

Security: Unbounded memory consumption when buffering S3 object streams#312
tuanaiseo wants to merge 1 commit into
jeremydaly:mainfrom
tuanaiseo:contribai/fix/security/unbounded-memory-consumption-when-buffer

Conversation

@tuanaiseo
Copy link
Copy Markdown

Problem

getObject converts the entire S3 response stream into a Buffer via streamToBuffer with no size limit. Large objects can exhaust Lambda memory and cause denial of service if object size is attacker-influenced or not strictly controlled.

Severity: high
File: lib/s3-service.js

Solution

Avoid full buffering for arbitrary objects. Enforce a maximum allowed content length before reading, stream directly to the client/storage when possible, and abort reads when size thresholds are exceeded.

Changes

  • lib/s3-service.js (modified)

Testing

  • Existing tests pass
  • Manual review completed
  • No new warnings/errors introduced

`getObject` converts the entire S3 response stream into a Buffer via `streamToBuffer` with no size limit. Large objects can exhaust Lambda memory and cause denial of service if object size is attacker-influenced or not strictly controlled.

Affected files: s3-service.js

Signed-off-by: tuanaiseo <221258316+tuanaiseo@users.noreply.github.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a size cap to the in-memory buffering performed by s3-service.getObject() to mitigate a potential DoS from very large S3 objects exhausting Lambda memory. The cap is configurable via AWS_S3_MAX_BUFFERED_OBJECT_SIZE / AWS_S3_MAX_OBJECT_SIZE env vars and defaults to 10 MiB. The previously-shared streamToBuffer import from ./utils is replaced by a local streamToBufferWithLimit helper that aborts mid-stream when the cap is exceeded.

Changes:

  • Introduce a configurable MAX_BUFFERED_OBJECT_SIZE (default 10 MiB) read from env vars.
  • Add a local streamToBufferWithLimit helper that destroys the source stream and throws when the limit is exceeded.
  • In getObject, pre-flight reject responses whose ContentLength exceeds the limit, and enforce the limit mid-stream when reading the body.

Comment thread lib/s3-service.js
Comment on lines +11 to +16
const MAX_BUFFERED_OBJECT_SIZE = parseInt(
process.env.AWS_S3_MAX_BUFFERED_OBJECT_SIZE ||
process.env.AWS_S3_MAX_OBJECT_SIZE ||
10485760,
10
);
Comment thread lib/s3-service.js
Comment on lines +11 to +16
const MAX_BUFFERED_OBJECT_SIZE = parseInt(
process.env.AWS_S3_MAX_BUFFERED_OBJECT_SIZE ||
process.env.AWS_S3_MAX_OBJECT_SIZE ||
10485760,
10
);
Comment thread lib/s3-service.js
Comment on lines +18 to +35
const streamToBufferWithLimit = async (stream, maxSize) => {
const chunks = [];
let size = 0;

for await (const chunk of stream) {
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
size += buffer.length;

if (size > maxSize) {
if (typeof stream.destroy === 'function') stream.destroy();
throw new Error('S3 object exceeds maximum allowed buffered size');
}

chunks.push(buffer);
}

return Buffer.concat(chunks);
};
Comment thread lib/s3-service.js
Comment on lines 28 to +54
@@ -21,9 +45,18 @@ exports.getObject = (params) => {

if (!res.Body) return res;

if (
Number.isFinite(MAX_BUFFERED_OBJECT_SIZE) &&
MAX_BUFFERED_OBJECT_SIZE > 0 &&
typeof res.ContentLength === 'number' &&
res.ContentLength > MAX_BUFFERED_OBJECT_SIZE
) {
throw new Error('S3 object exceeds maximum allowed buffered size');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants