isexe 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "isexe",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "Minimal module to check if a file is executable.",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/mjs/index.js",
package/src/options.ts CHANGED
@@ -20,6 +20,13 @@ export interface IsexeOptions {
20
20
  */
21
21
  gid?: number
22
22
 
23
+ /**
24
+ * effective group ID list to use when checking executable mode flags
25
+ * on posix
26
+ * Defaults to process.getgroups()
27
+ */
28
+ groups?: number[]
29
+
23
30
  /**
24
31
  * The ;-delimited path extension list for win32 implementation.
25
32
  * Defaults to process.env.PATHEXT
package/src/posix.ts CHANGED
@@ -31,7 +31,10 @@ export const isexe = async (
31
31
  * Synchronously determine whether a path is executable according to
32
32
  * the mode and current (or specified) user and group IDs.
33
33
  */
34
- export const sync = (path: string, options: IsexeOptions = {}): boolean => {
34
+ export const sync = (
35
+ path: string,
36
+ options: IsexeOptions = {}
37
+ ): boolean => {
35
38
  const { ignoreErrors = false } = options
36
39
  try {
37
40
  return checkStat(statSync(path), options)
@@ -46,23 +49,19 @@ const checkStat = (stat: Stats, options: IsexeOptions) =>
46
49
  stat.isFile() && checkMode(stat, options)
47
50
 
48
51
  const checkMode = (stat: Stats, options: IsexeOptions) => {
49
- if (!process.getuid || !process.getgid) {
52
+ const myUid = options.uid ?? process.getuid?.()
53
+ const myGroups = options.groups ?? process.getgroups?.() ?? []
54
+ const myGid = options.gid ?? process.getgid?.() ?? myGroups[0]
55
+ if (myUid === undefined || myGid === undefined) {
50
56
  throw new Error('cannot get uid or gid')
51
57
  }
58
+
59
+ const groups = new Set([myGid, ...myGroups])
60
+
52
61
  const mod = stat.mode
53
62
  const uid = stat.uid
54
63
  const gid = stat.gid
55
64
 
56
- const myUid =
57
- options.uid !== undefined
58
- ? options.uid
59
- : process.getuid && process.getuid()
60
-
61
- const myGid =
62
- options.gid !== undefined
63
- ? options.gid
64
- : process.getgid && process.getgid()
65
-
66
65
  const u = parseInt('100', 8)
67
66
  const g = parseInt('010', 8)
68
67
  const o = parseInt('001', 8)
@@ -70,7 +69,7 @@ const checkMode = (stat: Stats, options: IsexeOptions) => {
70
69
 
71
70
  return !!(
72
71
  mod & o ||
73
- (mod & g && gid === myGid) ||
72
+ (mod & g && groups.has(gid)) ||
74
73
  (mod & u && uid === myUid) ||
75
74
  (mod & ug && myUid === 0)
76
75
  )
@@ -6,23 +6,26 @@ export const createFixtures = (t: Tap.Test) => {
6
6
  'meow.cat': '#!/usr/bin/env cat\nmeow\n',
7
7
  'mine.cat':'#!/usr/bin/env cat\nmine\n',
8
8
  'ours.cat':'#!/usr/bin/env cat\nours\n',
9
+ 'others.cat':'#!/usr/bin/env cat\nothers\n',
9
10
  'fail.false':'#!/usr/bin/env false\n',
10
11
  })
11
12
 
12
13
  const meow = resolve(dir, 'meow.cat')
13
14
  const mine = resolve(dir, 'mine.cat')
14
15
  const ours = resolve(dir, 'ours.cat')
16
+ const others = resolve(dir, 'others.cat')
15
17
  const fail = resolve(dir, 'fail.false')
16
18
  const enoent = resolve(dir, 'enoent.exe')
17
19
  const modes = {
18
20
  [meow]: 0o755,
19
21
  [mine]: 0o744,
20
22
  [ours]:0o754,
23
+ [others]:0o754,
21
24
  [fail]:0o644,
22
25
  }
23
26
  for (const [path, mode] of Object.entries(modes)) {
24
27
  chmodSync(path, mode)
25
28
  }
26
29
 
27
- return { meow, mine, ours, fail, enoent, modes }
30
+ return { meow, mine, ours, others, fail, enoent, modes }
28
31
  }
package/test/posix.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import t from 'tap'
2
2
 
3
3
  import { createFixtures } from './fixtures/index'
4
- const { meow, fail, mine, ours, enoent, modes } = createFixtures(t)
4
+ const { meow, fail, mine, ours, others, enoent, modes } = createFixtures(t)
5
5
 
6
6
  const isWindows = process.platform === 'win32'
7
7
 
@@ -13,20 +13,21 @@ import * as fsPromises from 'fs/promises'
13
13
  // uid/gid comparisons
14
14
  const mockStat = (path: string, st: Stats) =>
15
15
  Object.assign(st, {
16
- uid: 123,
17
- gid: 321,
16
+ uid: path === others || path === ours ? 987 : 123,
17
+ gid: path === others ? 987 : 321,
18
18
  mode: !modes[path] ? st.mode : setMode(st.mode, modes[path]),
19
19
  })
20
20
 
21
21
  const setMode = (orig: number, mode: number) => (orig & 0o7777000) | mode
22
22
 
23
- const { getuid, getgid } = process
23
+ const { getuid, getgid, getgroups } = process
24
24
  t.teardown(() => {
25
- Object.assign(process, { getuid, getgid })
25
+ Object.assign(process, { getuid, getgid, getgroups })
26
26
  })
27
27
  Object.assign(process, {
28
28
  getuid: () => 123,
29
29
  getgid: () => 321,
30
+ getgroups: () => [321, 987],
30
31
  })
31
32
 
32
33
  const { isexe, sync } = t.mock('../dist/cjs/posix.js', {
@@ -44,11 +45,13 @@ const { isexe, sync } = t.mock('../dist/cjs/posix.js', {
44
45
  t.test('basic tests', async t => {
45
46
  t.equal(await isexe(meow), true)
46
47
  t.equal(await isexe(ours), true)
48
+ t.equal(await isexe(others), true)
47
49
  t.equal(await isexe(mine), true)
48
50
  t.equal(await isexe(fail), false)
49
51
 
50
52
  t.equal(sync(meow), true)
51
53
  t.equal(sync(ours), true)
54
+ t.equal(sync(others), true)
52
55
  t.equal(sync(mine), true)
53
56
  t.equal(sync(fail), false)
54
57
 
@@ -60,51 +63,75 @@ t.test('basic tests', async t => {
60
63
 
61
64
  t.test('override uid/gid', async t => {
62
65
  t.test('same uid, different gid', async t => {
63
- const o = { gid: 654 }
64
- t.equal(await isexe(meow), true)
65
- t.equal(await isexe(ours), true)
66
- t.equal(await isexe(mine), true)
67
- t.equal(await isexe(fail), false)
68
- t.equal(sync(meow), true)
69
- t.equal(sync(ours), true)
70
- t.equal(sync(mine), true)
71
- t.equal(sync(fail), false)
66
+ const o = { gid: 654, groups: [] }
67
+ t.equal(await isexe(meow, o), true)
68
+ t.equal(await isexe(ours, o), false)
69
+ t.equal(await isexe(others, o), false)
70
+ t.equal(await isexe(mine, o), true)
71
+ t.equal(await isexe(fail, o), false)
72
+ t.equal(sync(meow, o), true)
73
+ t.equal(sync(ours, o), false)
74
+ t.equal(sync(others, o), false)
75
+ t.equal(sync(mine, o), true)
76
+ t.equal(sync(fail, o), false)
72
77
  })
73
78
 
74
79
  t.test('different uid, same gid', async t => {
75
80
  const o = { uid: 456 }
76
81
  t.equal(await isexe(meow, o), true)
77
82
  t.equal(await isexe(ours, o), true)
83
+ t.equal(await isexe(others, o), true)
78
84
  t.equal(await isexe(mine, o), false)
79
85
  t.equal(await isexe(fail, o), false)
80
86
  t.equal(sync(meow, o), true)
81
87
  t.equal(sync(ours, o), true)
88
+ t.equal(sync(others, o), true)
82
89
  t.equal(sync(mine, o), false)
83
90
  t.equal(sync(fail, o), false)
84
91
  })
85
92
 
86
93
  t.test('different uid, different gid', async t => {
87
- const o = { uid: 456, gid: 654 }
94
+ const o = { uid: 456, gid: 654, groups: [] }
88
95
  t.equal(await isexe(meow, o), true)
89
96
  t.equal(await isexe(ours, o), false)
97
+ t.equal(await isexe(others, o), false)
90
98
  t.equal(await isexe(mine, o), false)
91
99
  t.equal(await isexe(fail, o), false)
92
100
  t.equal(sync(meow, o), true)
93
101
  t.equal(sync(ours, o), false)
102
+ t.equal(sync(others, o), false)
94
103
  t.equal(sync(mine, o), false)
95
104
  t.equal(sync(fail, o), false)
96
105
  })
106
+
107
+ t.test('root can run anything runnable', async t => {
108
+ const o = { uid: 0, gid: 999, groups: [] }
109
+ t.equal(await isexe(meow, o), true)
110
+ t.equal(await isexe(ours, o), true)
111
+ t.equal(await isexe(others, o), true)
112
+ t.equal(await isexe(mine, o), true)
113
+ t.equal(await isexe(fail, o), false)
114
+ t.equal(sync(meow, o), true)
115
+ t.equal(sync(ours, o), true)
116
+ t.equal(sync(others, o), true)
117
+ t.equal(sync(mine, o), true)
118
+ t.equal(sync(fail, o), false)
119
+ })
97
120
  })
98
121
 
99
122
  t.test('getuid/getgid required', async t => {
100
- const { getuid, getgid } = process
101
- t.teardown(() => { Object.assign(process, { getuid, getgid })})
123
+ const { getuid, getgid, getgroups } = process
124
+ t.teardown(() => { Object.assign(process, { getuid, getgid, getgroups })})
102
125
  process.getuid = undefined
103
126
  process.getgid = undefined
127
+ process.getgroups = undefined
104
128
  t.throws(() => sync(meow), {
105
129
  message: 'cannot get uid or gid'
106
130
  })
107
131
  t.rejects(isexe(meow), {
108
132
  message: 'cannot get uid or gid'
109
133
  })
134
+ // fine as long as a group/user is specified though
135
+ t.equal(sync(meow, { uid: 999, groups: [321] }), true)
136
+ t.equal(await isexe(meow, { uid: 999, groups: [321] }), true)
110
137
  })