upload-safety 0.0.0-stage → 0.1.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 (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +184 -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/index.d.ts +7 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +7 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/ole-streams.d.ts +15 -0
  16. package/dist/ole-streams.d.ts.map +1 -0
  17. package/dist/ole-streams.js +192 -0
  18. package/dist/ole-streams.js.map +1 -0
  19. package/dist/pdf-active-content.d.ts +3 -0
  20. package/dist/pdf-active-content.d.ts.map +1 -0
  21. package/dist/pdf-active-content.js +130 -0
  22. package/dist/pdf-active-content.js.map +1 -0
  23. package/dist/pdf-encryption.d.ts +70 -0
  24. package/dist/pdf-encryption.d.ts.map +1 -0
  25. package/dist/pdf-encryption.js +576 -0
  26. package/dist/pdf-encryption.js.map +1 -0
  27. package/dist/photo-metadata.d.ts +13 -0
  28. package/dist/photo-metadata.d.ts.map +1 -0
  29. package/dist/photo-metadata.js +227 -0
  30. package/dist/photo-metadata.js.map +1 -0
  31. package/dist/powerpoint-vba.d.ts +14 -0
  32. package/dist/powerpoint-vba.d.ts.map +1 -0
  33. package/dist/powerpoint-vba.js +122 -0
  34. package/dist/powerpoint-vba.js.map +1 -0
  35. package/dist/upload-allowlist.d.ts +48 -0
  36. package/dist/upload-allowlist.d.ts.map +1 -0
  37. package/dist/upload-allowlist.js +534 -0
  38. package/dist/upload-allowlist.js.map +1 -0
  39. package/dist/xml-markup.d.ts +12 -0
  40. package/dist/xml-markup.d.ts.map +1 -0
  41. package/dist/xml-markup.js +83 -0
  42. package/dist/xml-markup.js.map +1 -0
  43. package/dist/zip-entries.d.ts +34 -0
  44. package/dist/zip-entries.d.ts.map +1 -0
  45. package/dist/zip-entries.js +222 -0
  46. package/dist/zip-entries.js.map +1 -0
  47. package/package.json +58 -5
  48. package/src/check-upload.ts +50 -0
  49. package/src/excel-macro-sheets.ts +52 -0
  50. package/src/index.ts +6 -0
  51. package/src/ole-streams.ts +226 -0
  52. package/src/pdf-active-content.ts +138 -0
  53. package/src/pdf-encryption.ts +629 -0
  54. package/src/photo-metadata.ts +241 -0
  55. package/src/powerpoint-vba.ts +136 -0
  56. package/src/upload-allowlist.ts +628 -0
  57. package/src/xml-markup.ts +85 -0
  58. 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,185 @@
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 or PDF scripts. Photos 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
+ | `contract.docx` saved with a password | refused, `encrypted` |
34
+ | `data.xlsx` that is a zip bomb | refused, `content-mismatch` |
35
+ | `holiday.jpg` with GPS coordinates | accepted, EXIF, XMP, IPTC and comments removed, orientation kept |
36
+ | `../../CON.pdf`, a plain PDF | accepted, stored as `_CON.pdf` |
37
+
38
+ Every check fails closed: a part it cannot read is a refusal, not a pass.
39
+
40
+ ## Why
41
+
42
+ - **Zero runtime dependencies.** Only `node:zlib` and `node:crypto`. Nothing to audit but this package.
43
+ - **Typed reasons.** Each refusal is one of seven string literals, so a `Record<UploadRejection, string>` of messages is checked by the compiler.
44
+ - **Fast enough to run inline.** Synchronous; 647 real documents up to 20 MB each took at most 65 ms on a laptop.
45
+ - **Tested against crafted files.** 213 tests build malformed ZIPs, compound files and PDFs byte by byte, next to real Office and PDF files.
46
+
47
+ ## Install
48
+
49
+ ```sh
50
+ npm install upload-safety
51
+ ```
52
+
53
+ Node 22 or later, ESM.
54
+
55
+ ## Usage
56
+
57
+ ### Web `Request` (Next.js route handlers, Hono, Remix, Bun)
58
+
59
+ ```ts
60
+ import { checkUpload, type UploadRejection } from 'upload-safety';
61
+
62
+ export async function POST( request: Request ) {
63
+ const file = ( await request.formData() ).get( 'file' );
64
+ if ( ! ( file instanceof File ) ) {
65
+ return Response.json( { error: 'No file.' }, { status: 400 } );
66
+ }
67
+
68
+ const result = checkUpload(
69
+ { filename: file.name, mimeType: file.type, bytes: new Uint8Array( await file.arrayBuffer() ) },
70
+ { maxBytes: 10 * 1024 * 1024, allowedExtensions: [ 'pdf', 'docx', 'jpg', 'jpeg', 'png' ] }
71
+ );
72
+ if ( ! result.ok ) {
73
+ return Response.json( { error: MESSAGES[ result.reason ] }, { status: 422 } );
74
+ }
75
+
76
+ // Store result.bytes under result.filename with result.mimeType.
77
+ return Response.json( { name: result.filename } );
78
+ }
79
+
80
+ const MESSAGES: Record<UploadRejection, string> = {
81
+ 'empty': 'The file is empty.',
82
+ 'too-large': 'The file is too large.',
83
+ 'type-not-allowed': 'This file type is not allowed.',
84
+ 'mime-mismatch': 'The file type does not match its extension.',
85
+ 'content-mismatch': 'The file content does not match its extension.',
86
+ 'macros': 'Files with macros or other active content are not allowed.',
87
+ 'encrypted': 'Password-protected files cannot be checked.',
88
+ };
89
+ ```
90
+
91
+ ### Express with multer
92
+
93
+ ```ts
94
+ import express from 'express';
95
+ import multer from 'multer';
96
+ import { checkUpload } from 'upload-safety';
97
+
98
+ const MAX_BYTES = 10 * 1024 * 1024;
99
+ // Memory storage hands over the bytes; the limit stops a large body before it is buffered.
100
+ const upload = multer( { storage: multer.memoryStorage(), limits: { fileSize: MAX_BYTES } } );
101
+ const app = express();
102
+
103
+ app.post( '/upload', upload.single( 'file' ), ( req, res ) => {
104
+ if ( ! req.file ) {
105
+ return res.status( 400 ).json( { error: 'No file.' } );
106
+ }
107
+ const result = checkUpload(
108
+ { filename: req.file.originalname, mimeType: req.file.mimetype, bytes: req.file.buffer },
109
+ { maxBytes: MAX_BYTES }
110
+ );
111
+ if ( ! result.ok ) {
112
+ return res.status( 422 ).json( { error: result.reason } );
113
+ }
114
+ // Store result.bytes under result.filename with result.mimeType.
115
+ res.json( { name: result.filename } );
116
+ } );
117
+ ```
118
+
119
+ ### In the browser
120
+
121
+ `acceptAttribute()` gives the matching value for the file picker. It only filters the dialog; the server check is what counts.
122
+
123
+ ```tsx
124
+ <input type="file" accept={ acceptAttribute( [ 'pdf', 'docx' ] ) } />
125
+ ```
126
+
127
+ ## Formats
128
+
129
+ | Format | Extensions | Checked beyond the magic number |
130
+ | - | - | - |
131
+ | 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 |
132
+ | 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 |
133
+ | 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 |
134
+ | OpenDocument | `odt` `ods` `odp` | The `mimetype` entry; Basic and script modules; embedded OLE objects; event listeners; links to macros; encrypted manifests |
135
+ | Photos | `jpg` `jpeg` `png` | Structure; EXIF (GPS included), XMP, IPTC and comments are removed, the orientation is kept |
136
+
137
+ ZIP and OLE reading is capped against zip bombs and malformed sector chains. 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.
138
+
139
+ ## API
140
+
141
+ `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:
142
+
143
+ | Export | Does |
144
+ | - | - |
145
+ | `checkUpload( { filename, mimeType, bytes }, options )` | Every check below; `{ ok: true, extension, filename, mimeType, bytes }` or `{ ok: false, reason }` |
146
+ | `validateUploadDescriptor( { filename, mimeType, size }, options )` | Size, extension and MIME checks before reading the body |
147
+ | `inspectFileContent( extension, bytes )` | `'valid' \| 'macros' \| 'encrypted' \| 'mismatch'` |
148
+ | `stripPhotoMetadata( extension, bytes )` | Cleaned bytes, the input for non-photos, `null` for a broken photo |
149
+ | `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 |
150
+ | `getCanonicalMimeType( extension )` | The MIME type to store |
151
+ | `acceptAttribute( extensions? )` | Value for `<input type="file" accept>` |
152
+ | `isEncryptedPdf( buffer )` | PDF encryption check alone |
153
+
154
+ `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.
155
+
156
+ ## Limits
157
+
158
+ 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.
159
+
160
+ 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.
161
+
162
+ ## Before and after the checks
163
+
164
+ The library checks the bytes it is handed. The rest of the upload path is up to you:
165
+
166
+ - **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.
167
+ - **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.
168
+ - **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`).
169
+ - **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.
170
+ - **Run a virus scanner if you need one.** These checks refuse active content; they do not recognise malware in data.
171
+
172
+ ## Development
173
+
174
+ ```sh
175
+ npm install
176
+ npm test
177
+ npm run typecheck
178
+ npm run build # dist/ with .d.ts
179
+ ```
180
+
181
+ Found a way past a check? Please report it privately, as [SECURITY.md](SECURITY.md) describes.
182
+
183
+ ## License
184
+
185
+ [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,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"}
@@ -0,0 +1,192 @@
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
+ /** Upper bounds that keep a crafted file from costing more than a real one. */
7
+ export const MAX_OLE_STREAM_BYTES = 64 * 1024 * 1024;
8
+ const MAX_OLE_SECTORS = 1 << 20;
9
+ const SIGNATURE = Buffer.from([0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1]);
10
+ const HEADER_SIZE = 512;
11
+ const HEADER_FAT_SECTORS = 109;
12
+ const BYTE_ORDER = 0xfffe;
13
+ const MINI_SECTOR_SHIFT = 6;
14
+ const MINI_SECTOR_SIZE = 1 << MINI_SECTOR_SHIFT;
15
+ const MINI_STREAM_CUTOFF = 4096;
16
+ const END_OF_CHAIN = 0xfffffffe;
17
+ const FREE_SECTOR = 0xffffffff;
18
+ const NO_ENTRY = 0xffffffff;
19
+ const ENTRY_SIZE = 128;
20
+ const STORAGE = 1;
21
+ const STREAM = 2;
22
+ const ROOT = 5;
23
+ /**
24
+ * The first `count` sectors of a chain, or all up to its end without a count; null when one
25
+ * is out of range or repeats. Some writers run a chain on past its stream, as Office tolerates.
26
+ */
27
+ function followChain(next, start, sectorCount, count) {
28
+ const visited = new Uint8Array(sectorCount);
29
+ const sectors = [];
30
+ let sector = start;
31
+ while (sector !== END_OF_CHAIN && sectors.length !== count) {
32
+ if (sector >= sectorCount || visited[sector]) {
33
+ return null;
34
+ }
35
+ visited[sector] = 1;
36
+ sectors.push(sector);
37
+ sector = next(sector);
38
+ }
39
+ return count === undefined || sectors.length === count ? sectors : null;
40
+ }
41
+ /** Reads a stream of `size` bytes, or a chain to its end without one, through an allocation table. */
42
+ function chainReader(table, sector, sectorCount, sectorSize) {
43
+ const next = (index) => (index * 4 + 4 <= table.length ? table.readUInt32LE(index * 4) : FREE_SECTOR);
44
+ return (start, size) => {
45
+ if ((size ?? 0) > MAX_OLE_STREAM_BYTES) {
46
+ return null;
47
+ }
48
+ const count = size === undefined ? undefined : Math.ceil(size / sectorSize);
49
+ const sectors = count === 0 ? [] : followChain(next, start, sectorCount, count);
50
+ const data = sectors && Buffer.concat(sectors.map(sector));
51
+ return data && data.length >= (size ?? 0) ? data.subarray(0, size) : null;
52
+ };
53
+ }
54
+ /** The directory's entries, null for unused ones, or null when an entry is malformed. */
55
+ function readEntries(directory, version) {
56
+ if (!directory) {
57
+ return null;
58
+ }
59
+ const entries = [];
60
+ for (let offset = 0; offset + ENTRY_SIZE <= directory.length; offset += ENTRY_SIZE) {
61
+ const nameLength = directory.readUInt16LE(offset + 0x40);
62
+ const type = directory[offset + 0x42];
63
+ // Version 3 writers may leave garbage in the upper half of the size.
64
+ const size = directory.readUInt32LE(offset + 0x78) + (version === 3 ? 0 : directory.readUInt32LE(offset + 0x7c) * 2 ** 32);
65
+ if (type !== STORAGE && type !== STREAM && type !== ROOT) {
66
+ entries.push(null);
67
+ continue;
68
+ }
69
+ if (nameLength < 2 || nameLength > 64 || nameLength % 2) {
70
+ return null;
71
+ }
72
+ entries.push({
73
+ name: directory.toString('utf16le', offset, offset + nameLength - 2),
74
+ type,
75
+ left: directory.readUInt32LE(offset + 0x44),
76
+ right: directory.readUInt32LE(offset + 0x48),
77
+ child: directory.readUInt32LE(offset + 0x4c),
78
+ start: directory.readUInt32LE(offset + 0x74),
79
+ size,
80
+ });
81
+ }
82
+ return entries[0]?.type === ROOT ? entries : null;
83
+ }
84
+ /**
85
+ * The entries right below the root, walked through the sibling tree, or null for a broken
86
+ * tree or two of the same name, of which a check could read another than Office.
87
+ */
88
+ function readRootChildren(entries, root) {
89
+ const children = [];
90
+ const names = new Set();
91
+ const visited = new Set();
92
+ const pending = [root.child];
93
+ while (pending.length) {
94
+ const index = pending.pop();
95
+ if (index === NO_ENTRY) {
96
+ continue;
97
+ }
98
+ const entry = entries[index];
99
+ if (!entry || entry.type === ROOT || visited.has(index) || names.has(entry.name.toLowerCase())) {
100
+ return null;
101
+ }
102
+ visited.add(index);
103
+ names.add(entry.name.toLowerCase());
104
+ children.push(entry);
105
+ pending.push(entry.left, entry.right);
106
+ }
107
+ return children;
108
+ }
109
+ export function readOleDirectory(content) {
110
+ if (content.length < HEADER_SIZE || !content.subarray(0, SIGNATURE.length).equals(SIGNATURE) ||
111
+ content.readUInt16LE(0x1c) !== BYTE_ORDER || content.readUInt16LE(0x20) !== MINI_SECTOR_SHIFT ||
112
+ content.readUInt32LE(0x38) !== MINI_STREAM_CUTOFF) {
113
+ return null;
114
+ }
115
+ const version = content.readUInt16LE(0x1a);
116
+ const sectorShift = content.readUInt16LE(0x1e);
117
+ if (!(version === 3 && sectorShift === 9) && !(version === 4 && sectorShift === 12)) {
118
+ return null;
119
+ }
120
+ const sectorSize = 1 << sectorShift;
121
+ // Version 4 pads the header to a full sector; sector 0 follows it.
122
+ const sectorCount = Math.ceil((content.length - sectorSize) / sectorSize);
123
+ if (sectorCount <= 0 || sectorCount > MAX_OLE_SECTORS) {
124
+ return null;
125
+ }
126
+ const sector = (index) => content.subarray((index + 1) * sectorSize, (index + 2) * sectorSize);
127
+ // The FAT's sectors are listed in the header, then in the DIFAT chain.
128
+ const fatSectorCount = content.readUInt32LE(0x2c);
129
+ const difatSectorCount = content.readUInt32LE(0x48);
130
+ if (fatSectorCount > sectorCount || difatSectorCount > sectorCount) {
131
+ return null;
132
+ }
133
+ const fatSectors = [];
134
+ for (let index = 0; index < Math.min(fatSectorCount, HEADER_FAT_SECTORS); index++) {
135
+ fatSectors.push(content.readUInt32LE(0x4c + index * 4));
136
+ }
137
+ // Writers end the DIFAT chain with either marker, so its length comes from the header.
138
+ const difatSeen = new Set();
139
+ let difat = content.readUInt32LE(0x44);
140
+ for (let count = 0; count < difatSectorCount; count++) {
141
+ if (difat >= sectorCount || difatSeen.has(difat)) {
142
+ return null;
143
+ }
144
+ difatSeen.add(difat);
145
+ const entries = sector(difat);
146
+ for (let index = 0; index < sectorSize / 4 - 1 && fatSectors.length < fatSectorCount; index++) {
147
+ fatSectors.push(entries.readUInt32LE(index * 4));
148
+ }
149
+ difat = entries.readUInt32LE(sectorSize - 4);
150
+ }
151
+ if (fatSectors.length !== fatSectorCount || fatSectors.some((index) => index >= sectorCount)) {
152
+ return null;
153
+ }
154
+ const fat = Buffer.concat(fatSectors.map(sector));
155
+ const readChain = chainReader(fat, sector, sectorCount, sectorSize);
156
+ const directory = readEntries(readChain(content.readUInt32LE(0x30)), version);
157
+ const root = directory?.[0];
158
+ if (!directory || !root) {
159
+ return null;
160
+ }
161
+ const children = readRootChildren(directory, root);
162
+ if (!children) {
163
+ return null;
164
+ }
165
+ // Streams under the cutoff live in 64-byte sectors of the root's mini stream.
166
+ let readMiniChain;
167
+ const readSmallStream = (start, size) => {
168
+ if (!readMiniChain) {
169
+ const miniFatSectors = content.readUInt32LE(0x40);
170
+ const miniFat = miniFatSectors <= sectorCount ? readChain(content.readUInt32LE(0x3c), miniFatSectors * sectorSize) : null;
171
+ const miniStream = readChain(root.start, root.size);
172
+ readMiniChain = miniFat && miniStream ?
173
+ chainReader(miniFat, (index) => miniStream.subarray(index * MINI_SECTOR_SIZE, (index + 1) * MINI_SECTOR_SIZE), Math.ceil(miniStream.length / MINI_SECTOR_SIZE), MINI_SECTOR_SIZE) :
174
+ () => null;
175
+ }
176
+ return readMiniChain(start, size);
177
+ };
178
+ return {
179
+ names: directory.flatMap((entry) => (entry ? [entry.name] : [])),
180
+ readStream(name) {
181
+ const entry = children.find((child) => child.type === STREAM && child.name.toLowerCase() === name.toLowerCase());
182
+ if (!entry) {
183
+ return null;
184
+ }
185
+ if (entry.size === 0) {
186
+ return Buffer.alloc(0);
187
+ }
188
+ return entry.size < MINI_STREAM_CUTOFF ? readSmallStream(entry.start, entry.size) : readChain(entry.start, entry.size);
189
+ },
190
+ };
191
+ }
192
+ //# sourceMappingURL=ole-streams.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ole-streams.js","sourceRoot":"","sources":["../src/ole-streams.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ /** The verdict on a PDF: encrypted behind a password, holding active content, unreadable or plain. */
2
+ export declare function inspectPdf(content: Buffer): 'valid' | 'macros' | 'encrypted' | 'mismatch';
3
+ //# sourceMappingURL=pdf-active-content.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pdf-active-content.d.ts","sourceRoot":"","sources":["../src/pdf-active-content.ts"],"names":[],"mappings":"AA2HA,sGAAsG;AACtG,wBAAgB,UAAU,CAAE,OAAO,EAAE,MAAM,GAAI,OAAO,GAAG,QAAQ,GAAG,WAAW,GAAG,UAAU,CAa3F"}