@patrickdk77/pdf-defuse 0.2.1 → 0.2.2
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.
- package/README.md +8 -4
- package/dist/cli.js +6 -2
- package/dist/document.d.ts +1 -1
- package/dist/document.js +10 -5
- package/dist/sniff.js +8 -1
- package/dist/types.d.ts +5 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -69,7 +69,7 @@ Removed by default:
|
|
|
69
69
|
- Actions that reach outside the document: launching programs, links to other or embedded files, form submit and import, show and hide toggles, layer toggles, menu commands and media.
|
|
70
70
|
- Links other than http, https and mailto, and links built to mislead. That covers credentials before the host, IP hosts, punycode or mixed-script hosts, encoded hosts, tooltips or alternate text that name a different site, links and push buttons that cover most of a page, and relative links with no base address.
|
|
71
71
|
- Attached files, portfolios, XFA forms, slideshows, media and 3D annotations, and annotation types outside an allowlist.
|
|
72
|
-
- Encryption with an empty password, or with
|
|
72
|
+
- Encryption with an empty password, or with a password you pass.
|
|
73
73
|
- Earlier revisions, unreferenced objects and damaged structure. The output is one clean revision, unless it is a signed file that keeps its bytes, as described below.
|
|
74
74
|
|
|
75
75
|
Kept: text, images, fonts, layout, bookmarks, form fields and their values, safe links and metadata.
|
|
@@ -121,7 +121,7 @@ A run that stops before it has seen the whole file rejects the file with a null
|
|
|
121
121
|
|
|
122
122
|
| Option | Default | Purpose |
|
|
123
123
|
|---|---|---|
|
|
124
|
-
| `password` | `''` |
|
|
124
|
+
| `password` | `''` | One password or a list. pdf-defuse tries each in order, as the user password and then as the owner password, and the empty password last. See Passwords below |
|
|
125
125
|
| `limits` | none | `fileSize`, `objects`, `decompressedBytes`, `nestingDepth` and `timeMs`. A limit left out is not enforced |
|
|
126
126
|
| `stripMetadata` | `false` | Remove the document information dictionary wherever it is referenced, and the XMP metadata of every object: the catalog, pages, images and the rest |
|
|
127
127
|
| `actionOverrides` | none | Change the action for a category, or for a category and detail |
|
|
@@ -139,11 +139,13 @@ await disarmPdf(bytes, { actionOverrides: [{ category: 'JAVASCRIPT', action: 're
|
|
|
139
139
|
|
|
140
140
|
### Passwords
|
|
141
141
|
|
|
142
|
+
`password` takes one password or a list, because one upload can hold files with different passwords, such as a ZIP with one password and a PDF inside it with another. pdf-defuse tries each entry in order, as the user password and then as the owner password, and stops at the first that opens the file. The empty password comes after the list, so a file that opens without a password opens whatever the list holds. ENCRYPTED/USER_PASSWORD or ENCRYPTED/OWNER_PASSWORD says which kind of password opened the file. `pdfPlugin()` gives every attached PDF the list of the run that found it, and `pdfPlugin(options)` gives it the list in `options`.
|
|
143
|
+
|
|
142
144
|
A revision 6 password, the AES-256 scheme of ISO 32000-2, is tried three ways: prepared with SASLprep, as that standard requires, prepared the way pdf.js prepares it, and as typed. The last opens files from tools that skip SASLprep. Other revisions use the password as typed.
|
|
143
145
|
|
|
144
146
|
An empty owner password opens a file, as in qpdf, and ENCRYPTED/OWNER_PASSWORD reports it. pdf.js asks for a password there.
|
|
145
147
|
|
|
146
|
-
A file that encrypts only its attached files, through a crypt filter with `/AuthEvent /EFOpen`, opens without
|
|
148
|
+
A file that encrypts only its attached files, through a crypt filter with `/AuthEvent /EFOpen`, opens without a password, as in pdf.js, and ENCRYPTED/ATTACHMENTS_ONLY reports it. pdf-defuse removes the attached files that no password you pass decrypts, and any other stream under their key, which ENCRYPTED/NO_KEY reports. pdf.js draws such a page blank.
|
|
147
149
|
|
|
148
150
|
## Plugins
|
|
149
151
|
|
|
@@ -268,7 +270,7 @@ The `fileSize`, `objects` and `decompressedBytes` limits apply to one file and h
|
|
|
268
270
|
pdf-defuse inspect <file> [options]
|
|
269
271
|
pdf-defuse defuse <in> <out> [options]
|
|
270
272
|
|
|
271
|
-
--password-file <path> read
|
|
273
|
+
--password-file <path> read passwords from a file, one per line; PDF_DEFUSE_PASSWORD holds one
|
|
272
274
|
--plugin <module> load plugins from a file or an installed package, repeatable
|
|
273
275
|
--config <module> load a whole options object, repeatable; the other options apply on top
|
|
274
276
|
--keep-type <type> keep attached files of this MIME type or file extension unchanged, repeatable
|
|
@@ -286,6 +288,8 @@ Use `-` for stdin or stdout. The tool copies stdin to a temporary file in `tempD
|
|
|
286
288
|
|
|
287
289
|
The text report shows each control character as an escape such as `\u001b`. The JSON report escapes DEL and the C1 range too, which JSON itself does not require. A field name or a title from the PDF can hold an escape sequence that rewrites what the terminal shows, the verdict included.
|
|
288
290
|
|
|
291
|
+
`--password-file` reads one password per line. The tool drops a carriage return at the end of a line and skips blank lines, since the empty password is always tried. Spaces belong to the password. `PDF_DEFUSE_PASSWORD` holds one password, and a line break in it is part of that password. `--password-file` replaces it, and either one replaces a `password` that a `--config` module sets.
|
|
292
|
+
|
|
289
293
|
`objects`, `nestingDepth` and `timeMs` take whole numbers. `fileSize` and `decompressedBytes` take sizes. Any other value is a usage error.
|
|
290
294
|
|
|
291
295
|
A `--plugin` module exports `plugins`, an array, or `plugin`, a single plugin. Either can also sit on the default export, and a default export that is itself a plugin or an array of them works too. ES modules, CommonJS files and installed packages all load. Package names resolve from the working directory, and a package that exports only an `import` condition loads as well, through a subpath pattern such as `"./*"` too. `--plugin keep.js` loads `./keep.js` when the working directory has that file, and the installed package `keep.js` when it does not.
|
package/dist/cli.js
CHANGED
|
@@ -50,7 +50,7 @@ const USAGE = `Usage:
|
|
|
50
50
|
pdf-defuse defuse <in> <out> [options]
|
|
51
51
|
|
|
52
52
|
Options:
|
|
53
|
-
--password-file <path> read
|
|
53
|
+
--password-file <path> read passwords from a file, one per line; PDF_DEFUSE_PASSWORD holds one
|
|
54
54
|
--plugin <module> load plugins from a file or an installed package, repeatable
|
|
55
55
|
--config <module> load a whole options object, repeatable; the other options apply on top
|
|
56
56
|
--keep-type <type> keep contained files of this MIME type or file extension unchanged, repeatable
|
|
@@ -181,7 +181,11 @@ async function parse(argv) {
|
|
|
181
181
|
};
|
|
182
182
|
switch (a) {
|
|
183
183
|
case '--password-file':
|
|
184
|
-
password
|
|
184
|
+
// The empty password is always tried, so a blank line adds nothing.
|
|
185
|
+
password = (await fsp.readFile(next(), 'utf8'))
|
|
186
|
+
.split('\n')
|
|
187
|
+
.map(line => line.replace(/\r$/, ''))
|
|
188
|
+
.filter(line => line !== '');
|
|
185
189
|
break;
|
|
186
190
|
case '--plugin':
|
|
187
191
|
ordered.push(...pluginsFrom(await loadModule(next())));
|
package/dist/document.d.ts
CHANGED
package/dist/document.js
CHANGED
|
@@ -681,11 +681,16 @@ class PdfDocument {
|
|
|
681
681
|
this.encryptRefs.add(num);
|
|
682
682
|
const id = this.trailer.get('ID');
|
|
683
683
|
const id0 = Array.isArray(id) && id[0] instanceof objects_1.PdfString ? id[0].bytes : new Uint8Array(0);
|
|
684
|
-
const
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
684
|
+
const given = this.opts.password ?? '';
|
|
685
|
+
// A password meant for other files must not lock out one that opens without a password, so the empty one comes last.
|
|
686
|
+
const passwords = new Set([...(typeof given === 'string' ? [given] : given), '']);
|
|
687
|
+
let r = { status: 'password-required' };
|
|
688
|
+
for (const password of passwords) {
|
|
689
|
+
this.checkTime();
|
|
690
|
+
r = crypto_1.SecurityHandler.open(dict, id0, password);
|
|
691
|
+
if (r.status !== 'password-required')
|
|
692
|
+
break;
|
|
693
|
+
}
|
|
689
694
|
this.securityResult = r;
|
|
690
695
|
if (r.status === 'ok')
|
|
691
696
|
this.security = r.handler;
|
package/dist/sniff.js
CHANGED
|
@@ -95,7 +95,14 @@ function family(mime) {
|
|
|
95
95
|
* of "page.html." is html.
|
|
96
96
|
*/
|
|
97
97
|
function extensionOf(name) {
|
|
98
|
-
|
|
98
|
+
// A loop and lastIndexOf, since /[. ]+$/ backtracks: a name of 40,000 dots took seconds to match.
|
|
99
|
+
const n = name ?? '';
|
|
100
|
+
let end = n.length;
|
|
101
|
+
while (end > 0 && (n[end - 1] === '.' || n[end - 1] === ' '))
|
|
102
|
+
end--;
|
|
103
|
+
const dot = n.lastIndexOf('.', end - 1);
|
|
104
|
+
const ext = dot < 0 ? '' : n.slice(dot + 1, end);
|
|
105
|
+
return ext && !/[/\\]/.test(ext) ? ext.toLowerCase() : undefined;
|
|
99
106
|
}
|
|
100
107
|
function typeFromName(name) {
|
|
101
108
|
const ext = extensionOf(name);
|
package/dist/types.d.ts
CHANGED
|
@@ -269,8 +269,11 @@ export interface PdfScoreBands {
|
|
|
269
269
|
critical: number;
|
|
270
270
|
}
|
|
271
271
|
export interface PdfOptions {
|
|
272
|
-
/**
|
|
273
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Default ''. Each password is tried in order, as the user password and then as the owner password. The empty
|
|
274
|
+
* password comes after them, so a file that opens without a password opens whatever the list holds.
|
|
275
|
+
*/
|
|
276
|
+
password?: string | string[];
|
|
274
277
|
/** A limit left out is not enforced. */
|
|
275
278
|
limits?: PdfLimits;
|
|
276
279
|
/** Default false. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patrickdk77/pdf-defuse",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Sanitize and clean PDF files. Detects and removes JavaScript, attached files, launch actions, unsafe links and empty-password encryption, scores the risk, and writes a disarmed copy with bounded memory and no runtime dependencies.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "patrickdk77",
|