-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdirectory.ts
More file actions
71 lines (67 loc) · 2.89 KB
/
directory.ts
File metadata and controls
71 lines (67 loc) · 2.89 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
/**
* @fileoverview Stub-directory creation + permission audit. The
* IPC directory is created with 0o700 so other users can't read or
* plant files; on POSIX we also verify ownership and permission
* bits before any write, since a pre-existing directory could
* belong to a different user or have permissive modes inherited
* from umask.
*/
import process from 'node:process'
import { ErrorCtor } from '../primordials/error'
import { getFs, getPath } from './_internal'
/**
* Ensure IPC directory exists for stub file creation.
* Uses restrictive (0o700) permissions so other users cannot read or write
* stub files. On POSIX, after `mkdir` we verify the directory is owned by
* the current user and not world/group-writable — protects against a
* prior local attacker pre-creating `.socket-ipc/<app>/` with permissive
* modes and planting symlinks for stub filenames. Throws if the directory
* fails the check.
* @internal
*/
export async function ensureIpcDirectory(filePath: string): Promise<void> {
const fs = getFs()
const path = getPath()
const dir = path.dirname(filePath)
await fs.promises.mkdir(dir, { recursive: true, mode: 0o700 })
// Windows skip-path; tested on Windows runners.
/* c8 ignore start */
if (process.platform === 'win32') {
return
}
/* c8 ignore stop */
// oxlint-disable-next-line socket/prefer-exists-sync -- need lstat to discriminate symlink/dir via isDirectory().
const stats = await fs.promises.lstat(dir)
// Defensive: mkdir just succeeded so dir is a directory.
/* c8 ignore start */
if (!stats.isDirectory()) {
throw new ErrorCtor(`IPC path is not a directory: ${dir}`)
}
/* c8 ignore stop */
const getuid = process.getuid
/* c8 ignore next - process.getuid is always present on POSIX. */
const ownUid = typeof getuid === 'function' ? getuid.call(process) : -1
/* c8 ignore next 5 - Cross-user ownership guard fires only if the
IPC directory was created by a different uid; can't be triggered
in-test without spawning a separate user. */
if (ownUid !== -1 && stats.uid !== ownUid) {
throw new ErrorCtor(
`IPC directory ${dir} is owned by another user (uid ${stats.uid}); refusing to use it.`,
)
}
// Permission bits only (mask out file-type bits). Reject any group or
// other access — only owner bits may be set.
// eslint-disable-next-line no-bitwise
const mode = stats.mode & 0o777
/* c8 ignore next 7 - chmod-tightening fires only if umask leaves
group/other bits set; default Node umask 0o022 strips group-write
but keeps group/other read+execute, so the value depends on the
CI runner's umask. */
// eslint-disable-next-line no-bitwise
if ((mode & 0o077) !== 0) {
// Tighten an over-permissive directory we just inherited. Use chmod
// rather than fail outright so a first-run that inherits e.g. 0o755
// from umask still succeeds.
await fs.promises.chmod(dir, 0o700)
}
}