Skip to content

Commit b5a70bb

Browse files
LPegasusiclanton
andauthored
[rush] Fix disallowInsecureSha1 exemptPackageVersions not working with pnpm v9 (#5525)
* Fix disallowInsecureSha1 exemptPackageVersions not working with pnpm v9 * Add a test for a non-exempted package. * fixup! Fix disallowInsecureSha1 exemptPackageVersions not working with pnpm v9 * Clean up _parseDependencyPath. * Rush change. --------- Co-authored-by: LPegasus <lpegasus@users.noreply.github.com> Co-authored-by: Ian Clanton-Thuon <iclanton@users.noreply.github.com>
1 parent 1520027 commit b5a70bb

File tree

7 files changed

+253
-6
lines changed

7 files changed

+253
-6
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@microsoft/rush",
5+
"comment": "Fix an issue where packages listed in the `pnpmLockfilePolicies.disallowInsecureSha1.exemptPackageVersions` `common/config/rush/pnpm-config.json` config file are not exempted in PNPM 9.",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@microsoft/rush"
10+
}

libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -605,13 +605,30 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile {
605605
* Example: "/@typescript-eslint/experimental-utils/5.9.1_eslint@8.6.0+typescript@4.4.4" --> "/@typescript-eslint/experimental-utils/5.9.1"
606606
*/
607607
private _parseDependencyPath(packagePath: string): string {
608-
let depPath: string = packagePath;
609-
if (this.shrinkwrapFileMajorVersion >= ShrinkwrapFileMajorVersion.V6) {
610-
depPath = this._convertLockfileV6DepPathToV5DepPath(packagePath);
608+
let name: string | undefined;
609+
let version: string | undefined;
610+
611+
/**
612+
* For PNPM lockfile version 9 and above, use pnpmKitV9 to parse the dependency path.
613+
* Example: "@some/pkg@1.0.0" --> "@some/pkg@1.0.0"
614+
* Example: "@some/pkg@1.0.0(peer@2.0.0)" --> "@some/pkg@1.0.0"
615+
* Example: "pkg@1.0.0(patch_hash)" --> "pkg@1.0.0"
616+
*/
617+
if (this.shrinkwrapFileMajorVersion >= ShrinkwrapFileMajorVersion.V9) {
618+
({ name, version } = pnpmKitV9.dependencyPath.parse(packagePath));
619+
} else {
620+
if (this.shrinkwrapFileMajorVersion >= ShrinkwrapFileMajorVersion.V6) {
621+
packagePath = this._convertLockfileV6DepPathToV5DepPath(packagePath);
622+
}
623+
624+
({ name, version } = pnpmKitV8.dependencyPath.parse(packagePath));
611625
}
612-
const pkgInfo: ReturnType<typeof pnpmKitV8.dependencyPath.parse> =
613-
pnpmKitV8.dependencyPath.parse(depPath);
614-
return this._getPackageId(pkgInfo.name as string, pkgInfo.version as string);
626+
627+
if (!name || !version) {
628+
throw new InternalError(`Unable to parse package path: ${packagePath}`);
629+
}
630+
631+
return this._getPackageId(name, version);
615632
}
616633

617634
/** @override */

libraries/rush-lib/src/logic/pnpm/test/PnpmShrinkwrapFile.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { PnpmShrinkwrapFile, parsePnpm9DependencyKey, parsePnpmDependencyKey } f
66
import { RushConfiguration } from '../../../api/RushConfiguration';
77
import type { RushConfigurationProject } from '../../../api/RushConfigurationProject';
88
import type { Subspace } from '../../../api/Subspace';
9+
import { StringBufferTerminalProvider, Terminal } from '@rushstack/terminal';
10+
import { PnpmOptionsConfiguration } from '../PnpmOptionsConfiguration';
11+
import { AlreadyReportedError } from '@rushstack/node-core-library';
912

1013
const DEPENDENCY_NAME: string = 'dependency_name';
1114
const SCOPED_DEPENDENCY_NAME: string = '@scope/dependency_name';
@@ -483,6 +486,74 @@ snapshots:
483486
)
484487
).resolves.toBe(false);
485488
});
489+
490+
it('sha1 integrity can be handled when disallowInsecureSha1', async () => {
491+
const project = getMockRushProject();
492+
const pnpmShrinkwrapFile = getPnpmShrinkwrapFileFromFile(
493+
`${__dirname}/yamlFiles/pnpm-lock-v9/sha1-integrity.yaml`,
494+
project.rushConfiguration.defaultSubspace
495+
);
496+
497+
const defaultSubspace = project.rushConfiguration.defaultSubspace;
498+
499+
const mockPnpmOptions = PnpmOptionsConfiguration.loadFromJsonFileOrThrow(
500+
`${__dirname}/jsonFiles/pnpm-config-disallow-sha1.json`,
501+
defaultSubspace.getSubspaceTempFolderPath()
502+
);
503+
504+
jest.spyOn(defaultSubspace, 'getPnpmOptions').mockReturnValue(mockPnpmOptions);
505+
506+
const spyTerminalWrite = jest.fn();
507+
const terminal = new Terminal({
508+
eolCharacter: '\n',
509+
supportsColor: false,
510+
write: spyTerminalWrite
511+
});
512+
513+
expect(() =>
514+
pnpmShrinkwrapFile.validateShrinkwrapAfterUpdate(
515+
project.rushConfiguration,
516+
project.rushConfiguration.defaultSubspace,
517+
terminal
518+
)
519+
).not.toThrow();
520+
expect(spyTerminalWrite).not.toHaveBeenCalled();
521+
});
522+
523+
it('sha1 integrity can be handled when disallowInsecureSha1', async () => {
524+
const project = getMockRushProject();
525+
const pnpmShrinkwrapFile = getPnpmShrinkwrapFileFromFile(
526+
`${__dirname}/yamlFiles/pnpm-lock-v9/sha1-integrity-non-exempted-package.yaml`,
527+
project.rushConfiguration.defaultSubspace
528+
);
529+
530+
const defaultSubspace = project.rushConfiguration.defaultSubspace;
531+
532+
const mockPnpmOptions = PnpmOptionsConfiguration.loadFromJsonFileOrThrow(
533+
`${__dirname}/jsonFiles/pnpm-config-disallow-sha1.json`,
534+
defaultSubspace.getSubspaceTempFolderPath()
535+
);
536+
537+
jest.spyOn(defaultSubspace, 'getPnpmOptions').mockReturnValue(mockPnpmOptions);
538+
539+
const terminalProvider: StringBufferTerminalProvider = new StringBufferTerminalProvider();
540+
const terminal = new Terminal(terminalProvider);
541+
542+
expect(() =>
543+
pnpmShrinkwrapFile.validateShrinkwrapAfterUpdate(
544+
project.rushConfiguration,
545+
project.rushConfiguration.defaultSubspace,
546+
terminal
547+
)
548+
).toThrowError(AlreadyReportedError);
549+
expect({
550+
log: terminalProvider.getOutput(),
551+
warning: terminalProvider.getWarningOutput(),
552+
error: terminalProvider.getErrorOutput(),
553+
verbose: terminalProvider.getVerboseOutput(),
554+
debug: terminalProvider.getDebugOutput()
555+
}).toMatchSnapshot();
556+
});
486557
});
487558
});
488559
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`PnpmShrinkwrapFile Check is workspace project modified pnpm lockfile major version 9 sha1 integrity can be handled when disallowInsecureSha1 1`] = `
4+
Object {
5+
"debug": "",
6+
"error": "Error: An integrity field with \\"sha1\\" was detected in the pnpm-lock.yaml file located in subspace default; this conflicts with the \\"disallowInsecureSha1\\" policy from pnpm-config.json.[n][n]",
7+
"log": "",
8+
"verbose": "",
9+
"warning": "",
10+
}
11+
`;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"pnpmLockfilePolicies": {
3+
"disallowInsecureSha1": {
4+
"enabled": true,
5+
"exemptPackageVersions": {
6+
"@some/sha1-pkg": ["1.4.3"],
7+
"other-sha1-pkg": ["2.0.0"],
8+
"fake-with-patch": ["1.0.0"],
9+
"fake-with-peer": ["1.0.0"],
10+
"fake": ["7.8.1"]
11+
}
12+
}
13+
}
14+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
lockfileVersion: '9.0'
2+
3+
settings:
4+
autoInstallPeers: true
5+
excludeLinksFromLockfile: false
6+
7+
patchedDependencies:
8+
fake-with-patch@1.0.0:
9+
hash: tta6uuavpnftppsegagihriayy
10+
path: patches/fake-with-patch@1.0.0.patch
11+
12+
importers:
13+
.:
14+
dependencies:
15+
'@some/sha1-pkg':
16+
specifier: ^1.0.0
17+
version: 1.4.3
18+
other-sha1-pkg:
19+
specifier: ~2.0.0
20+
version: 2.0.0
21+
fake-with-patch:
22+
specifier: 1.0.0
23+
version: 1.0.0
24+
fake-with-peer:
25+
specifier: 1.0.0
26+
version: 1.0.0
27+
fake-with-npm:
28+
specifier: npm:fake@7.8.1
29+
version: fake@7.8.1
30+
fake-non-exempted:
31+
specifier: 1.0.0
32+
version: 1.0.0
33+
34+
packages:
35+
'@some/sha1-pkg@1.4.3':
36+
resolution: { integrity: sha1-KQzv7h3EqVCA2u7BOFut5Vqbl5o= }
37+
38+
other-sha1-pkg@2.0.0:
39+
resolution: { integrity: sha1-+5v5y5gkJ6x2+X1K3pZ5p8W7m4o= }
40+
41+
fake-with-patch@1.0.0:
42+
resolution: { integrity: sha1-FAKEPATCHINTEGRITY1234567890= }
43+
44+
fake-with-peer@1.0.0:
45+
resolution: { integrity: sha1-FAKEPEERINTEGRITY0987654321= }
46+
47+
fake@7.8.1:
48+
resolution: { integrity: sha1-FAKEPEERINTEGRITY0987654321= }
49+
50+
fake-non-exempted@1.0.0:
51+
resolution: { integrity: sha1-FAKEPEERINTEGRITY0987654321= }
52+
53+
snapshots:
54+
'@some/sha1-pkg@1.4.3': {}
55+
56+
other-sha1-pkg@2.0.0: {}
57+
58+
fake-with-patch@1.0.0(patch_hash=tta6uuavpnftppsegagihriayy): {}
59+
60+
fake-with-peer@1.0.0(@some/sha1-pkg@1.4.3):
61+
transitivePeerDependencies:
62+
- '@some/sha1-pkg'
63+
64+
fake@7.8.1: {}
65+
66+
fake-non-exempted@1.0.0: {}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
lockfileVersion: '9.0'
2+
3+
settings:
4+
autoInstallPeers: true
5+
excludeLinksFromLockfile: false
6+
7+
patchedDependencies:
8+
fake-with-patch@1.0.0:
9+
hash: tta6uuavpnftppsegagihriayy
10+
path: patches/fake-with-patch@1.0.0.patch
11+
12+
importers:
13+
.:
14+
dependencies:
15+
'@some/sha1-pkg':
16+
specifier: ^1.0.0
17+
version: 1.4.3
18+
other-sha1-pkg:
19+
specifier: ~2.0.0
20+
version: 2.0.0
21+
fake-with-patch:
22+
specifier: 1.0.0
23+
version: 1.0.0
24+
fake-with-peer:
25+
specifier: 1.0.0
26+
version: 1.0.0
27+
fake-with-npm:
28+
specifier: npm:fake@7.8.1
29+
version: fake@7.8.1
30+
31+
packages:
32+
'@some/sha1-pkg@1.4.3':
33+
resolution: { integrity: sha1-KQzv7h3EqVCA2u7BOFut5Vqbl5o= }
34+
35+
other-sha1-pkg@2.0.0:
36+
resolution: { integrity: sha1-+5v5y5gkJ6x2+X1K3pZ5p8W7m4o= }
37+
38+
fake-with-patch@1.0.0:
39+
resolution: { integrity: sha1-FAKEPATCHINTEGRITY1234567890= }
40+
41+
fake-with-peer@1.0.0:
42+
resolution: { integrity: sha1-FAKEPEERINTEGRITY0987654321= }
43+
44+
fake@7.8.1:
45+
resolution: { integrity: sha1-FAKEPEERINTEGRITY0987654321= }
46+
47+
snapshots:
48+
'@some/sha1-pkg@1.4.3': {}
49+
50+
other-sha1-pkg@2.0.0: {}
51+
52+
fake-with-patch@1.0.0(patch_hash=tta6uuavpnftppsegagihriayy): {}
53+
54+
fake-with-peer@1.0.0(@some/sha1-pkg@1.4.3):
55+
transitivePeerDependencies:
56+
- '@some/sha1-pkg'
57+
58+
fake@7.8.1: {}

0 commit comments

Comments
 (0)