diff --git a/lib/index.js b/lib/index.js index 9381157..84ddc83 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,4 +1,5 @@ const { spawn } = require('child_process') +const inferOwner = require('infer-owner') const isPipe = (stdio = 'pipe', fd) => stdio === 'pipe' || stdio === null ? true @@ -8,9 +9,13 @@ const isPipe = (stdio = 'pipe', fd) => // 'extra' object is for decorating the error a bit more const promiseSpawn = (cmd, args, opts = {}, extra = {}) => { const cwd = opts.cwd || process.cwd() + const isRoot = process.getuid && process.getuid() === 0 + const { uid, gid } = isRoot ? inferOwner.sync(cwd) : {} return promiseSpawnUid(cmd, args, { ...opts, cwd, + uid, + gid, }, extra) } diff --git a/package.json b/package.json index 0ff9eea..ce6a807 100644 --- a/package.json +++ b/package.json @@ -41,5 +41,8 @@ "templateOSS": { "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", "version": "3.2.2" + }, + "dependencies": { + "infer-owner": "^1.0.4" } } diff --git a/test/promise-spawn.js b/test/promise-spawn.js index 7b9a6fb..3658077 100644 --- a/test/promise-spawn.js +++ b/test/promise-spawn.js @@ -1,6 +1,7 @@ const t = require('tap') const Minipass = require('minipass') const EE = require('events') +const fs = require('fs') const isPipe = (stdio = 'pipe', fd) => stdio === 'pipe' || stdio === null ? true @@ -214,3 +215,33 @@ t.test('expose process', t => { t.end() setTimeout(() => p.process.exit(0)) }) + +t.test('infer ownership', t => { + const { lstatSync } = fs + t.teardown(() => fs.lstatSync = lstatSync) + fs.lstatSync = (path) => ({ uid: 420, gid: 69 }) + const getuid = process.getuid + t.teardown(() => process.getuid = getuid) + + t.test('as non-root, do not change uid/gid, regardless of arguments', t => { + process.getuid = () => 1234 + return t.resolveMatch(promiseSpawn('whoami', [], { uid: 4321, gid: 9876 }), { + code: 0, + signal: null, + stdout: Buffer.from('UID undefined\nGID undefined\n'), + stderr: Buffer.alloc(0), + }) + }) + + t.test('as root, change uid/gid to folder, regardless of arguments', t => { + process.getuid = () => 0 + return t.resolveMatch(promiseSpawn('whoami', [], { uid: 4321, gid: 9876 }), { + code: 0, + signal: null, + stdout: Buffer.from('UID 420\nGID 69\n'), + stderr: Buffer.alloc(0), + }) + }) + + t.end() +})