mirror of
https://github.com/pnpm/pnpm.git
synced 2026-06-28 01:45:30 -04:00
Resolves the 15 open alerts on https://github.com/pnpm/pnpm/security/code-scanning by addressing all four categories that CodeQL flagged. ### Prototype-polluting assignment (3 alerts, product code) - `pkg-manifest/utils/src/convertEnginesRuntimeToDependencies.ts`: the inner write now dispatches over a literal `switch` on `runtimeName`, so the assignment is always keyed by `'node' | 'deno' | 'bun'`. - `pkg-manifest/utils/src/updateProjectManifestObject.ts`: added an `isProtoPollutionKey` barrier at the top of the loop so `packageSpec.alias` can never reach the dynamic property write with `__proto__` / `constructor` / `prototype`. - `installing/deps-installer/src/uninstall/removeDeps.ts`: the package list is filtered through `isProtoPollutionKey` once up front, and the dependency record is captured into a local before the loop. ### Polynomial ReDoS (2 alerts) - `deps/inspection/list/src/renderDependentsTree.ts`: `replace(/\n+$/, '')` swapped for a constant-time `charCodeAt` trim. - `resolving/npm-resolver/src/fetch.ts`: removed the super-linear-backtracking `semverRegex` and replaced it with an O(n) `stripTrailingSemverSuffix` that splits on the rightmost `@` and `semver.valid`s, with a digit-block fallback so `foo1.0.0`-style names still produce the existing "Did you mean foo?" hint. ### Bad code sanitization (8 alerts, test infrastructure) - `__utils__/test-ipc-server/src/TestIpcServer.ts`: the `JSON.stringify(...).slice(1, -1)` smell at the source of all 8 test-file alerts is gone. Both `sendLineScript` and `generateSendStdinScript` now build the JS source with plain `JSON.stringify` and delegate shell wrapping to a new `wrapNodeEval` helper that escapes `\\` and `"` for the outer double-quoted shell argument. ### Incomplete sanitization (2 alerts, test file) - `releasing/commands/test/publish/oidcProvenance.test.ts`: `.replace('/', '%2f')` → `.replaceAll(...)` on both flagged lines.
60 lines
2.3 KiB
TypeScript
60 lines
2.3 KiB
TypeScript
import { describe, expect, test } from '@jest/globals'
|
|
|
|
import { RegistryResponseError } from '../src/fetch.js'
|
|
|
|
const request = { url: 'https://registry.npmjs.org/foo' }
|
|
const notFoundResponse = { status: 404, statusText: 'Not Found' }
|
|
|
|
describe('RegistryResponseError hint', () => {
|
|
test('suggests stripped name when pkg has "@<version>" suffix', () => {
|
|
const err = new RegistryResponseError(request, notFoundResponse, 'lodash@4.17.21')
|
|
expect(err.hint).toContain('Did you mean lodash?')
|
|
})
|
|
|
|
test('suggests stripped name when pkg has trailing "X.Y.Z" without "@"', () => {
|
|
const err = new RegistryResponseError(request, notFoundResponse, 'lodash4.17.21')
|
|
expect(err.hint).toContain('Did you mean lodash?')
|
|
})
|
|
|
|
test('handles scoped names with version suffix', () => {
|
|
const err = new RegistryResponseError(request, notFoundResponse, '@scope/foo@1.2.3')
|
|
expect(err.hint).toContain('Did you mean @scope/foo?')
|
|
})
|
|
|
|
test('does not add a suggestion for bare scoped names', () => {
|
|
const err = new RegistryResponseError(request, notFoundResponse, '@scope/foo')
|
|
expect(err.hint).not.toMatch(/Did you mean/)
|
|
})
|
|
|
|
test('does not add a suggestion when the prefix would be empty', () => {
|
|
const err = new RegistryResponseError(request, notFoundResponse, '1.0.0')
|
|
expect(err.hint).not.toMatch(/Did you mean/)
|
|
})
|
|
|
|
test('does not add a suggestion for a plain unversioned name', () => {
|
|
const err = new RegistryResponseError(request, notFoundResponse, 'foo')
|
|
expect(err.hint).not.toMatch(/Did you mean/)
|
|
})
|
|
|
|
test('does not emit a hint on non-404 responses', () => {
|
|
const err = new RegistryResponseError(
|
|
request,
|
|
{ status: 500, statusText: 'Internal Server Error' },
|
|
'foo@1.0.0'
|
|
)
|
|
expect(err.hint ?? '').not.toMatch(/Did you mean/)
|
|
})
|
|
|
|
test('stays linear under adversarial input', () => {
|
|
// The previous implementation used a regex with super-linear backtracking.
|
|
// 10k repetitions of "1" should still complete promptly.
|
|
const pkgName = '1'.repeat(10_000)
|
|
const start = Date.now()
|
|
const err = new RegistryResponseError(request, notFoundResponse, pkgName)
|
|
const elapsed = Date.now() - start
|
|
expect(elapsed).toBeLessThan(1_000)
|
|
// The all-digits input has no usable name prefix, so no suggestion is added.
|
|
expect(err.hint).not.toMatch(/Did you mean/)
|
|
})
|
|
})
|