upload-safety 0.0.0-stage → 0.2.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.
Files changed (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +188 -2
  3. package/dist/check-upload.d.ts +23 -0
  4. package/dist/check-upload.d.ts.map +1 -0
  5. package/dist/check-upload.js +22 -0
  6. package/dist/check-upload.js.map +1 -0
  7. package/dist/excel-macro-sheets.d.ts +12 -0
  8. package/dist/excel-macro-sheets.d.ts.map +1 -0
  9. package/dist/excel-macro-sheets.js +51 -0
  10. package/dist/excel-macro-sheets.js.map +1 -0
  11. package/dist/heif-metadata.d.ts +14 -0
  12. package/dist/heif-metadata.d.ts.map +1 -0
  13. package/dist/heif-metadata.js +216 -0
  14. package/dist/heif-metadata.js.map +1 -0
  15. package/dist/index.d.ts +7 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +7 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/ole-streams.d.ts +15 -0
  20. package/dist/ole-streams.d.ts.map +1 -0
  21. package/dist/ole-streams.js +192 -0
  22. package/dist/ole-streams.js.map +1 -0
  23. package/dist/pdf-active-content.d.ts +3 -0
  24. package/dist/pdf-active-content.d.ts.map +1 -0
  25. package/dist/pdf-active-content.js +130 -0
  26. package/dist/pdf-active-content.js.map +1 -0
  27. package/dist/pdf-encryption.d.ts +70 -0
  28. package/dist/pdf-encryption.d.ts.map +1 -0
  29. package/dist/pdf-encryption.js +576 -0
  30. package/dist/pdf-encryption.js.map +1 -0
  31. package/dist/photo-metadata.d.ts +14 -0
  32. package/dist/photo-metadata.d.ts.map +1 -0
  33. package/dist/photo-metadata.js +307 -0
  34. package/dist/photo-metadata.js.map +1 -0
  35. package/dist/powerpoint-vba.d.ts +14 -0
  36. package/dist/powerpoint-vba.d.ts.map +1 -0
  37. package/dist/powerpoint-vba.js +122 -0
  38. package/dist/powerpoint-vba.js.map +1 -0
  39. package/dist/text-content.d.ts +7 -0
  40. package/dist/text-content.d.ts.map +1 -0
  41. package/dist/text-content.js +83 -0
  42. package/dist/text-content.js.map +1 -0
  43. package/dist/upload-allowlist.d.ts +48 -0
  44. package/dist/upload-allowlist.d.ts.map +1 -0
  45. package/dist/upload-allowlist.js +560 -0
  46. package/dist/upload-allowlist.js.map +1 -0
  47. package/dist/xml-markup.d.ts +12 -0
  48. package/dist/xml-markup.d.ts.map +1 -0
  49. package/dist/xml-markup.js +83 -0
  50. package/dist/xml-markup.js.map +1 -0
  51. package/dist/zip-entries.d.ts +34 -0
  52. package/dist/zip-entries.d.ts.map +1 -0
  53. package/dist/zip-entries.js +222 -0
  54. package/dist/zip-entries.js.map +1 -0
  55. package/package.json +62 -5
  56. package/src/check-upload.ts +50 -0
  57. package/src/excel-macro-sheets.ts +52 -0
  58. package/src/heif-metadata.ts +232 -0
  59. package/src/index.ts +6 -0
  60. package/src/ole-streams.ts +226 -0
  61. package/src/pdf-active-content.ts +138 -0
  62. package/src/pdf-encryption.ts +629 -0
  63. package/src/photo-metadata.ts +325 -0
  64. package/src/powerpoint-vba.ts +136 -0
  65. package/src/text-content.ts +85 -0
  66. package/src/upload-allowlist.ts +657 -0
  67. package/src/xml-markup.ts +85 -0
  68. package/src/zip-entries.ts +255 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Simon Halimonov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,189 @@
1
- # Temporary Holding Version
1
+ # upload-safety
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm](https://img.shields.io/npm/v/upload-safety?color=cb3837)](https://www.npmjs.com/package/upload-safety)
4
+ [![CI](https://github.com/simulieren/upload-safety/actions/workflows/ci.yml/badge.svg)](https://github.com/simulieren/upload-safety/actions/workflows/ci.yml)
5
+ [![license](https://img.shields.io/github/license/simulieren/upload-safety)](LICENSE)
6
+ [![node](https://img.shields.io/badge/node-%E2%89%A522-339933)](package.json)
7
+ [![dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](package.json)
8
+ [![types](https://img.shields.io/badge/types-TypeScript-3178c6)](src/index.ts)
9
+
10
+ Server-side upload checks for Node that read the file, not just its name. An upload passes only when its extension, its declared MIME type and its bytes agree, and when it carries nothing that runs on its own: no macros, embedded objects, DDE fields, PDF scripts or CSV formulas. Photos, including iPhone HEIC, lose their GPS position and other metadata on the way through.
11
+
12
+ For apps that take CVs, invoices, contracts and photos from strangers and hand them to colleagues who open them in Office or Acrobat.
13
+
14
+ ```ts
15
+ const result = checkUpload( { filename, mimeType, bytes }, { maxBytes: 10 * 1024 * 1024 } );
16
+ if ( ! result.ok ) {
17
+ // result.reason: 'macros', 'encrypted', 'content-mismatch', ...
18
+ }
19
+ ```
20
+
21
+ ## What it catches
22
+
23
+ | Upload | Result |
24
+ | - | - |
25
+ | `cv.pdf` that is a Windows executable | refused, `content-mismatch` |
26
+ | `report.docx` with a VBA project, also renamed or hidden behind a content type | refused, `macros` |
27
+ | `invoice.docx` with an embedded OLE package that runs a file on double click | refused, `macros` |
28
+ | `letter.docx` with a `DDEAUTO` field, also split across runs or spelled with `QUOTE` | refused, `macros` |
29
+ | `template.docx` that loads a remote `.dotm` on opening | refused, `macros` |
30
+ | `form.pdf` with JavaScript, a launch action or an attachment, also in compressed object streams | refused, `macros` |
31
+ | `budget.xls` with a very hidden Excel 4.0 macro sheet | refused, `macros` |
32
+ | `notes.odt` with a Basic macro or an event listener | refused, `macros` |
33
+ | `export.csv` with `=HYPERLINK(...)` or `=cmd\|' /C calc'!A0` in a cell | refused, `macros` |
34
+ | `contract.docx` saved with a password | refused, `encrypted` |
35
+ | `data.xlsx` that is a zip bomb | refused, `content-mismatch` |
36
+ | `holiday.jpg` with GPS coordinates | accepted, EXIF, XMP, IPTC and comments removed, orientation kept |
37
+ | `IMG_0042.heic` from an iPhone | accepted, EXIF and XMP overwritten in place, pixels unchanged |
38
+ | `../../CON.pdf`, a plain PDF | accepted, stored as `_CON.pdf` |
39
+
40
+ Every check fails closed: a part it cannot read is a refusal, not a pass.
41
+
42
+ ## Why
43
+
44
+ - **Zero runtime dependencies.** Only `node:zlib` and `node:crypto`. Nothing to audit but this package.
45
+ - **Typed reasons.** Each refusal is one of seven string literals, so a `Record<UploadRejection, string>` of messages is checked by the compiler.
46
+ - **Fast enough to run inline.** Synchronous; 647 real documents up to 20 MB each took at most 65 ms on a laptop.
47
+ - **Tested against crafted files.** 235 tests build malformed ZIPs, compound files and PDFs byte by byte, next to real Office and PDF files.
48
+
49
+ ## Install
50
+
51
+ ```sh
52
+ npm install upload-safety
53
+ ```
54
+
55
+ Node 22 or later, ESM.
56
+
57
+ ## Usage
58
+
59
+ ### Web `Request` (Next.js route handlers, Hono, Remix, Bun)
60
+
61
+ ```ts
62
+ import { checkUpload, type UploadRejection } from 'upload-safety';
63
+
64
+ export async function POST( request: Request ) {
65
+ const file = ( await request.formData() ).get( 'file' );
66
+ if ( ! ( file instanceof File ) ) {
67
+ return Response.json( { error: 'No file.' }, { status: 400 } );
68
+ }
69
+
70
+ const result = checkUpload(
71
+ { filename: file.name, mimeType: file.type, bytes: new Uint8Array( await file.arrayBuffer() ) },
72
+ { maxBytes: 10 * 1024 * 1024, allowedExtensions: [ 'pdf', 'docx', 'jpg', 'jpeg', 'png' ] }
73
+ );
74
+ if ( ! result.ok ) {
75
+ return Response.json( { error: MESSAGES[ result.reason ] }, { status: 422 } );
76
+ }
77
+
78
+ // Store result.bytes under result.filename with result.mimeType.
79
+ return Response.json( { name: result.filename } );
80
+ }
81
+
82
+ const MESSAGES: Record<UploadRejection, string> = {
83
+ 'empty': 'The file is empty.',
84
+ 'too-large': 'The file is too large.',
85
+ 'type-not-allowed': 'This file type is not allowed.',
86
+ 'mime-mismatch': 'The file type does not match its extension.',
87
+ 'content-mismatch': 'The file content does not match its extension.',
88
+ 'macros': 'Files with macros or other active content are not allowed.',
89
+ 'encrypted': 'Password-protected files cannot be checked.',
90
+ };
91
+ ```
92
+
93
+ ### Express with multer
94
+
95
+ ```ts
96
+ import express from 'express';
97
+ import multer from 'multer';
98
+ import { checkUpload } from 'upload-safety';
99
+
100
+ const MAX_BYTES = 10 * 1024 * 1024;
101
+ // Memory storage hands over the bytes; the limit stops a large body before it is buffered.
102
+ const upload = multer( { storage: multer.memoryStorage(), limits: { fileSize: MAX_BYTES } } );
103
+ const app = express();
104
+
105
+ app.post( '/upload', upload.single( 'file' ), ( req, res ) => {
106
+ if ( ! req.file ) {
107
+ return res.status( 400 ).json( { error: 'No file.' } );
108
+ }
109
+ const result = checkUpload(
110
+ { filename: req.file.originalname, mimeType: req.file.mimetype, bytes: req.file.buffer },
111
+ { maxBytes: MAX_BYTES }
112
+ );
113
+ if ( ! result.ok ) {
114
+ return res.status( 422 ).json( { error: result.reason } );
115
+ }
116
+ // Store result.bytes under result.filename with result.mimeType.
117
+ res.json( { name: result.filename } );
118
+ } );
119
+ ```
120
+
121
+ ### In the browser
122
+
123
+ `acceptAttribute()` gives the matching value for the file picker. It only filters the dialog; the server check is what counts.
124
+
125
+ ```tsx
126
+ <input type="file" accept={ acceptAttribute( [ 'pdf', 'docx' ] ) } />
127
+ ```
128
+
129
+ ## Formats
130
+
131
+ | Format | Extensions | Checked beyond the magic number |
132
+ | - | - | - |
133
+ | PDF | `pdf` | JavaScript, launch, form submit and import actions, links to other files (`GoToR`, `GoToE`), multimedia, XFA forms and embedded files, also inside compressed object streams; encryption |
134
+ | Office (OOXML) | `docx` `xlsx` `pptx` | Required parts; VBA and ActiveX parts; macro content types and relationships; embedded and linked OLE objects; altChunk imports; subdocuments, frames and remote templates; DDE fields in Word and DDE links in Excel; encryption |
135
+ | Office (legacy) | `doc` `xls` `ppt` | OLE directory; VBA storages; embedded OLE objects; Excel 4.0 macro sheets; DDE and OLE links in Excel; PowerPoint VBA and object records; encryption flags |
136
+ | OpenDocument | `odt` `ods` `odp` | The `mimetype` entry; Basic and script modules; embedded OLE objects; event listeners; links to macros; encrypted manifests |
137
+ | Photos | `jpg` `jpeg` `png` `webp` | Structure; EXIF (GPS included), XMP, IPTC, comments and any other metadata segment or chunk are removed, the orientation is kept |
138
+ | HEIF photos | `heic` `heif` `avif` | Brands; EXIF and XMP items are overwritten in place, since dropping them would shift the offsets the file is built on. The orientation lives in the image properties and stays. Image sequences, such as animated AVIF, are refused |
139
+ | Text | `csv` `txt` | No binary data (UTF-8, UTF-16 with a byte order mark, or a Windows code page); in CSV no cell that starts like a formula (`=`, `+`, `-`, `@`) and calls a function or DDE. Numbers such as `-5` or `+49 (30) 1234` pass |
140
+
141
+ ZIP and OLE reading is capped against zip bombs and malformed sector chains. Every format is in the default list; pass `allowedExtensions` to accept fewer. A PDF that asks for a password is refused. One that only restricts permissions opens without a password and passes, unless its object streams are encrypted, since then they cannot be checked.
142
+
143
+ ## API
144
+
145
+ `checkUpload` validates the descriptor, inspects the bytes, strips photo metadata and returns the sanitized file name and the canonical MIME type. The parts are exported on their own too:
146
+
147
+ | Export | Does |
148
+ | - | - |
149
+ | `checkUpload( { filename, mimeType, bytes }, options )` | Every check below; `{ ok: true, extension, filename, mimeType, bytes }` or `{ ok: false, reason }` |
150
+ | `validateUploadDescriptor( { filename, mimeType, size }, options )` | Size, extension and MIME checks before reading the body |
151
+ | `inspectFileContent( extension, bytes )` | `'valid' \| 'macros' \| 'encrypted' \| 'mismatch'` |
152
+ | `stripPhotoMetadata( extension, bytes )` | Cleaned bytes, the input for non-photos, `null` for a broken photo |
153
+ | `sanitizeFilename( name )` | Drops path parts, unsafe characters and trailing dots, renames Windows device names such as `CON`, caps the length at 200 UTF-8 bytes |
154
+ | `getCanonicalMimeType( extension )` | The MIME type to store |
155
+ | `acceptAttribute( extensions? )` | Value for `<input type="file" accept>` |
156
+ | `isEncryptedPdf( buffer )` | PDF encryption check alone |
157
+
158
+ `options` takes `maxBytes` and, to narrow the built-in list, `allowedExtensions`. The reason `macros` covers all active content: VBA and Excel 4.0 macros, embedded OLE objects, DDE, event listeners and PDF scripts and attachments.
159
+
160
+ ## Limits
161
+
162
+ These checks are not an antivirus scan. They refuse active content and anything they cannot read. They do not judge whether the data in an otherwise plain file is malicious, and they do not look inside a document embedded in another one, such as the workbook behind a Word chart. DDE is not looked for in legacy `.doc` files, nor in an `.xlsx` cell formula that names a DDE source without an external link part.
163
+
164
+ The ZIP limits (`MAX_ZIP_UNCOMPRESSED_BYTES`, `MAX_ZIP_ENTRY_UNCOMPRESSED_BYTES`) are set to 100 MB. If a virus scanner runs after these checks, keep them at or below its own limits: ClamAV, for one, reports content past `MaxScanSize` and `MaxFileSize` as clean.
165
+
166
+ ## Before and after the checks
167
+
168
+ The library checks the bytes it is handed. The rest of the upload path is up to you:
169
+
170
+ - **Limit the request body before reading it.** `maxBytes` is checked after the whole file is in memory, so cap the body in your server or proxy first.
171
+ - **Serve stored files as downloads.** Send `Content-Disposition: attachment` and `X-Content-Type-Options: nosniff`, and use the canonical `result.mimeType`, not the one the client declared. Serving uploads from a separate origin keeps a file that slips through away from your cookies.
172
+ - **Limit image dimensions before resizing.** A small JPEG or PNG can declare a huge size. These checks do not decode images, so cap width and height in whatever makes thumbnails (sharp's `limitInputPixels`, ImageMagick's `policy.xml`).
173
+ - **Expect metadata in documents.** Only JPEG and PNG uploads are stripped. Author names in Office and PDF files, and EXIF in images inside documents, are kept.
174
+ - **Run a virus scanner if you need one.** These checks refuse active content; they do not recognise malware in data.
175
+
176
+ ## Development
177
+
178
+ ```sh
179
+ npm install
180
+ npm test
181
+ npm run typecheck
182
+ npm run build # dist/ with .d.ts
183
+ ```
184
+
185
+ Found a way past a check? Please report it privately, as [SECURITY.md](SECURITY.md) describes.
186
+
187
+ ## License
188
+
189
+ [MIT](LICENSE)
@@ -0,0 +1,23 @@
1
+ import { type AllowedExtension, type DescriptorRejection, type UploadOptions } from './upload-allowlist.js';
2
+ export type UploadRejection = DescriptorRejection | 'macros' | 'encrypted' | 'content-mismatch';
3
+ export interface UploadInput {
4
+ filename: string;
5
+ /** The MIME type the client declared; empty when it sent none. */
6
+ mimeType: string;
7
+ bytes: Uint8Array;
8
+ }
9
+ export type UploadCheck = {
10
+ ok: true;
11
+ extension: AllowedExtension;
12
+ filename: string;
13
+ /** The canonical MIME type for the extension, not the declared one. */
14
+ mimeType: string;
15
+ /** The bytes to store; photos come without their metadata. */
16
+ bytes: Uint8Array;
17
+ } | {
18
+ ok: false;
19
+ reason: UploadRejection;
20
+ };
21
+ /** Runs every check on an upload and returns what is safe to store. */
22
+ export declare function checkUpload({ filename, mimeType, bytes }: UploadInput, options: UploadOptions): UploadCheck;
23
+ //# sourceMappingURL=check-upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-upload.d.ts","sourceRoot":"","sources":["../src/check-upload.ts"],"names":[],"mappings":"AACA,OAAO,EAIN,KAAK,gBAAgB,EACrB,KAAK,mBAAmB,EACxB,KAAK,aAAa,EAClB,MAAM,uBAAuB,CAAC;AAE/B,MAAM,MAAM,eAAe,GAAG,mBAAmB,GAAG,QAAQ,GAAG,WAAW,GAAG,kBAAkB,CAAC;AAEhG,MAAM,WAAW,WAAW;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,MAAM,WAAW,GACpB;IACD,EAAE,EAAE,IAAI,CAAC;IACT,SAAS,EAAE,gBAAgB,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,KAAK,EAAE,UAAU,CAAC;CAClB,GACC;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,CAAC;AAE1C,uEAAuE;AACvE,wBAAgB,WAAW,CAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,GAAI,WAAW,CAiB7G"}
@@ -0,0 +1,22 @@
1
+ import { stripPhotoMetadata } from './photo-metadata.js';
2
+ import { getCanonicalMimeType, inspectFileContent, validateUploadDescriptor, } from './upload-allowlist.js';
3
+ /** Runs every check on an upload and returns what is safe to store. */
4
+ export function checkUpload({ filename, mimeType, bytes }, options) {
5
+ const descriptor = validateUploadDescriptor({ filename, mimeType, size: bytes.byteLength }, options);
6
+ if (!descriptor.ok) {
7
+ return descriptor;
8
+ }
9
+ const { extension, sanitizedFilename } = descriptor;
10
+ const verdict = inspectFileContent(extension, bytes);
11
+ if (verdict === 'macros' || verdict === 'encrypted') {
12
+ return { ok: false, reason: verdict };
13
+ }
14
+ const stored = verdict === 'valid' ? stripPhotoMetadata(extension, bytes) : null;
15
+ if (!stored) {
16
+ return { ok: false, reason: 'content-mismatch' };
17
+ }
18
+ return {
19
+ ok: true, extension, filename: sanitizedFilename, mimeType: getCanonicalMimeType(extension), bytes: stored,
20
+ };
21
+ }
22
+ //# sourceMappingURL=check-upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-upload.js","sourceRoot":"","sources":["../src/check-upload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EACN,oBAAoB,EACpB,kBAAkB,EAClB,wBAAwB,GAIxB,MAAM,uBAAuB,CAAC;AAuB/B,uEAAuE;AACvE,MAAM,UAAU,WAAW,CAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAe,EAAE,OAAsB;IAC9F,MAAM,UAAU,GAAG,wBAAwB,CAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,UAAU,EAAE,EAAE,OAAO,CAAE,CAAC;IACvG,IAAK,CAAE,UAAU,CAAC,EAAE,EAAG,CAAC;QACvB,OAAO,UAAU,CAAC;IACnB,CAAC;IACD,MAAM,EAAE,SAAS,EAAE,iBAAiB,EAAE,GAAG,UAAU,CAAC;IACpD,MAAM,OAAO,GAAG,kBAAkB,CAAE,SAAS,EAAE,KAAK,CAAE,CAAC;IACvD,IAAK,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,WAAW,EAAG,CAAC;QACvD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IACvC,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,kBAAkB,CAAE,SAAS,EAAE,KAAK,CAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IACnF,IAAK,CAAE,MAAM,EAAG,CAAC;QAChB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAClD,CAAC;IACD,OAAO;QACN,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,iBAAiB,EAAE,QAAQ,EAAE,oBAAoB,CAAE,SAAS,CAAE,EAAE,KAAK,EAAE,MAAM;KAC5G,CAAC;AACH,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Finds Excel 4.0 macro sheets and VBA modules in the globals of a BIFF5/BIFF8
3
+ * Workbook stream, which list every sheet in a BoundSheet8 record [MS-XLS 2.4.28],
4
+ * DDE and OLE links, which a SupBook record introduces [MS-XLS 2.4.271], and
5
+ * encryption, which a FILEPASS record announces [MS-XLS 2.4.117].
6
+ */
7
+ /**
8
+ * Whether the globals list a macro sheet or a DDE or OLE link, or encrypt the rest. The walk ends
9
+ * at their EOF, at FILEPASS or at a record that overruns the stream, which Excel could not read either.
10
+ */
11
+ export declare function inspectExcelGlobals(workbook: Buffer): 'macros' | 'encrypted' | 'valid';
12
+ //# sourceMappingURL=excel-macro-sheets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"excel-macro-sheets.d.ts","sourceRoot":"","sources":["../src/excel-macro-sheets.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAkBH;;;GAGG;AACH,wBAAgB,mBAAmB,CAAE,QAAQ,EAAE,MAAM,GAAI,QAAQ,GAAG,WAAW,GAAG,OAAO,CAwBxF"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Finds Excel 4.0 macro sheets and VBA modules in the globals of a BIFF5/BIFF8
3
+ * Workbook stream, which list every sheet in a BoundSheet8 record [MS-XLS 2.4.28],
4
+ * DDE and OLE links, which a SupBook record introduces [MS-XLS 2.4.271], and
5
+ * encryption, which a FILEPASS record announces [MS-XLS 2.4.117].
6
+ */
7
+ const RECORD_HEADER_SIZE = 4;
8
+ const BOUND_SHEET = 0x0085;
9
+ const END_OF_FILE = 0x000a;
10
+ // Everything after FILEPASS is encrypted, the sheet type included.
11
+ const FILE_PASS = 0x002f;
12
+ /** BoundSheet8 `dt` after lbPlyPos (4 bytes) and hsState (1 byte): 1 macro sheet, 6 VBA module. */
13
+ const SHEET_TYPE_OFFSET = 5;
14
+ const MACRO_SHEET_TYPES = [1, 6];
15
+ // A SupBook names the source of external references. One of a DDE link (a formula such as
16
+ // `=cmd|'/c calc'!A1`) or an OLE link lists no sheets, unlike an external workbook, and carries no
17
+ // marker of the workbook itself or of add-in functions in place of the length of its path.
18
+ const SUP_BOOK = 0x01ae;
19
+ const SUP_BOOK_SIZE = 4;
20
+ const SELF_REFERENCE = 0x0401;
21
+ const ADD_IN_REFERENCE = 0x3a01;
22
+ /**
23
+ * Whether the globals list a macro sheet or a DDE or OLE link, or encrypt the rest. The walk ends
24
+ * at their EOF, at FILEPASS or at a record that overruns the stream, which Excel could not read either.
25
+ */
26
+ export function inspectExcelGlobals(workbook) {
27
+ let offset = 0;
28
+ while (offset + RECORD_HEADER_SIZE <= workbook.length) {
29
+ const type = workbook.readUInt16LE(offset);
30
+ const size = workbook.readUInt16LE(offset + 2);
31
+ const body = offset + RECORD_HEADER_SIZE;
32
+ if (type === FILE_PASS) {
33
+ return 'encrypted';
34
+ }
35
+ if (type === END_OF_FILE) {
36
+ return 'valid';
37
+ }
38
+ if (type === BOUND_SHEET && size > SHEET_TYPE_OFFSET && body + SHEET_TYPE_OFFSET < workbook.length &&
39
+ MACRO_SHEET_TYPES.includes(workbook[body + SHEET_TYPE_OFFSET])) {
40
+ return 'macros';
41
+ }
42
+ if (type === SUP_BOOK && size >= SUP_BOOK_SIZE && body + SUP_BOOK_SIZE <= workbook.length &&
43
+ workbook.readUInt16LE(body) === 0 &&
44
+ ![SELF_REFERENCE, ADD_IN_REFERENCE].includes(workbook.readUInt16LE(body + 2))) {
45
+ return 'macros';
46
+ }
47
+ offset = body + size;
48
+ }
49
+ return 'valid';
50
+ }
51
+ //# sourceMappingURL=excel-macro-sheets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"excel-macro-sheets.js","sourceRoot":"","sources":["../src/excel-macro-sheets.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAC7B,MAAM,WAAW,GAAG,MAAM,CAAC;AAC3B,MAAM,WAAW,GAAG,MAAM,CAAC;AAC3B,mEAAmE;AACnE,MAAM,SAAS,GAAG,MAAM,CAAC;AACzB,mGAAmG;AACnG,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAC5B,MAAM,iBAAiB,GAAG,CAAE,CAAC,EAAE,CAAC,CAAE,CAAC;AACnC,0FAA0F;AAC1F,mGAAmG;AACnG,2FAA2F;AAC3F,MAAM,QAAQ,GAAG,MAAM,CAAC;AACxB,MAAM,aAAa,GAAG,CAAC,CAAC;AACxB,MAAM,cAAc,GAAG,MAAM,CAAC;AAC9B,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEhC;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAE,QAAgB;IACpD,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,OAAQ,MAAM,GAAG,kBAAkB,IAAI,QAAQ,CAAC,MAAM,EAAG,CAAC;QACzD,MAAM,IAAI,GAAG,QAAQ,CAAC,YAAY,CAAE,MAAM,CAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,QAAQ,CAAC,YAAY,CAAE,MAAM,GAAG,CAAC,CAAE,CAAC;QACjD,MAAM,IAAI,GAAG,MAAM,GAAG,kBAAkB,CAAC;QACzC,IAAK,IAAI,KAAK,SAAS,EAAG,CAAC;YAC1B,OAAO,WAAW,CAAC;QACpB,CAAC;QACD,IAAK,IAAI,KAAK,WAAW,EAAG,CAAC;YAC5B,OAAO,OAAO,CAAC;QAChB,CAAC;QACD,IAAK,IAAI,KAAK,WAAW,IAAI,IAAI,GAAG,iBAAiB,IAAI,IAAI,GAAG,iBAAiB,GAAG,QAAQ,CAAC,MAAM;YAClG,iBAAiB,CAAC,QAAQ,CAAE,QAAQ,CAAE,IAAI,GAAG,iBAAiB,CAAE,CAAE,EAAG,CAAC;YACtE,OAAO,QAAQ,CAAC;QACjB,CAAC;QACD,IAAK,IAAI,KAAK,QAAQ,IAAI,IAAI,IAAI,aAAa,IAAI,IAAI,GAAG,aAAa,IAAI,QAAQ,CAAC,MAAM;YACzF,QAAQ,CAAC,YAAY,CAAE,IAAI,CAAE,KAAK,CAAC;YACnC,CAAE,CAAE,cAAc,EAAE,gBAAgB,CAAE,CAAC,QAAQ,CAAE,QAAQ,CAAC,YAAY,CAAE,IAAI,GAAG,CAAC,CAAE,CAAE,EAAG,CAAC;YACxF,OAAO,QAAQ,CAAC;QACjB,CAAC;QACD,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IACtB,CAAC;IACD,OAAO,OAAO,CAAC;AAChB,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Removes EXIF and XMP from a HEIF still image (HEIC, AVIF) without moving a byte. The item
3
+ * locations are byte offsets that dropping data would shift, so the items are overwritten in
4
+ * place instead: EXIF with an empty TIFF block, XMP with spaces. The orientation lives in the
5
+ * `irot` and `imir` properties, which stay. Image sequences (`moov`) are refused.
6
+ */
7
+ /**
8
+ * The HEIF image with its EXIF and XMP items overwritten, or null when its boxes do not read,
9
+ * an item's data cannot be located, or it is an image sequence.
10
+ */
11
+ export declare function stripHeif(content: Buffer): Buffer | null;
12
+ /** The major and compatible brands of a file's leading `ftyp` box, empty when there is none. */
13
+ export declare function readFtypBrands(content: Buffer): string[];
14
+ //# sourceMappingURL=heif-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heif-metadata.d.ts","sourceRoot":"","sources":["../src/heif-metadata.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AA2KH;;;GAGG;AACH,wBAAgB,SAAS,CAAE,OAAO,EAAE,MAAM,GAAI,MAAM,GAAG,IAAI,CAqC1D;AAED,gGAAgG;AAChG,wBAAgB,cAAc,CAAE,OAAO,EAAE,MAAM,GAAI,MAAM,EAAE,CAW1D"}
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Removes EXIF and XMP from a HEIF still image (HEIC, AVIF) without moving a byte. The item
3
+ * locations are byte offsets that dropping data would shift, so the items are overwritten in
4
+ * place instead: EXIF with an empty TIFF block, XMP with spaces. The orientation lives in the
5
+ * `irot` and `imir` properties, which stay. Image sequences (`moov`) are refused.
6
+ */
7
+ /** Bounds far above real files that keep a crafted one cheap. */
8
+ const MAX_BOXES = 10_000;
9
+ const MAX_ITEMS = 100_000;
10
+ const XMP_CONTENT_TYPE = 'application/rdf+xml';
11
+ /** An EXIF item: the offset of the TIFF header (0), then a big-endian TIFF block with an empty IFD0. */
12
+ const EMPTY_EXIF = Buffer.from('00000000' + '4d4d002a00000008' + '0000' + '00000000', 'hex');
13
+ const SPACE = 0x20;
14
+ /** The boxes in `[start, end)`, or null when one runs past it or there are too many. */
15
+ function readBoxes(bytes, start, end) {
16
+ const boxes = [];
17
+ for (let offset = start; offset < end;) {
18
+ if (offset + 8 > end || boxes.length === MAX_BOXES) {
19
+ return null;
20
+ }
21
+ let size = bytes.readUInt32BE(offset);
22
+ let header = 8;
23
+ if (size === 1) {
24
+ const large = offset + 16 <= end ? bytes.readBigUInt64BE(offset + 8) : undefined;
25
+ if (large === undefined || large > BigInt(end - offset)) {
26
+ return null;
27
+ }
28
+ size = Number(large);
29
+ header = 16;
30
+ }
31
+ else if (size === 0) {
32
+ // Runs to the end of the file.
33
+ size = end - offset;
34
+ }
35
+ const type = bytes.toString('latin1', offset + 4, offset + 8);
36
+ header += type === 'uuid' ? 16 : 0;
37
+ if (size < header || offset + size > end) {
38
+ return null;
39
+ }
40
+ boxes.push({ type, start: offset + header, end: offset + size });
41
+ offset += size;
42
+ }
43
+ return boxes;
44
+ }
45
+ /** Reads the fields of a box's content in order; any read past its end throws. */
46
+ class FieldReader {
47
+ position;
48
+ bytes;
49
+ end;
50
+ constructor(bytes, box) {
51
+ this.bytes = bytes;
52
+ this.position = box.start;
53
+ this.end = box.end;
54
+ }
55
+ /** An unsigned big-endian integer of 0, 1, 2, 4 or 8 bytes. */
56
+ uint(size) {
57
+ if (this.position + size > this.end) {
58
+ throw new RangeError('Box field past its end');
59
+ }
60
+ const value = size === 8 ? Number(this.bytes.readBigUInt64BE(this.position)) : size === 0 ? 0 : this.bytes.readUIntBE(this.position, size);
61
+ this.position += size;
62
+ return value;
63
+ }
64
+ text(length) {
65
+ if (this.position + length > this.end) {
66
+ throw new RangeError('Box field past its end');
67
+ }
68
+ this.position += length;
69
+ return this.bytes.toString('latin1', this.position - length, this.position);
70
+ }
71
+ /** A NUL-terminated string, or the rest of the box when the NUL is missing. */
72
+ string() {
73
+ const nul = this.bytes.indexOf(0, this.position);
74
+ const stop = nul < 0 || nul >= this.end ? this.end : nul;
75
+ const value = this.bytes.toString('utf8', this.position, stop);
76
+ this.position = Math.min(this.end, stop + 1);
77
+ return value;
78
+ }
79
+ }
80
+ /** The IDs of the EXIF and XMP items an `iinf` box lists; throws a RangeError when it does not read. */
81
+ function readMetadataItems(bytes, iinf) {
82
+ const reader = new FieldReader(bytes, iinf);
83
+ const version = reader.uint(1);
84
+ reader.uint(3);
85
+ reader.uint(version === 0 ? 2 : 4);
86
+ const exif = new Set();
87
+ const xmp = new Set();
88
+ const entries = readBoxes(bytes, reader.position, iinf.end);
89
+ if (!entries) {
90
+ throw new RangeError('Item entries do not read');
91
+ }
92
+ for (const infe of entries) {
93
+ if (infe.type !== 'infe') {
94
+ continue;
95
+ }
96
+ const entry = new FieldReader(bytes, infe);
97
+ const entryVersion = entry.uint(1);
98
+ entry.uint(3);
99
+ const id = entry.uint(entryVersion === 3 ? 4 : 2);
100
+ entry.uint(2);
101
+ // Versions 0 and 1 describe a MIME item without naming its type.
102
+ const type = entryVersion >= 2 ? entry.text(4) : 'mime';
103
+ entry.string();
104
+ if (type === 'Exif') {
105
+ exif.add(id);
106
+ }
107
+ else if (type === 'mime' && entry.string().toLowerCase() === XMP_CONTENT_TYPE) {
108
+ xmp.add(id);
109
+ }
110
+ }
111
+ return { exif, xmp };
112
+ }
113
+ /**
114
+ * Where the data of each item lies, as file offsets read from an `iloc` box, `[ -1, -1 ]` for an
115
+ * extent that cannot be located; throws a RangeError when the box does not read.
116
+ */
117
+ function readItemLocations(bytes, iloc, idat) {
118
+ const reader = new FieldReader(bytes, iloc);
119
+ const version = reader.uint(1);
120
+ reader.uint(3);
121
+ const sizes = reader.uint(2);
122
+ const offsetSize = sizes >> 12;
123
+ const lengthSize = (sizes >> 8) & 0xf;
124
+ const baseOffsetSize = (sizes >> 4) & 0xf;
125
+ const indexSize = version === 0 ? 0 : sizes & 0xf;
126
+ const itemCount = reader.uint(version < 2 ? 2 : 4);
127
+ if (itemCount > MAX_ITEMS) {
128
+ throw new RangeError('Too many items');
129
+ }
130
+ const locations = new Map();
131
+ for (let item = 0; item < itemCount; item++) {
132
+ const id = reader.uint(version < 2 ? 2 : 4);
133
+ const method = version === 0 ? 0 : reader.uint(2) & 0xf;
134
+ const dataReference = reader.uint(2);
135
+ const baseOffset = reader.uint(baseOffsetSize);
136
+ const extentCount = reader.uint(2);
137
+ const extents = [];
138
+ for (let extent = 0; extent < extentCount; extent++) {
139
+ reader.uint(indexSize);
140
+ const offset = reader.uint(offsetSize);
141
+ const length = reader.uint(lengthSize);
142
+ // Method 0 points into the file, 1 into the `idat` box; 2 (other items) and other files are not read.
143
+ const origin = method === 0 && dataReference === 0 ? 0 : method === 1 ? idat?.start : undefined;
144
+ const start = origin === undefined ? -1 : origin + baseOffset + offset;
145
+ const limit = method === 1 ? idat?.end ?? -1 : bytes.length;
146
+ extents.push(start < 0 || length === 0 || start + length > limit ? [-1, -1] : [start, start + length]);
147
+ }
148
+ locations.set(id, [...(locations.get(id) ?? []), ...extents]);
149
+ }
150
+ return locations;
151
+ }
152
+ /** Writes `data`, padded with `fill`, over the extents in order. */
153
+ function overwrite(bytes, extents, data, fill) {
154
+ let written = 0;
155
+ for (const [start, end] of extents) {
156
+ bytes.fill(fill, start, end);
157
+ written += data.copy(bytes, start, Math.min(written, data.length), Math.min(data.length, written + end - start));
158
+ }
159
+ }
160
+ /**
161
+ * The HEIF image with its EXIF and XMP items overwritten, or null when its boxes do not read,
162
+ * an item's data cannot be located, or it is an image sequence.
163
+ */
164
+ export function stripHeif(content) {
165
+ const boxes = readBoxes(content, 0, content.length);
166
+ if (!boxes || boxes[0]?.type !== 'ftyp' || boxes.some((box) => box.type === 'moov')) {
167
+ return null;
168
+ }
169
+ const metas = boxes.filter((box) => box.type === 'meta');
170
+ // `meta` is a full box: version and flags come before its children.
171
+ const children = metas.length === 1 ? readBoxes(content, metas[0].start + 4, metas[0].end) : null;
172
+ const iinf = children?.find((box) => box.type === 'iinf');
173
+ const iloc = children?.find((box) => box.type === 'iloc');
174
+ if (!iinf || !iloc) {
175
+ return null;
176
+ }
177
+ const stripped = Buffer.from(content);
178
+ try {
179
+ const { exif, xmp } = readMetadataItems(content, iinf);
180
+ const locations = readItemLocations(content, iloc, children.find((box) => box.type === 'idat'));
181
+ for (const [ids, data, fill] of [[exif, EMPTY_EXIF, 0], [xmp, Buffer.alloc(0), SPACE]]) {
182
+ for (const id of ids) {
183
+ const extents = locations.get(id) ?? [];
184
+ if (extents.some(([start]) => start < 0)) {
185
+ return null;
186
+ }
187
+ overwrite(stripped, extents, data, fill);
188
+ }
189
+ }
190
+ }
191
+ catch (error) {
192
+ if (error instanceof RangeError) {
193
+ return null;
194
+ }
195
+ throw error;
196
+ }
197
+ // Top-level `uuid` boxes hold vendor data, such as XMP in some writers.
198
+ for (const box of boxes.filter((box) => box.type === 'uuid')) {
199
+ stripped.fill(0, box.start, box.end);
200
+ }
201
+ return stripped;
202
+ }
203
+ /** The major and compatible brands of a file's leading `ftyp` box, empty when there is none. */
204
+ export function readFtypBrands(content) {
205
+ const size = content.length >= 16 ? content.readUInt32BE(0) : 0;
206
+ if (size < 16 || size > content.length || content.toString('latin1', 4, 8) !== 'ftyp') {
207
+ return [];
208
+ }
209
+ // The minor version sits between the major brand and the compatible ones.
210
+ const brands = [content.toString('latin1', 8, 12)];
211
+ for (let offset = 16; offset + 4 <= size; offset += 4) {
212
+ brands.push(content.toString('latin1', offset, offset + 4));
213
+ }
214
+ return brands;
215
+ }
216
+ //# sourceMappingURL=heif-metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heif-metadata.js","sourceRoot":"","sources":["../src/heif-metadata.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export * from './check-upload.js';
2
+ export * from './upload-allowlist.js';
3
+ export { stripPhotoMetadata } from './photo-metadata.js';
4
+ export { isEncryptedPdf } from './pdf-encryption.js';
5
+ export { MAX_ZIP_ENTRIES, MAX_ZIP_ENTRY_UNCOMPRESSED_BYTES, MAX_ZIP_UNCOMPRESSED_BYTES } from './zip-entries.js';
6
+ export { MAX_OLE_STREAM_BYTES } from './ole-streams.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,gCAAgC,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AACjH,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export * from './check-upload.js';
2
+ export * from './upload-allowlist.js';
3
+ export { stripPhotoMetadata } from './photo-metadata.js';
4
+ export { isEncryptedPdf } from './pdf-encryption.js';
5
+ export { MAX_ZIP_ENTRIES, MAX_ZIP_ENTRY_UNCOMPRESSED_BYTES, MAX_ZIP_UNCOMPRESSED_BYTES } from './zip-entries.js';
6
+ export { MAX_OLE_STREAM_BYTES } from './ole-streams.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,uBAAuB,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,gCAAgC,EAAE,0BAA0B,EAAE,MAAM,kBAAkB,CAAC;AACjH,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Reads the directory and streams of an OLE compound file (the container of
3
+ * legacy .doc/.xls/.ppt files) by following its sector chains.
4
+ * Returns null for anything malformed, oversized or outside versions 3 and 4.
5
+ */
6
+ export interface OleDirectory {
7
+ /** Names of every storage and stream, at any depth. */
8
+ names: string[];
9
+ /** A stream right below the root, matched case-insensitively, or null when missing or malformed. */
10
+ readStream(name: string): Buffer | null;
11
+ }
12
+ /** Upper bounds that keep a crafted file from costing more than a real one. */
13
+ export declare const MAX_OLE_STREAM_BYTES: number;
14
+ export declare function readOleDirectory(content: Buffer): OleDirectory | null;
15
+ //# sourceMappingURL=ole-streams.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ole-streams.d.ts","sourceRoot":"","sources":["../src/ole-streams.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,YAAY;IAC5B,uDAAuD;IACvD,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,oGAAoG;IACpG,UAAU,CAAE,IAAI,EAAE,MAAM,GAAI,MAAM,GAAG,IAAI,CAAC;CAC1C;AAED,+EAA+E;AAC/E,eAAO,MAAM,oBAAoB,QAAmB,CAAC;AAwHrD,wBAAgB,gBAAgB,CAAE,OAAO,EAAE,MAAM,GAAI,YAAY,GAAG,IAAI,CA2FvE"}