Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@microsoft/rush",
"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.",
"type": "none"
}
],
"packageName": "@microsoft/rush"
}
29 changes: 23 additions & 6 deletions libraries/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -605,13 +605,30 @@ export class PnpmShrinkwrapFile extends BaseShrinkwrapFile {
* Example: "/@typescript-eslint/experimental-utils/[email protected][email protected]" --> "/@typescript-eslint/experimental-utils/5.9.1"
*/
private _parseDependencyPath(packagePath: string): string {
let depPath: string = packagePath;
if (this.shrinkwrapFileMajorVersion >= ShrinkwrapFileMajorVersion.V6) {
depPath = this._convertLockfileV6DepPathToV5DepPath(packagePath);
let name: string | undefined;
let version: string | undefined;

/**
* For PNPM lockfile version 9 and above, use pnpmKitV9 to parse the dependency path.
* Example: "@some/[email protected]" --> "@some/[email protected]"
* Example: "@some/[email protected]([email protected])" --> "@some/[email protected]"
* Example: "[email protected](patch_hash)" --> "[email protected]"
*/
if (this.shrinkwrapFileMajorVersion >= ShrinkwrapFileMajorVersion.V9) {
({ name, version } = pnpmKitV9.dependencyPath.parse(packagePath));
} else {
if (this.shrinkwrapFileMajorVersion >= ShrinkwrapFileMajorVersion.V6) {
packagePath = this._convertLockfileV6DepPathToV5DepPath(packagePath);
}

({ name, version } = pnpmKitV8.dependencyPath.parse(packagePath));
}
const pkgInfo: ReturnType<typeof pnpmKitV8.dependencyPath.parse> =
pnpmKitV8.dependencyPath.parse(depPath);
return this._getPackageId(pkgInfo.name as string, pkgInfo.version as string);

if (!name || !version) {
throw new InternalError(`Unable to parse package path: ${packagePath}`);
}

return this._getPackageId(name, version);
}

/** @override */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { PnpmShrinkwrapFile, parsePnpm9DependencyKey, parsePnpmDependencyKey } f
import { RushConfiguration } from '../../../api/RushConfiguration';
import type { RushConfigurationProject } from '../../../api/RushConfigurationProject';
import type { Subspace } from '../../../api/Subspace';
import { StringBufferTerminalProvider, Terminal } from '@rushstack/terminal';
import { PnpmOptionsConfiguration } from '../PnpmOptionsConfiguration';
import { AlreadyReportedError } from '@rushstack/node-core-library';

const DEPENDENCY_NAME: string = 'dependency_name';
const SCOPED_DEPENDENCY_NAME: string = '@scope/dependency_name';
Expand Down Expand Up @@ -483,6 +486,74 @@ snapshots:
)
).resolves.toBe(false);
});

it('sha1 integrity can be handled when disallowInsecureSha1', async () => {
const project = getMockRushProject();
const pnpmShrinkwrapFile = getPnpmShrinkwrapFileFromFile(
`${__dirname}/yamlFiles/pnpm-lock-v9/sha1-integrity.yaml`,
project.rushConfiguration.defaultSubspace
);

const defaultSubspace = project.rushConfiguration.defaultSubspace;

const mockPnpmOptions = PnpmOptionsConfiguration.loadFromJsonFileOrThrow(
`${__dirname}/jsonFiles/pnpm-config-disallow-sha1.json`,
defaultSubspace.getSubspaceTempFolderPath()
);

jest.spyOn(defaultSubspace, 'getPnpmOptions').mockReturnValue(mockPnpmOptions);

const spyTerminalWrite = jest.fn();
const terminal = new Terminal({
eolCharacter: '\n',
supportsColor: false,
write: spyTerminalWrite
});

expect(() =>
pnpmShrinkwrapFile.validateShrinkwrapAfterUpdate(
project.rushConfiguration,
project.rushConfiguration.defaultSubspace,
terminal
)
).not.toThrow();
expect(spyTerminalWrite).not.toHaveBeenCalled();
});

it('sha1 integrity can be handled when disallowInsecureSha1', async () => {
const project = getMockRushProject();
const pnpmShrinkwrapFile = getPnpmShrinkwrapFileFromFile(
`${__dirname}/yamlFiles/pnpm-lock-v9/sha1-integrity-non-exempted-package.yaml`,
project.rushConfiguration.defaultSubspace
);

const defaultSubspace = project.rushConfiguration.defaultSubspace;

const mockPnpmOptions = PnpmOptionsConfiguration.loadFromJsonFileOrThrow(
`${__dirname}/jsonFiles/pnpm-config-disallow-sha1.json`,
defaultSubspace.getSubspaceTempFolderPath()
);

jest.spyOn(defaultSubspace, 'getPnpmOptions').mockReturnValue(mockPnpmOptions);

const terminalProvider: StringBufferTerminalProvider = new StringBufferTerminalProvider();
const terminal = new Terminal(terminalProvider);

expect(() =>
pnpmShrinkwrapFile.validateShrinkwrapAfterUpdate(
project.rushConfiguration,
project.rushConfiguration.defaultSubspace,
terminal
)
).toThrowError(AlreadyReportedError);
expect({
log: terminalProvider.getOutput(),
warning: terminalProvider.getWarningOutput(),
error: terminalProvider.getErrorOutput(),
verbose: terminalProvider.getVerboseOutput(),
debug: terminalProvider.getDebugOutput()
}).toMatchSnapshot();
});
});
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`PnpmShrinkwrapFile Check is workspace project modified pnpm lockfile major version 9 sha1 integrity can be handled when disallowInsecureSha1 1`] = `
Object {
"debug": "",
"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]",
"log": "",
"verbose": "",
"warning": "",
}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"pnpmLockfilePolicies": {
"disallowInsecureSha1": {
"enabled": true,
"exemptPackageVersions": {
"@some/sha1-pkg": ["1.4.3"],
"other-sha1-pkg": ["2.0.0"],
"fake-with-patch": ["1.0.0"],
"fake-with-peer": ["1.0.0"],
"fake": ["7.8.1"]
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
lockfileVersion: '9.0'

settings:
autoInstallPeers: true
excludeLinksFromLockfile: false

patchedDependencies:
[email protected]:
hash: tta6uuavpnftppsegagihriayy
path: patches/[email protected]

importers:
.:
dependencies:
'@some/sha1-pkg':
specifier: ^1.0.0
version: 1.4.3
other-sha1-pkg:
specifier: ~2.0.0
version: 2.0.0
fake-with-patch:
specifier: 1.0.0
version: 1.0.0
fake-with-peer:
specifier: 1.0.0
version: 1.0.0
fake-with-npm:
specifier: npm:[email protected]
version: [email protected]
fake-non-exempted:
specifier: 1.0.0
version: 1.0.0

packages:
'@some/[email protected]':
resolution: { integrity: sha1-KQzv7h3EqVCA2u7BOFut5Vqbl5o= }

[email protected]:
resolution: { integrity: sha1-+5v5y5gkJ6x2+X1K3pZ5p8W7m4o= }

[email protected]:
resolution: { integrity: sha1-FAKEPATCHINTEGRITY1234567890= }

[email protected]:
resolution: { integrity: sha1-FAKEPEERINTEGRITY0987654321= }

[email protected]:
resolution: { integrity: sha1-FAKEPEERINTEGRITY0987654321= }

[email protected]:
resolution: { integrity: sha1-FAKEPEERINTEGRITY0987654321= }

snapshots:
'@some/[email protected]': {}

[email protected]: {}

[email protected](patch_hash=tta6uuavpnftppsegagihriayy): {}

[email protected](@some/[email protected]):
transitivePeerDependencies:
- '@some/sha1-pkg'

[email protected]: {}

[email protected]: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
lockfileVersion: '9.0'

settings:
autoInstallPeers: true
excludeLinksFromLockfile: false

patchedDependencies:
[email protected]:
hash: tta6uuavpnftppsegagihriayy
path: patches/[email protected]

importers:
.:
dependencies:
'@some/sha1-pkg':
specifier: ^1.0.0
version: 1.4.3
other-sha1-pkg:
specifier: ~2.0.0
version: 2.0.0
fake-with-patch:
specifier: 1.0.0
version: 1.0.0
fake-with-peer:
specifier: 1.0.0
version: 1.0.0
fake-with-npm:
specifier: npm:[email protected]
version: [email protected]

packages:
'@some/[email protected]':
resolution: { integrity: sha1-KQzv7h3EqVCA2u7BOFut5Vqbl5o= }

[email protected]:
resolution: { integrity: sha1-+5v5y5gkJ6x2+X1K3pZ5p8W7m4o= }

[email protected]:
resolution: { integrity: sha1-FAKEPATCHINTEGRITY1234567890= }

[email protected]:
resolution: { integrity: sha1-FAKEPEERINTEGRITY0987654321= }

[email protected]:
resolution: { integrity: sha1-FAKEPEERINTEGRITY0987654321= }

snapshots:
'@some/[email protected]': {}

[email protected]: {}

[email protected](patch_hash=tta6uuavpnftppsegagihriayy): {}

[email protected](@some/[email protected]):
transitivePeerDependencies:
- '@some/sha1-pkg'

[email protected]: {}
Loading