| Version | Supported |
|---|---|
| 1.14.x | Yes |
| 1.13.x | Yes |
| 1.12.x | Yes |
| 1.11.x | Yes |
| 1.10.x | Yes |
| 1.9.x | Yes |
| 1.8.x | Yes |
| 1.7.x | Yes |
| 1.6.x | Yes |
| 1.5.x | Yes |
| 1.4.x | Yes |
| 1.3.x | Yes |
| 1.2.x | Yes |
| 1.1.x | Yes |
| 1.0.x | No — upgrade to the latest 1.x |
| < 1.0 | No |
1.0.0 cannot be installed on any Angular newer than 17 and shipped a dependency fault that breaks dependency injection. If you are on it, upgrading is the fix.
Please report privately via GitHub security advisories rather than opening a public issue.
Include the affected package and version, what an attacker can achieve, and a reproduction if you have one. You can expect an acknowledgement within a few days; this is a small project, so please allow reasonable time before disclosing publicly.
Read this before filing — two behaviours look like security features and are not.
Permissions and masking are presentational. EntityPermissions (view/edit/delete)
and maskData control what the browser renders. A masked field displays XXXXXXXXX while the
real value remains in the form control and is included in the submitted record. A field hidden
by permissions.view was still sent to the browser by whatever supplied the record.
They are a UI convenience, not an access-control boundary. Authorize on the server: do not send a user data they may not see, and re-check every permission when a submitted record reaches your API. A report that a masked value is readable in DevTools is expected behaviour, documented in the README and in the code.
Configs are data, and are treated as such. The rules engine interprets enumerated
operators over structured conditions — there is no eval, no new Function, and no template
compilation of config strings anywhere in the packages. Dot-paths from config (refererField,
tab ids) refuse __proto__, constructor and prototype on both read and write, so a config
cannot reach an object's prototype.
If you find a way for config content to execute code or escape those guards, that is a real vulnerability and we want to hear about it.
The other packages run in a browser tab or in a build step. This one accepts a file over the network, which changes the threat model rather than extending it — so it gets its own section.
Every limit below has a finite default, and each is enforced during streaming rather than after. A guard that runs once the file is in memory has already lost.
| Threat | Guard |
|---|---|
| Upload exhaustion | maxBytes, counted as bytes arrive; the body is abandoned and closed on breach |
A .csv that is really a zip |
Format is decided by magic bytes, never by the client-controlled extension |
Zip bomb (an .xlsx is a zip) |
maxUncompressedBytes, maxCompressionRatio, maxZipEntries, per-entry size — all checked as entries inflate |
| XML entity expansion / XXE | The parser refuses an undefined entity outright and does not process an internal DTD subset; asserted with crafted files rather than assumed |
| Row / column / cell floods | maxRows, maxColumns, maxCellLength |
| Formula injection in generated files | Every written cell goes through escapeFormula, and the xlsx writer's output is checked for formula elements |
Path traversal via :entity |
A lookup key into the configs you supplied. Never a path segment, never interpolated |
Content-Disposition header injection |
Filenames are derived from the config's entity and sanitised; a client-supplied name never reaches a header |
| Prototype pollution via a posted plan | setRecordValue refuses __proto__ and constructor.prototype; a posted hostile plan is a test, not an inherited assurance |
| A malformed or hostile plan | validateMappingPlan runs before a single row is read |
| Multipart abuse | Field count, field-name length, field value size and file count are all capped |
| Stalled uploads (slowloris) | Idle and total request timeouts, both of which close the connection |
| Information disclosure | The error envelope carries a code and a message this library wrote. No stack, no filesystem path, no parser internals |
Authentication, authorization and concurrency limiting are yours. The router is mounted inside your app, behind your middleware. Nothing in it checks who is asking, and nothing in it limits how many imports run at once. A report that an unauthenticated request reaches the import endpoint is a report about the app it was mounted in.
An import is not transactional, and a retry will double-write. A stream that fails at row
30,000 has already written 29,999 records. Over HTTP a retry is likely rather than possible —
a client, a proxy or a user will send the same file again — so onImport must be
idempotent. This library does not deduplicate: an EntityFormConfig has no natural-key
concept for it to deduplicate on. POST /:entity/validate runs the identical pipeline and
writes nothing, which is the intended way to find problems before anything is stored.
validators.pattern became a server concern. It is a config-supplied regular expression
that core compiles and runs against cell text. In a browser a catastrophic backtrack costs the
user their own tab; on a server the cell content is attacker-chosen and the cost is your CPU.
maxCellLength bounds the input and therefore bounds the blow-up, but a pattern authored
without that in mind is now a denial-of-service surface. If you author patterns, review them
for catastrophic backtracking.
A published template is a file another program executes. Cells are escaped and the writer's output is checked, but the content comes from your config's labels. A label you would not paste into a spreadsheet is a label you should not put in a config.
Releases are published from CI by the Release workflow using npm trusted publishing (OIDC):
there is no long-lived publish token, and each tarball carries a provenance attestation linking
it to the commit and workflow that produced it. You can verify it with npm audit signatures.
Three of the four published packages declare no runtime dependencies at all:
| Package | Runtime dependencies |
|---|---|
@dynamic-entity/core |
none |
ngx-dynamic-entity |
none |
ngx-dynamic-entity-builder |
none |
@dynamic-entity/server |
busboy, exceljs |
So an advisory against this repository's node_modules is almost always an advisory against
the build, not against anything installed alongside your application. express is an
optional peer — you bring your own — and is not in the table for that reason.
npm audit is not expected to read zero here, and a residue with no written reason is
indistinguishable from one nobody looked at. What is currently accepted, and why:
uuid< 11.1.1, reached throughexceljs— GHSA-w5hq-g745-h8pq, a missing buffer bounds check inv3/v5/v6when abufargument is supplied.exceljscallsv4()and passes no buffer (lib/xlsx/xform/sheet/cf-ext/cf-rule-ext-xform.js), so the affected code is not reachable from this package.exceljsitself carries no advisory: it is flagged only for depending onuuid, and npm's suggested remedy is a downgrade toexceljs@3.4.0— a version that predates the dependency, and a worse position than the one it replaces. Bothexceljsandsockjspinuuid@^8, so an override cannot move them without forcing a major version of a transitive dependency on a library that was never tested against it. The reachability argument is the stronger guarantee; taken, and recorded here.- Angular build tooling (
@angular-devkit/build-angular,webpack-dev-server,sockjs,esbuild,qs) — development scope, pinned by the Angular version this repo targets, and never present in a published tarball. Theesbuildadvisory is specific to running a development server on Windows, which no consumer of these packages does on their account.
Everything with a non-breaking fix is taken rather than accepted; the list above is what is left after that.