-
Notifications
You must be signed in to change notification settings - Fork 42
Expand file tree
/
Copy pathpath-resolve.mts
More file actions
143 lines (134 loc) · 5 KB
/
path-resolve.mts
File metadata and controls
143 lines (134 loc) · 5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
import { existsSync } from 'node:fs'
import path from 'node:path'
import { resolveRealBinSync, whichRealSync } from '@socketsecurity/lib/bin'
import { NPM } from '@socketsecurity/lib/constants/agents'
import { WIN32 } from '@socketsecurity/lib/constants/platform'
import { isDirSync } from '@socketsecurity/lib/fs'
import {
createSupportedFilesFilter,
globWithGitIgnore,
pathsToGlobPatterns,
} from './glob.mts'
import { NODE_MODULES } from '../../constants/packages.mts'
import { shadowBinPath } from '../../constants/paths.mts'
import type { SocketYml } from '@socketsecurity/config'
import type { SocketSdkSuccessResult } from '@socketsecurity/sdk'
export function findBinPathDetailsSync(binName: string): {
name: string
path: string | undefined
shadowed: boolean
} {
const rawBinPaths =
whichRealSync(binName, {
all: true,
nothrow: true,
}) ?? []
// whichRealSync may return a string when only one result is found, even with all: true.
// This handles both the current published version and future versions.
const binPaths = Array.isArray(rawBinPaths)
? rawBinPaths
: typeof rawBinPaths === 'string'
? [rawBinPaths]
: []
let shadowIndex = -1
let theBinPath: string | undefined
for (let i = 0, { length } = binPaths; i < length; i += 1) {
const binPath = binPaths[i]!
// Skip our bin directory if it's in the front.
if (path.dirname(binPath) === shadowBinPath) {
shadowIndex = i
} else {
theBinPath = resolveRealBinSync(binPath)
break
}
}
return { name: binName, path: theBinPath, shadowed: shadowIndex !== -1 }
}
export function findNpmDirPathSync(npmBinPath: string): string | undefined {
// On Windows, reject Unix absolute paths (starting with / but not //).
// This allows UNC paths: //server/share, \\server\share.
// And long paths: \\?\C:\..., //?/C:/...
// Backslash paths (\\...) don't match startsWith('/') so they pass through.
if (WIN32 && npmBinPath.startsWith('/') && !npmBinPath.startsWith('//')) {
return undefined
}
const MAX_ITERATIONS = 100
let thePath = npmBinPath
let iterations = 0
while (true) {
if (iterations >= MAX_ITERATIONS) {
throw new Error(
`path traversal exceeded maximum iterations of ${MAX_ITERATIONS}`,
)
}
iterations += 1
const libNmNpmPath = path.join(thePath, `lib/${NODE_MODULES}/${NPM}`)
// mise, which uses opaque binaries, puts its npm bin in a path like:
// /Users/SomeUsername/.local/share/mise/installs/node/vX.X.X/bin/npm.
// HOWEVER, the location of the npm install is:
// /Users/SomeUsername/.local/share/mise/installs/node/vX.X.X/lib/node_modules/npm.
if (
// Use existsSync here because statsSync, even with { throwIfNoEntry: false },
// will throw an ENOTDIR error for paths like ./a-file-that-exists/a-directory-that-does-not.
// See https://github.com/nodejs/node/issues/56993.
isDirSync(libNmNpmPath)
) {
thePath = libNmNpmPath
}
const hasNmInCurrPath = isDirSync(path.join(thePath, NODE_MODULES))
const hasNmInParentPath =
!hasNmInCurrPath && isDirSync(path.join(thePath, `../${NODE_MODULES}`))
if (
// npm bin paths may look like:
// /usr/local/share/npm/bin/npm
// ~/.nvm/versions/node/vX.X.X/bin/npm
// %USERPROFILE%\AppData\Roaming\npm\bin\npm.cmd
// OR
// C:\Program Files\nodejs\npm.cmd
//
// In practically all cases the npm path contains a node_modules folder:
// /usr/local/share/npm/bin/npm/node_modules
// C:\Program Files\nodejs\node_modules
(hasNmInCurrPath ||
// In some bespoke cases the node_modules folder is in the parent directory.
hasNmInParentPath) &&
// Optimistically look for the default location.
(path.basename(thePath) === NPM ||
// Chocolatey installs npm bins in the same directory as node bins.
(!!WIN32 && existsSync(path.join(thePath, `${NPM}.cmd`))))
) {
return hasNmInParentPath ? path.dirname(thePath) : thePath
}
const parent = path.dirname(thePath)
if (parent === thePath) {
return undefined
}
thePath = parent
}
}
export type PackageFilesForScanOptions = {
cwd?: string | undefined
config?: SocketYml | undefined
}
export async function getPackageFilesForScan(
inputPaths: string[],
supportedFiles: SocketSdkSuccessResult<'getReportSupportedFiles'>['data'],
options?: PackageFilesForScanOptions | undefined,
): Promise<string[]> {
const { config: socketConfig, cwd = process.cwd() } = {
__proto__: null,
...options,
} as PackageFilesForScanOptions
// Apply the supported files filter during streaming to avoid accumulating
// all files in memory. This is critical for large monorepos with 100k+ files
// where accumulating all paths before filtering causes OOM errors.
const filter = createSupportedFilesFilter(supportedFiles)
return await globWithGitIgnore(
pathsToGlobPatterns(inputPaths, options?.cwd),
{
cwd,
filter,
socketConfig,
},
)
}