Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
The format is based on [Keep a Changelog](http://keepachangelog.com/).

## Unreleased

### Fixed

- Upload size limit is now enforced even when malware scanning is disabled (`scan: false`). Previously, `validateAttachmentSize` used `MAX_FILE_SIZE` as a function reference instead of calling it, and the function returned `-1` (no limit) when scanning was off — allowing arbitrarily large uploads on entities without `@Validation.Maximum`.

## Version 4.0.0 - 2026-08-03

**BREAKING CHANGE: The attachments plugin comes now without hyperscaler dependencies, please make sure to install them accordingly!**
Expand Down
10 changes: 7 additions & 3 deletions lib/generic-handlers.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
const cds = require("@sap/cds")
const LOG = cds.log("attachments")
const { extname } = require("path")
const { MAX_FILE_SIZE, sizeInBytes, checkMimeTypeMatch } = require("./helper")
const {
DEFAULT_MAX_FILE_SIZE,
sizeInBytes,
checkMimeTypeMatch,
} = require("./helper")
const { getMime } = require("./mime")

/**
Expand Down Expand Up @@ -469,8 +473,8 @@ async function validateAttachmentSize(req, validateContentLength = false) {

const maxFileSize = ctx.element["@Validation.Maximum"]
? (sizeInBytes(ctx.element["@Validation.Maximum"], req.target.name) ??
MAX_FILE_SIZE)
: MAX_FILE_SIZE
DEFAULT_MAX_FILE_SIZE)
: DEFAULT_MAX_FILE_SIZE

const isInMemory =
ctx.content != null && typeof ctx.content.length === "number"
Expand Down
18 changes: 5 additions & 13 deletions lib/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -494,15 +494,7 @@ multipliers.PB = multipliers.TB * 1024
multipliers.EB = multipliers.PB * 1024
multipliers.ZB = multipliers.EB * 1024

/**
* Returns the maximum file size for uploads.
* Returns -1 (no limit) when malware scanning is disabled, otherwise 400MB.
* Evaluated at runtime to support dynamic configuration changes.
* @returns {number} Maximum file size in bytes, or -1 for no limit
*/
function MAX_FILE_SIZE() {
return cds.env.requires?.attachments?.scan == false ? -1 : 419430400 //400 MB in bytes
}
const DEFAULT_MAX_FILE_SIZE = 419430400 // 400 MB in bytes

/**
* Converts a byte size string into the corresponding number.
Expand All @@ -515,7 +507,7 @@ function sizeInBytes(size, target) {
LOG.warn(
`Could not determine the maximum byte size for the content of ${target}, falling back to default.`,
)
return MAX_FILE_SIZE()
return DEFAULT_MAX_FILE_SIZE
}

if (typeof size === "number") {
Expand All @@ -527,7 +519,7 @@ function sizeInBytes(size, target) {
LOG.warn(
`Could not determine the maximum byte size for the content of ${target}, falling back to default.`,
)
return MAX_FILE_SIZE()
return DEFAULT_MAX_FILE_SIZE
}

const unitMatches = size.toUpperCase().match(/([KMGTPEZ]I?)?B$/)
Expand All @@ -538,7 +530,7 @@ function sizeInBytes(size, target) {
LOG.warn(
`Could not determine the maximum byte size for the content of ${target}, falling back to default.`,
)
return MAX_FILE_SIZE()
return DEFAULT_MAX_FILE_SIZE
}

return value * multipliers[unit]
Expand Down Expand Up @@ -917,7 +909,7 @@ module.exports = {
checkMimeTypeMatch,
traverseEntity,
buildBackAssocChain,
MAX_FILE_SIZE,
DEFAULT_MAX_FILE_SIZE,
getAttachmentKind,
handleDuplicates,
createSizeCheckHandler,
Expand Down
25 changes: 9 additions & 16 deletions tests/unit/unitTests.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const {
getObjectStoreCredentials,
fetchToken,
sizeInBytes,
MAX_FILE_SIZE,
DEFAULT_MAX_FILE_SIZE,
validateServiceManagerCredentials,
} = require("../../lib/helper")

Expand Down Expand Up @@ -117,12 +117,8 @@ describe("fetchToken", () => {
})

describe("max attachment size", () => {
test("should return 400MB in normal scenario", () => {
expect(MAX_FILE_SIZE()).toEqual(400 * 1024 * 1024)
})
test("should return -1 when scan is disabled", () => {
cds.env.requires.attachments.scan = false
expect(MAX_FILE_SIZE()).toEqual(-1)
test("DEFAULT_MAX_FILE_SIZE is 400MB", () => {
expect(DEFAULT_MAX_FILE_SIZE).toEqual(400 * 1024 * 1024)
})
})

Expand All @@ -139,15 +135,12 @@ describe("size to byte converter", () => {
expect(sizeInBytes(1234)).toEqual(1234)
})

test("conversion of size string returns default MAX_FILE_SIZE if no size could be determined", () => {
// sizeInBytes returns MAX_FILE_SIZE (400MB = 419430400 bytes) as a safe default
// when the size cannot be determined
const MAX_FILE_SIZE = 419430400 // 400MB in bytes
expect(sizeInBytes("ABCDEFG")).toEqual(MAX_FILE_SIZE)

expect(sizeInBytes(undefined)).toEqual(MAX_FILE_SIZE)

expect(sizeInBytes({ $edmJson: "Dummy Value" })).toEqual(MAX_FILE_SIZE)
test("conversion of size string returns DEFAULT_MAX_FILE_SIZE if no size could be determined", () => {
expect(sizeInBytes("ABCDEFG")).toEqual(DEFAULT_MAX_FILE_SIZE)
expect(sizeInBytes(undefined)).toEqual(DEFAULT_MAX_FILE_SIZE)
expect(sizeInBytes({ $edmJson: "Dummy Value" })).toEqual(
DEFAULT_MAX_FILE_SIZE,
)
})
})

Expand Down
Loading