@pnpm/patching.apply-patch 1100.0.0 → 1100.0.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 +1 -1
- package/lib/index.d.ts +4 -0
- package/lib/index.js +58 -3
- package/package.json +10 -7
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> Apply a patch to a directory
|
|
4
4
|
|
|
5
5
|
<!--@shields('npm')-->
|
|
6
|
-
[](https://
|
|
6
|
+
[](https://npmx.dev/package/@pnpm/patching.apply-patch)
|
|
7
7
|
<!--/@-->
|
|
8
8
|
|
|
9
9
|
## Installation
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
import { PnpmError } from '@pnpm/error';
|
|
1
2
|
export interface ApplyPatchToDirOpts {
|
|
2
3
|
patchedDir: string;
|
|
3
4
|
patchFilePath: string;
|
|
4
5
|
}
|
|
5
6
|
export declare function applyPatchToDir(opts: ApplyPatchToDirOpts): boolean;
|
|
7
|
+
export declare class PatchPathEscapesError extends PnpmError {
|
|
8
|
+
constructor(opts: ApplyPatchToDirOpts, badPath: string);
|
|
9
|
+
}
|
package/lib/index.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import util from 'node:util';
|
|
1
4
|
import { PnpmError } from '@pnpm/error';
|
|
2
5
|
import { applyPatch } from '@pnpm/patch-package/dist/applyPatches.js';
|
|
6
|
+
import { parsePatchFile } from '@pnpm/patch-package/dist/patch/parse.js';
|
|
3
7
|
export function applyPatchToDir(opts) {
|
|
4
8
|
// Ideally, we would just run "patch" or "git apply".
|
|
5
9
|
// However, "patch" is not available on Windows and "git apply" is hard to execute on a subdirectory of an existing repository
|
|
10
|
+
assertPatchPathsStayInside(opts);
|
|
6
11
|
const cwd = process.cwd();
|
|
7
12
|
process.chdir(opts.patchedDir);
|
|
8
13
|
let success = false;
|
|
@@ -11,11 +16,12 @@ export function applyPatchToDir(opts) {
|
|
|
11
16
|
patchFilePath: opts.patchFilePath,
|
|
12
17
|
});
|
|
13
18
|
}
|
|
14
|
-
catch (err) {
|
|
15
|
-
if (err.code === 'ENOENT') {
|
|
19
|
+
catch (err) {
|
|
20
|
+
if (util.types.isNativeError(err) && 'code' in err && err.code === 'ENOENT') {
|
|
16
21
|
throw new PnpmError('PATCH_NOT_FOUND', `Patch file not found: ${opts.patchFilePath}`);
|
|
17
22
|
}
|
|
18
|
-
|
|
23
|
+
const message = util.types.isNativeError(err) ? err.message : String(err);
|
|
24
|
+
throw new PnpmError('INVALID_PATCH', `Applying patch "${opts.patchFilePath}" failed: ${message}`);
|
|
19
25
|
}
|
|
20
26
|
finally {
|
|
21
27
|
process.chdir(cwd);
|
|
@@ -25,4 +31,53 @@ export function applyPatchToDir(opts) {
|
|
|
25
31
|
}
|
|
26
32
|
return success;
|
|
27
33
|
}
|
|
34
|
+
// A patch file is attacker-controlled data: `diff --git a/../../X b/../../X` headers
|
|
35
|
+
// would otherwise let the applier traverse out of the package directory and write,
|
|
36
|
+
// delete, or rename files anywhere the install user can.
|
|
37
|
+
function assertPatchPathsStayInside(opts) {
|
|
38
|
+
let patchContent;
|
|
39
|
+
try {
|
|
40
|
+
patchContent = fs.readFileSync(opts.patchFilePath, 'utf8');
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (util.types.isNativeError(err) && 'code' in err && err.code === 'ENOENT') {
|
|
44
|
+
throw new PnpmError('PATCH_NOT_FOUND', `Patch file not found: ${opts.patchFilePath}`);
|
|
45
|
+
}
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
let effects;
|
|
49
|
+
try {
|
|
50
|
+
effects = parsePatchFile(patchContent);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Defer parse-error reporting to applyPatch so its existing
|
|
54
|
+
// ERR_PNPM_INVALID_PATCH path produces the message and exit behavior.
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const root = path.resolve(opts.patchedDir);
|
|
58
|
+
const rootWithSep = root.endsWith(path.sep) ? root : root + path.sep;
|
|
59
|
+
for (const effect of effects) {
|
|
60
|
+
const candidates = effect.type === 'rename'
|
|
61
|
+
? [effect.fromPath, effect.toPath]
|
|
62
|
+
: [effect.path];
|
|
63
|
+
for (const candidate of candidates) {
|
|
64
|
+
if (!candidate)
|
|
65
|
+
continue;
|
|
66
|
+
if (path.isAbsolute(candidate) ||
|
|
67
|
+
candidate.split(/[/\\]/).includes('..')) {
|
|
68
|
+
throw new PatchPathEscapesError(opts, candidate);
|
|
69
|
+
}
|
|
70
|
+
const resolved = path.resolve(root, candidate);
|
|
71
|
+
if (resolved !== root && !resolved.startsWith(rootWithSep)) {
|
|
72
|
+
throw new PatchPathEscapesError(opts, candidate);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export class PatchPathEscapesError extends PnpmError {
|
|
78
|
+
constructor(opts, badPath) {
|
|
79
|
+
super('PATCH_FAILED', `Could not apply patch ${opts.patchFilePath} to ${opts.patchedDir}: ` +
|
|
80
|
+
`patch path escapes target dir: ${badPath}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
28
83
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pnpm/patching.apply-patch",
|
|
3
|
-
"version": "1100.0.
|
|
3
|
+
"version": "1100.0.2",
|
|
4
4
|
"description": "Apply a patch to a directory",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pnpm",
|
|
@@ -9,7 +9,10 @@
|
|
|
9
9
|
],
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"funding": "https://opencollective.com/pnpm",
|
|
12
|
-
"repository":
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/pnpm/pnpm/tree/main/patching/apply-patch"
|
|
15
|
+
},
|
|
13
16
|
"homepage": "https://github.com/pnpm/pnpm/tree/main/patching/apply-patch#readme",
|
|
14
17
|
"bugs": {
|
|
15
18
|
"url": "https://github.com/pnpm/pnpm/issues"
|
|
@@ -29,14 +32,14 @@
|
|
|
29
32
|
"@pnpm/error": "1100.0.0"
|
|
30
33
|
},
|
|
31
34
|
"peerDependencies": {
|
|
32
|
-
"@pnpm/logger": "
|
|
35
|
+
"@pnpm/logger": "^1100.0.0"
|
|
33
36
|
},
|
|
34
37
|
"devDependencies": {
|
|
35
|
-
"@jest/globals": "30.
|
|
38
|
+
"@jest/globals": "30.4.1",
|
|
39
|
+
"@pnpm/prepare": "1100.0.16",
|
|
36
40
|
"@pnpm/logger": "1100.0.0",
|
|
37
|
-
"@pnpm/
|
|
38
|
-
"@pnpm/
|
|
39
|
-
"@pnpm/test-fixtures": "1100.0.0"
|
|
41
|
+
"@pnpm/test-fixtures": "1100.0.0",
|
|
42
|
+
"@pnpm/patching.apply-patch": "1100.0.2"
|
|
40
43
|
},
|
|
41
44
|
"engines": {
|
|
42
45
|
"node": ">=22.13"
|