Skip to content

Commit d2f4176

Browse files
committed
fix(git): Do not drop uid/gid when executing in root-owned directory
Fix: npm/cli#624 Fix: npm/cli#642 Fix: npm/cli#514 Infer the ownership of a git command invocation based on the cwd, if one is specified.
1 parent 2cf8f28 commit d2f4176

File tree

2 files changed

+94
-4
lines changed

2 files changed

+94
-4
lines changed

Diff for: lib/util/git.js

+19-4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const promiseRetry = require('promise-retry')
1616
const uniqueFilename = require('unique-filename')
1717
const which = BB.promisify(require('which'))
1818
const semver = require('semver')
19+
const inferOwner = require('infer-owner')
1920

2021
const GOOD_ENV_VARS = new Set([
2122
'GIT_ASKPASS',
@@ -181,10 +182,24 @@ function revs (repo, opts) {
181182
})
182183
}
183184

185+
// infer the owner from the cwd git is operating in, if not the
186+
// process cwd, but only if we're root.
187+
// See: https://github.com/npm/cli/issues/624
188+
module.exports._cwdOwner = cwdOwner
189+
function cwdOwner (gitOpts, opts) {
190+
const isRoot = process.getuid && process.getuid() === 0
191+
if (!isRoot || !gitOpts.cwd) { return Promise.resolve() }
192+
193+
return BB.resolve(inferOwner(gitOpts.cwd).then(owner => {
194+
gitOpts.uid = owner.uid
195+
gitOpts.gid = owner.gid
196+
}))
197+
}
198+
184199
module.exports._exec = execGit
185200
function execGit (gitArgs, gitOpts, opts) {
186201
opts = optCheck(opts)
187-
return checkGit(opts).then(gitPath => {
202+
return BB.resolve(cwdOwner(gitOpts, opts).then(() => checkGit(opts).then(gitPath => {
188203
return promiseRetry((retry, number) => {
189204
if (number !== 1) {
190205
opts.log.silly('pacote', 'Retrying git command: ' + gitArgs.join(' ') + ' attempt # ' + number)
@@ -202,13 +217,13 @@ function execGit (gitArgs, gitOpts, opts) {
202217
maxTimeout: opts['fetch-retry-maxtimeout'],
203218
minTimeout: opts['fetch-retry-mintimeout']
204219
})
205-
})
220+
})))
206221
}
207222

208223
module.exports._spawn = spawnGit
209224
function spawnGit (gitArgs, gitOpts, opts) {
210225
opts = optCheck(opts)
211-
return checkGit(opts).then(gitPath => {
226+
return BB.resolve(cwdOwner(gitOpts, opts).then(() => checkGit(opts).then(gitPath => {
212227
return promiseRetry((retry, number) => {
213228
if (number !== 1) {
214229
opts.log.silly('pacote', 'Retrying git command: ' + gitArgs.join(' ') + ' attempt # ' + number)
@@ -231,7 +246,7 @@ function spawnGit (gitArgs, gitOpts, opts) {
231246
return stdout
232247
})
233248
}, opts.retry)
234-
})
249+
})))
235250
}
236251

237252
module.exports._mkOpts = mkOpts

Diff for: test/git.cwd-owner.js

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
'use strict'
2+
const t = require('tap')
3+
4+
process.getuid = () => 0
5+
process.getgid = () => 0
6+
7+
const requireInject = require('require-inject')
8+
const fs = require('fs')
9+
const { _cwdOwner: cwdOwner } = requireInject('../lib/util/git.js', {
10+
fs: Object.assign({}, fs, {
11+
lstat: (path, cb) => {
12+
if (path === '/var/root/.npm/_cacache/bleep/boop') {
13+
return process.nextTick(() => cb(null, { uid: 0, gid: 0 }))
14+
}
15+
if (path === '/home/user/.npm/_cacache/bleep/boop') {
16+
return process.nextTick(() => cb(null, { uid: 69, gid: 420 }))
17+
}
18+
fs.lstat(path, cb)
19+
},
20+
stat: (path, cb) => {
21+
if (path === '/var/root/.npm/_cacache/bleep/boop') {
22+
return process.nextTick(() => cb(null, { uid: 0, gid: 0 }))
23+
}
24+
if (path === '/home/user/.npm/_cacache/bleep/boop') {
25+
return process.nextTick(() => cb(null, { uid: 69, gid: 420 }))
26+
}
27+
fs.stat(path, cb)
28+
}
29+
})
30+
})
31+
32+
t.test('running in root-owned folder, but with non-root uid/gid opts', t => {
33+
const gitOpts = {
34+
cwd: '/var/root/.npm/_cacache/bleep/boop'
35+
}
36+
const opts = {
37+
uid: 69,
38+
gid: 420
39+
}
40+
return cwdOwner(gitOpts, opts).then(() => {
41+
t.strictSame(gitOpts, {
42+
cwd: '/var/root/.npm/_cacache/bleep/boop',
43+
uid: 0,
44+
gid: 0
45+
})
46+
})
47+
})
48+
49+
t.test('running in non-root-owned folder', t => {
50+
const gitOpts = {
51+
cwd: '/home/user/.npm/_cacache/bleep/boop'
52+
}
53+
const opts = {
54+
uid: 12,
55+
gid: 34
56+
}
57+
return cwdOwner(gitOpts, opts).then(() => {
58+
t.strictSame(gitOpts, {
59+
cwd: '/home/user/.npm/_cacache/bleep/boop',
60+
uid: 69,
61+
gid: 420
62+
})
63+
})
64+
})
65+
66+
t.test('running without a cwd', t => {
67+
const gitOpts = {}
68+
const opts = {
69+
uid: 12,
70+
gid: 34
71+
}
72+
return cwdOwner(gitOpts, opts).then(() => {
73+
t.strictSame(gitOpts, {})
74+
})
75+
})

0 commit comments

Comments
 (0)