Describe the bug
The generic handlers registered by handle_attachments for every application service crash on ordinary, non-attachment writes when the query's target is expressed as a string (entity name) rather than a { ref: [...] } object.
Two conditions in lib/plugin.js (v3.12.2 and current main) dereference .ref unconditionally:
// before(["CREATE","UPDATE"]) — ~line 492
if (req.query?.INSERT?.into.ref.length > 1) {
// before(["DELETE","CANCEL"]) — ~line 574
if (req.query?.DELETE?.from?.ref.length > 1) {
When into / from is a string (e.g. from INSERT.into('My.Entity') / DELETE.from('My.Entity') — a documented, valid form per capire › cds.ql), .ref is undefined, so .ref.length throws:
TypeError: Cannot read properties of undefined (reading 'length')
Two things make this worse:
- These two blocks have no
isAttachmentsEntity / hasAttachmentsComposition guard (unlike every other handler in the impl), so they run for all CREATE/UPDATE/DELETE across the app — including entities that have nothing to do with attachments.
- The code is internally inconsistent: the DELETE line already guards
from?. but not ref?.; the INSERT line guards neither into?. nor ref?..
This is distinct from #429 ("Remove wrong req.query assumption"), which fixed the outer req.query being absent. That fix shipped in 3.12.0, but the inner into.ref / from.ref deref remains in 3.12.2 and on main.
Real-world impact: this is deploy-blocking for us. A startup data-replication step writes federated data to local entities using the string-form target (UPSERT(rows).into('<entity name>')); with @cap-js/attachments enabled, the unguarded handler trips on these plain inserts and the boot/deploy fails. We currently work around it with a patch-package patch adding the optional chaining.
To Reproduce
Steps to reproduce the behavior:
-
A CAP (Node.js) project with @cap-js/attachments@3.12.2 enabled (cds.requires.attachments), containing at least one entity with Composition of many Attachments (so the plugin activates) plus any plain entity:
using { Attachments } from '@cap-js/attachments';
entity Incidents { key ID: UUID; attachments: Composition of many Attachments; }
entity Books { key ID: Integer; title: String; }
service MyService {
entity Incidents as projection on Incidents;
entity Books as projection on Books;
}
-
Issue a write whose target is the string form, dispatched through the service:
const srv = await cds.connect.to('MyService');
await srv.run( INSERT.into('MyService.Books').entries({ ID: 1, title: 'x' }) );
// and/or
await srv.run( DELETE.from('MyService.Books').where({ ID: 1 }) );
-
Observe the crash thrown from node_modules/@cap-js/attachments/lib/plugin.js at the INSERT?.into.ref.length line (~492) and, for the delete, the DELETE?.from?.ref.length line (~574):
TypeError: Cannot read properties of undefined (reading 'length')
Expected behavior
A string-form (ref-less) target is valid CQN and should be tolerated. The handler should treat "no .ref / not a multi-segment path" as "not a child-entity write" and simply skip its composition check, rather than throwing:
if (req.query?.INSERT?.into?.ref?.length > 1) { … }
if (req.query?.DELETE?.from?.ref?.length > 1) { … }
[ ] is it a regression issue? — No (present since the composition-limit handlers were introduced; #429 fixed a related but different outer-req.query crash).
Environment
@cap-js/attachments: 3.12.2
@sap/cds: 9.x
- Node.js: 22
Describe the bug
The generic handlers registered by
handle_attachmentsfor every application service crash on ordinary, non-attachment writes when the query's target is expressed as a string (entity name) rather than a{ ref: [...] }object.Two conditions in
lib/plugin.js(v3.12.2 and currentmain) dereference.refunconditionally:When
into/fromis a string (e.g. fromINSERT.into('My.Entity')/DELETE.from('My.Entity')— a documented, valid form per capire › cds.ql),.refisundefined, so.ref.lengththrows:Two things make this worse:
isAttachmentsEntity/hasAttachmentsCompositionguard (unlike every other handler in the impl), so they run for all CREATE/UPDATE/DELETE across the app — including entities that have nothing to do with attachments.from?.but notref?.; the INSERT line guards neitherinto?.norref?..This is distinct from #429 ("Remove wrong req.query assumption"), which fixed the outer
req.querybeing absent. That fix shipped in 3.12.0, but the innerinto.ref/from.refderef remains in 3.12.2 and onmain.Real-world impact: this is deploy-blocking for us. A startup data-replication step writes federated data to local entities using the string-form target (
UPSERT(rows).into('<entity name>')); with@cap-js/attachmentsenabled, the unguarded handler trips on these plain inserts and the boot/deploy fails. We currently work around it with apatch-packagepatch adding the optional chaining.To Reproduce
Steps to reproduce the behavior:
A CAP (Node.js) project with
@cap-js/attachments@3.12.2enabled (cds.requires.attachments), containing at least one entity withComposition of many Attachments(so the plugin activates) plus any plain entity:Issue a write whose target is the string form, dispatched through the service:
Observe the crash thrown from
node_modules/@cap-js/attachments/lib/plugin.jsat theINSERT?.into.ref.lengthline (~492) and, for the delete, theDELETE?.from?.ref.lengthline (~574):Expected behavior
A string-form (ref-less) target is valid CQN and should be tolerated. The handler should treat "no
.ref/ not a multi-segment path" as "not a child-entity write" and simply skip its composition check, rather than throwing:[ ] is it a regression issue? — No (present since the composition-limit handlers were introduced; #429 fixed a related but different outer-
req.querycrash).Environment
@cap-js/attachments: 3.12.2@sap/cds: 9.x