start-command 0.9.0 → 0.10.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/CHANGELOG.md +36 -0
- package/README.md +49 -9
- package/REQUIREMENTS.md +48 -2
- package/experiments/user-isolation-research.md +83 -0
- package/package.json +1 -1
- package/src/bin/cli.js +131 -44
- package/src/lib/args-parser.js +62 -0
- package/src/lib/isolation.js +96 -40
- package/src/lib/user-manager.js +429 -0
- package/test/args-parser.test.js +179 -0
- package/test/isolation.test.js +33 -0
- package/test/user-manager.test.js +286 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,41 @@
|
|
|
1
1
|
# start-command
|
|
2
2
|
|
|
3
|
+
## 0.10.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8ea5659: Add user isolation support with --isolated-user and --keep-user options
|
|
8
|
+
|
|
9
|
+
Implements user isolation that creates a new isolated user to run commands:
|
|
10
|
+
|
|
11
|
+
## --isolated-user option (create isolated user with same permissions)
|
|
12
|
+
- Add --isolated-user, -u option to create a new isolated user automatically
|
|
13
|
+
- New user inherits group memberships from current user (sudo, docker, wheel, etc.)
|
|
14
|
+
- User is automatically deleted after command completes (unless --keep-user)
|
|
15
|
+
- Works with screen and tmux isolation backends (not docker)
|
|
16
|
+
- Optional custom username via --isolated-user=myname or -u myname
|
|
17
|
+
- For screen/tmux: Wraps commands with sudo -n -u <user>
|
|
18
|
+
- Requires sudo NOPASSWD configuration for useradd/userdel/sudo
|
|
19
|
+
|
|
20
|
+
## --keep-user option
|
|
21
|
+
- Add --keep-user option to prevent user deletion after command completes
|
|
22
|
+
- Useful when you need to inspect files created during execution
|
|
23
|
+
- User must be manually deleted with: sudo userdel -r <username>
|
|
24
|
+
|
|
25
|
+
## Other improvements
|
|
26
|
+
- Add comprehensive tests for user isolation
|
|
27
|
+
- Update documentation with user isolation examples
|
|
28
|
+
- Integrate --keep-alive and --auto-remove-docker-container from main branch
|
|
29
|
+
|
|
30
|
+
Usage:
|
|
31
|
+
- $ --isolated-user -- npm test # Auto-generated username, auto-deleted
|
|
32
|
+
- $ --isolated-user myrunner -- npm start # Custom username
|
|
33
|
+
- $ -u myrunner -- npm start # Short form
|
|
34
|
+
- $ --isolated screen --isolated-user -- npm test # Combine with process isolation
|
|
35
|
+
- $ --isolated-user --keep-user -- npm test # Keep user after completion
|
|
36
|
+
|
|
37
|
+
Note: User isolation requires sudo NOPASSWD configuration.
|
|
38
|
+
|
|
3
39
|
## 0.9.0
|
|
4
40
|
|
|
5
41
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -152,6 +152,44 @@ $ --isolated docker --image oven/bun:latest -- bun install
|
|
|
152
152
|
$ -i tmux -s my-session -d bun start
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
+
### User Isolation
|
|
156
|
+
|
|
157
|
+
Create a new isolated user with the same group permissions as your current user to run commands in complete isolation:
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
# Create an isolated user with same permissions and run command
|
|
161
|
+
$ --isolated-user -- npm test
|
|
162
|
+
|
|
163
|
+
# Specify custom username for the isolated user
|
|
164
|
+
$ --isolated-user myrunner -- npm start
|
|
165
|
+
$ -u myrunner -- npm start
|
|
166
|
+
|
|
167
|
+
# Combine with process isolation (screen or tmux)
|
|
168
|
+
$ --isolated screen --isolated-user -- npm test
|
|
169
|
+
|
|
170
|
+
# Keep the user after command completes (don't delete)
|
|
171
|
+
$ --isolated-user --keep-user -- npm start
|
|
172
|
+
|
|
173
|
+
# The isolated user inherits your group memberships:
|
|
174
|
+
# - sudo group (if you have it)
|
|
175
|
+
# - docker group (if you have it)
|
|
176
|
+
# - wheel, admin, and other privileged groups
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
The `--isolated-user` option:
|
|
180
|
+
|
|
181
|
+
- Creates a new system user with the same group memberships as your current user
|
|
182
|
+
- Runs the command as that user
|
|
183
|
+
- Automatically deletes the user after the command completes (unless `--keep-user` is specified)
|
|
184
|
+
- Requires sudo access without password (NOPASSWD configuration)
|
|
185
|
+
- Works with screen and tmux isolation backends (not docker)
|
|
186
|
+
|
|
187
|
+
This is useful for:
|
|
188
|
+
|
|
189
|
+
- Running untrusted code in isolation
|
|
190
|
+
- Testing with a clean user environment
|
|
191
|
+
- Ensuring commands don't affect your user's files
|
|
192
|
+
|
|
155
193
|
#### Supported Backends
|
|
156
194
|
|
|
157
195
|
| Backend | Description | Installation |
|
|
@@ -162,15 +200,17 @@ $ -i tmux -s my-session -d bun start
|
|
|
162
200
|
|
|
163
201
|
#### Isolation Options
|
|
164
202
|
|
|
165
|
-
| Option | Description
|
|
166
|
-
| -------------------------------- |
|
|
167
|
-
| `--isolated, -i` | Isolation backend (screen, tmux, docker)
|
|
168
|
-
| `--attached, -a` | Run in attached/foreground mode (default)
|
|
169
|
-
| `--detached, -d` | Run in detached/background mode
|
|
170
|
-
| `--session, -s` | Custom session/container name
|
|
171
|
-
| `--image` | Docker image (required for docker isolation)
|
|
172
|
-
| `--
|
|
173
|
-
| `--
|
|
203
|
+
| Option | Description |
|
|
204
|
+
| -------------------------------- | --------------------------------------------------------- |
|
|
205
|
+
| `--isolated, -i` | Isolation backend (screen, tmux, docker) |
|
|
206
|
+
| `--attached, -a` | Run in attached/foreground mode (default) |
|
|
207
|
+
| `--detached, -d` | Run in detached/background mode |
|
|
208
|
+
| `--session, -s` | Custom session/container name |
|
|
209
|
+
| `--image` | Docker image (required for docker isolation) |
|
|
210
|
+
| `--isolated-user, -u [name]` | Create isolated user with same permissions (screen/tmux) |
|
|
211
|
+
| `--keep-user` | Keep isolated user after command completes (don't delete) |
|
|
212
|
+
| `--keep-alive, -k` | Keep session alive after command completes |
|
|
213
|
+
| `--auto-remove-docker-container` | Auto-remove docker container after exit (docker only) |
|
|
174
214
|
|
|
175
215
|
**Note:** Using both `--attached` and `--detached` together will result in an error - you must choose one mode.
|
|
176
216
|
|
package/REQUIREMENTS.md
CHANGED
|
@@ -142,6 +142,8 @@ Support two patterns for passing wrapper options:
|
|
|
142
142
|
- `--detached, -d`: Run in detached/background mode
|
|
143
143
|
- `--session, -s <name>`: Custom session name
|
|
144
144
|
- `--image <image>`: Docker image (required for docker backend)
|
|
145
|
+
- `--isolated-user, -u [username]`: Create new isolated user with same group permissions as current user
|
|
146
|
+
- `--keep-user`: Keep isolated user after command completes (don't delete)
|
|
145
147
|
- `--keep-alive, -k`: Keep isolation environment alive after command exits (disabled by default)
|
|
146
148
|
- `--auto-remove-docker-container`: Automatically remove docker container after exit (disabled by default, only valid with --isolated docker)
|
|
147
149
|
|
|
@@ -157,7 +159,50 @@ Support two patterns for passing wrapper options:
|
|
|
157
159
|
- **Detached mode**: Command runs in background, session info displayed for reattachment
|
|
158
160
|
- **Conflict handling**: If both --attached and --detached are specified, show error asking user to choose one
|
|
159
161
|
|
|
160
|
-
#### 6.5
|
|
162
|
+
#### 6.5 User Isolation
|
|
163
|
+
|
|
164
|
+
- `--isolated-user, -u [username]`: Create a new isolated user with same permissions
|
|
165
|
+
- Creates user with same group memberships as current user (sudo, docker, wheel, etc.)
|
|
166
|
+
- Automatically generates username if not specified
|
|
167
|
+
- User is automatically deleted after command completes (unless `--keep-user` is specified)
|
|
168
|
+
- For screen/tmux: Wraps command with `sudo -n -u <username>`
|
|
169
|
+
- Requires sudo NOPASSWD for useradd/userdel commands
|
|
170
|
+
- Works with screen and tmux isolation (not docker)
|
|
171
|
+
|
|
172
|
+
#### 6.6 Keep User Option
|
|
173
|
+
|
|
174
|
+
- `--keep-user`: Keep the isolated user after command completes
|
|
175
|
+
- Only valid with `--isolated-user` option
|
|
176
|
+
- User must be manually deleted later with `sudo userdel -r <username>`
|
|
177
|
+
|
|
178
|
+
Example usage:
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
# Create isolated user and run command (user auto-deleted after)
|
|
182
|
+
$ --isolated-user -- npm test
|
|
183
|
+
|
|
184
|
+
# Custom username for isolated user
|
|
185
|
+
$ --isolated-user myrunner -- npm start
|
|
186
|
+
$ -u myrunner -- npm start
|
|
187
|
+
|
|
188
|
+
# Combine with screen isolation
|
|
189
|
+
$ --isolated screen --isolated-user -- npm test
|
|
190
|
+
|
|
191
|
+
# Combine with tmux detached mode
|
|
192
|
+
$ -i tmux -d --isolated-user testuser -- npm run build
|
|
193
|
+
|
|
194
|
+
# Keep user after command completes
|
|
195
|
+
$ --isolated-user --keep-user -- npm test
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Benefits:
|
|
199
|
+
|
|
200
|
+
- Clean user environment for each run
|
|
201
|
+
- Inherits sudo/docker access from current user
|
|
202
|
+
- Files created during execution belong to isolated user
|
|
203
|
+
- Automatic cleanup after execution (unless --keep-user)
|
|
204
|
+
|
|
205
|
+
#### 6.7 Auto-Exit Behavior
|
|
161
206
|
|
|
162
207
|
By default, all isolation environments (screen, tmux, docker) automatically exit after the target command completes execution. This ensures:
|
|
163
208
|
|
|
@@ -180,10 +225,11 @@ The `--auto-remove-docker-container` flag enables automatic removal of the conta
|
|
|
180
225
|
|
|
181
226
|
Note: `--auto-remove-docker-container` is only valid with `--isolated docker` and is independent of the `--keep-alive` flag.
|
|
182
227
|
|
|
183
|
-
#### 6.
|
|
228
|
+
#### 6.8 Graceful Degradation
|
|
184
229
|
|
|
185
230
|
- If isolation backend is not installed, show informative error with installation instructions
|
|
186
231
|
- If session creation fails, report error with details
|
|
232
|
+
- If sudo fails due to password requirement, command will fail with sudo error
|
|
187
233
|
|
|
188
234
|
## Configuration Options
|
|
189
235
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# User Isolation Research
|
|
2
|
+
|
|
3
|
+
## Issue #30: Support user isolation
|
|
4
|
+
|
|
5
|
+
### Understanding the Requirement
|
|
6
|
+
|
|
7
|
+
Based on the issue description:
|
|
8
|
+
|
|
9
|
+
> We need to find a way to support not only isolation in screen, but also isolation by user at the same time.
|
|
10
|
+
|
|
11
|
+
And the clarification from the user:
|
|
12
|
+
|
|
13
|
+
> No, there is no way to use existing user to run the command, user isolation should mean we create user - run command using this user, after command have finished we can delete user, unless we have `--keep-user` option.
|
|
14
|
+
|
|
15
|
+
This means:
|
|
16
|
+
|
|
17
|
+
1. Running commands in isolated environments (screen, tmux, docker) - **ALREADY IMPLEMENTED**
|
|
18
|
+
2. Creating new isolated users with same permissions as current user - **IMPLEMENTED**
|
|
19
|
+
3. Automatic cleanup of isolated users after command completes - **IMPLEMENTED**
|
|
20
|
+
4. Option to keep the user (`--keep-user`) - **IMPLEMENTED**
|
|
21
|
+
|
|
22
|
+
### Related Issues
|
|
23
|
+
|
|
24
|
+
- Issue #31: Support ssh isolation (execute commands on remote ssh servers)
|
|
25
|
+
- Issue #9: Isolation support (closed - implemented screen/tmux/docker)
|
|
26
|
+
|
|
27
|
+
### Final Implementation
|
|
28
|
+
|
|
29
|
+
The `--isolated-user` option creates a new isolated user with the same group memberships as the current user:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# Create isolated user and run command (user auto-deleted after)
|
|
33
|
+
$ --isolated-user -- npm test
|
|
34
|
+
|
|
35
|
+
# Custom username for isolated user
|
|
36
|
+
$ --isolated-user myrunner -- npm start
|
|
37
|
+
$ -u myrunner -- npm start
|
|
38
|
+
|
|
39
|
+
# Combine with screen isolation
|
|
40
|
+
$ --isolated screen --isolated-user -- npm test
|
|
41
|
+
|
|
42
|
+
# Combine with tmux detached mode
|
|
43
|
+
$ -i tmux -d --isolated-user testuser -- npm run build
|
|
44
|
+
|
|
45
|
+
# Keep user after command completes
|
|
46
|
+
$ --isolated-user --keep-user -- npm test
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### How It Works
|
|
50
|
+
|
|
51
|
+
1. **User Creation**
|
|
52
|
+
- Creates new system user with same group memberships as current user
|
|
53
|
+
- Inherits sudo, docker, wheel, admin, and other groups
|
|
54
|
+
- Uses `sudo useradd` with `-G` flag for groups
|
|
55
|
+
|
|
56
|
+
2. **Command Execution**
|
|
57
|
+
- For screen/tmux: Wraps command with `sudo -n -u <user>`
|
|
58
|
+
- For standalone (no isolation backend): Uses `sudo -n -u <user> sh -c '<command>'`
|
|
59
|
+
|
|
60
|
+
3. **Cleanup**
|
|
61
|
+
- After command completes, user is deleted with `sudo userdel -r <user>`
|
|
62
|
+
- Unless `--keep-user` flag is specified
|
|
63
|
+
|
|
64
|
+
### Requirements
|
|
65
|
+
|
|
66
|
+
- `sudo` access with NOPASSWD configuration for:
|
|
67
|
+
- `useradd` - to create the isolated user
|
|
68
|
+
- `userdel` - to delete the isolated user
|
|
69
|
+
- `sudo -u` - to run commands as the isolated user
|
|
70
|
+
|
|
71
|
+
### Benefits
|
|
72
|
+
|
|
73
|
+
- Clean user environment for each run
|
|
74
|
+
- Inherits sudo/docker access from current user
|
|
75
|
+
- Files created during execution belong to isolated user
|
|
76
|
+
- Automatic cleanup after execution (unless --keep-user)
|
|
77
|
+
- Prevents untrusted code from affecting your user's files
|
|
78
|
+
|
|
79
|
+
### Limitations
|
|
80
|
+
|
|
81
|
+
- Not supported with Docker isolation (Docker has its own user isolation mechanism)
|
|
82
|
+
- Requires sudo NOPASSWD configuration
|
|
83
|
+
- Only works on Unix-like systems (Linux, macOS)
|
package/package.json
CHANGED
package/src/bin/cli.js
CHANGED
|
@@ -15,12 +15,19 @@ const {
|
|
|
15
15
|
} = require('../lib/args-parser');
|
|
16
16
|
const {
|
|
17
17
|
runIsolated,
|
|
18
|
+
runAsIsolatedUser,
|
|
18
19
|
getTimestamp,
|
|
19
20
|
createLogHeader,
|
|
20
21
|
createLogFooter,
|
|
21
22
|
writeLogFile,
|
|
22
23
|
createLogPath,
|
|
23
24
|
} = require('../lib/isolation');
|
|
25
|
+
const {
|
|
26
|
+
createIsolatedUser,
|
|
27
|
+
deleteUser,
|
|
28
|
+
hasSudoAccess,
|
|
29
|
+
getCurrentUserGroups,
|
|
30
|
+
} = require('../lib/user-manager');
|
|
24
31
|
|
|
25
32
|
// Configuration from environment variables
|
|
26
33
|
const config = {
|
|
@@ -211,37 +218,33 @@ function getToolVersion(toolName, versionFlag, verbose = false) {
|
|
|
211
218
|
return firstLine || null;
|
|
212
219
|
}
|
|
213
220
|
|
|
214
|
-
/**
|
|
215
|
-
* Print usage information
|
|
216
|
-
*/
|
|
221
|
+
/** Print usage information */
|
|
217
222
|
function printUsage() {
|
|
218
|
-
console.log(
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
console.log(' $ -i screen -d bun start');
|
|
244
|
-
console.log(' $ --isolated docker --image oven/bun:latest -- bun install');
|
|
223
|
+
console.log(`Usage: $ [options] [--] <command> [args...]
|
|
224
|
+
$ <command> [args...]
|
|
225
|
+
|
|
226
|
+
Options:
|
|
227
|
+
--isolated, -i <env> Run in isolated environment (screen, tmux, docker)
|
|
228
|
+
--attached, -a Run in attached mode (foreground)
|
|
229
|
+
--detached, -d Run in detached mode (background)
|
|
230
|
+
--session, -s <name> Session name for isolation
|
|
231
|
+
--image <image> Docker image (required for docker isolation)
|
|
232
|
+
--isolated-user, -u [name] Create isolated user with same permissions
|
|
233
|
+
--keep-user Keep isolated user after command completes
|
|
234
|
+
--keep-alive, -k Keep isolation environment alive after command exits
|
|
235
|
+
--auto-remove-docker-container Auto-remove docker container after exit
|
|
236
|
+
--version, -v Show version information
|
|
237
|
+
|
|
238
|
+
Examples:
|
|
239
|
+
$ echo "Hello World"
|
|
240
|
+
$ bun test
|
|
241
|
+
$ --isolated tmux -- bun start
|
|
242
|
+
$ -i screen -d bun start
|
|
243
|
+
$ --isolated docker --image oven/bun:latest -- bun install
|
|
244
|
+
$ --isolated-user -- npm test # Create isolated user
|
|
245
|
+
$ -u myuser -- npm start # Custom username
|
|
246
|
+
$ -i screen --isolated-user -- npm test # Combine with process isolation
|
|
247
|
+
$ --isolated-user --keep-user -- npm start`);
|
|
245
248
|
console.log('');
|
|
246
249
|
console.log('Piping with $:');
|
|
247
250
|
console.log(' echo "hi" | $ agent # Preferred - pipe TO $ command');
|
|
@@ -311,8 +314,8 @@ if (!config.disableSubstitutions) {
|
|
|
311
314
|
|
|
312
315
|
// Main execution
|
|
313
316
|
(async () => {
|
|
314
|
-
// Check if running in isolation mode
|
|
315
|
-
if (hasIsolation(wrapperOptions)) {
|
|
317
|
+
// Check if running in isolation mode or with user isolation
|
|
318
|
+
if (hasIsolation(wrapperOptions) || wrapperOptions.user) {
|
|
316
319
|
await runWithIsolation(wrapperOptions, command);
|
|
317
320
|
} else {
|
|
318
321
|
await runDirect(command);
|
|
@@ -330,45 +333,112 @@ async function runWithIsolation(options, cmd) {
|
|
|
330
333
|
const startTime = getTimestamp();
|
|
331
334
|
|
|
332
335
|
// Create log file path
|
|
333
|
-
const logFilePath = createLogPath(environment);
|
|
336
|
+
const logFilePath = createLogPath(environment || 'direct');
|
|
334
337
|
|
|
335
338
|
// Get session name (will be generated by runIsolated if not provided)
|
|
336
339
|
const sessionName =
|
|
337
340
|
options.session ||
|
|
338
|
-
`${environment}-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
341
|
+
`${environment || 'start'}-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
342
|
+
|
|
343
|
+
// Handle --isolated-user option: create a new user with same permissions
|
|
344
|
+
let createdUser = null;
|
|
345
|
+
|
|
346
|
+
if (options.user) {
|
|
347
|
+
// Check for sudo access
|
|
348
|
+
if (!hasSudoAccess()) {
|
|
349
|
+
console.error(
|
|
350
|
+
'Error: --isolated-user requires sudo access without password.'
|
|
351
|
+
);
|
|
352
|
+
console.error(
|
|
353
|
+
'Configure NOPASSWD in sudoers or run with appropriate permissions.'
|
|
354
|
+
);
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Get current user groups to show what will be inherited
|
|
359
|
+
const currentGroups = getCurrentUserGroups();
|
|
360
|
+
const importantGroups = ['sudo', 'docker', 'wheel', 'admin'].filter((g) =>
|
|
361
|
+
currentGroups.includes(g)
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
console.log(`[User Isolation] Creating new user with same permissions...`);
|
|
365
|
+
if (importantGroups.length > 0) {
|
|
366
|
+
console.log(
|
|
367
|
+
`[User Isolation] Inheriting groups: ${importantGroups.join(', ')}`
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Create the isolated user
|
|
372
|
+
const userResult = createIsolatedUser(options.userName);
|
|
373
|
+
if (!userResult.success) {
|
|
374
|
+
console.error(
|
|
375
|
+
`Error: Failed to create isolated user: ${userResult.message}`
|
|
376
|
+
);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
createdUser = userResult.username;
|
|
381
|
+
console.log(`[User Isolation] Created user: ${createdUser}`);
|
|
382
|
+
if (userResult.groups && userResult.groups.length > 0) {
|
|
383
|
+
console.log(
|
|
384
|
+
`[User Isolation] User groups: ${userResult.groups.join(', ')}`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
if (options.keepUser) {
|
|
388
|
+
console.log(`[User Isolation] User will be kept after command completes`);
|
|
389
|
+
}
|
|
390
|
+
console.log('');
|
|
391
|
+
}
|
|
339
392
|
|
|
340
393
|
// Print start message (unified format)
|
|
341
394
|
console.log(`[${startTime}] Starting: ${cmd}`);
|
|
342
395
|
console.log('');
|
|
343
396
|
|
|
344
397
|
// Log isolation info
|
|
345
|
-
|
|
398
|
+
if (environment) {
|
|
399
|
+
console.log(`[Isolation] Environment: ${environment}, Mode: ${mode}`);
|
|
400
|
+
}
|
|
346
401
|
if (options.session) {
|
|
347
402
|
console.log(`[Isolation] Session: ${options.session}`);
|
|
348
403
|
}
|
|
349
404
|
if (options.image) {
|
|
350
405
|
console.log(`[Isolation] Image: ${options.image}`);
|
|
351
406
|
}
|
|
407
|
+
if (createdUser) {
|
|
408
|
+
console.log(`[Isolation] User: ${createdUser} (isolated)`);
|
|
409
|
+
}
|
|
352
410
|
console.log('');
|
|
353
411
|
|
|
354
412
|
// Create log content
|
|
355
413
|
let logContent = createLogHeader({
|
|
356
414
|
command: cmd,
|
|
357
|
-
environment,
|
|
415
|
+
environment: environment || 'direct',
|
|
358
416
|
mode,
|
|
359
417
|
sessionName,
|
|
360
418
|
image: options.image,
|
|
419
|
+
user: createdUser,
|
|
361
420
|
startTime,
|
|
362
421
|
});
|
|
363
422
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
423
|
+
let result;
|
|
424
|
+
|
|
425
|
+
if (environment) {
|
|
426
|
+
// Run in isolation backend (screen, tmux, docker)
|
|
427
|
+
result = await runIsolated(environment, cmd, {
|
|
428
|
+
session: options.session,
|
|
429
|
+
image: options.image,
|
|
430
|
+
detached: mode === 'detached',
|
|
431
|
+
user: createdUser,
|
|
432
|
+
keepAlive: options.keepAlive,
|
|
433
|
+
autoRemoveDockerContainer: options.autoRemoveDockerContainer,
|
|
434
|
+
});
|
|
435
|
+
} else if (createdUser) {
|
|
436
|
+
// Run directly as the created user (no isolation backend)
|
|
437
|
+
result = await runAsIsolatedUser(cmd, createdUser);
|
|
438
|
+
} else {
|
|
439
|
+
// This shouldn't happen in isolation mode, but handle gracefully
|
|
440
|
+
result = { success: false, message: 'No isolation configuration provided' };
|
|
441
|
+
}
|
|
372
442
|
|
|
373
443
|
// Get exit code
|
|
374
444
|
const exitCode =
|
|
@@ -390,6 +460,23 @@ async function runWithIsolation(options, cmd) {
|
|
|
390
460
|
console.log(`Exit code: ${exitCode}`);
|
|
391
461
|
console.log(`Log saved: ${logFilePath}`);
|
|
392
462
|
|
|
463
|
+
// Cleanup: delete the created user if we created one (unless --keep-user)
|
|
464
|
+
if (createdUser && !options.keepUser) {
|
|
465
|
+
console.log('');
|
|
466
|
+
console.log(`[User Isolation] Cleaning up user: ${createdUser}`);
|
|
467
|
+
const deleteResult = deleteUser(createdUser, { removeHome: true });
|
|
468
|
+
if (deleteResult.success) {
|
|
469
|
+
console.log(`[User Isolation] User deleted successfully`);
|
|
470
|
+
} else {
|
|
471
|
+
console.log(`[User Isolation] Warning: ${deleteResult.message}`);
|
|
472
|
+
}
|
|
473
|
+
} else if (createdUser && options.keepUser) {
|
|
474
|
+
console.log('');
|
|
475
|
+
console.log(
|
|
476
|
+
`[User Isolation] Keeping user: ${createdUser} (use 'sudo userdel -r ${createdUser}' to delete)`
|
|
477
|
+
);
|
|
478
|
+
}
|
|
479
|
+
|
|
393
480
|
process.exit(exitCode);
|
|
394
481
|
}
|
|
395
482
|
|
package/src/lib/args-parser.js
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
* --detached, -d Run in detached mode (background)
|
|
12
12
|
* --session, -s <name> Session name for isolation
|
|
13
13
|
* --image <image> Docker image (required for docker isolation)
|
|
14
|
+
* --isolated-user, -u [username] Create isolated user with same permissions (auto-generated name if not specified)
|
|
15
|
+
* --keep-user Keep isolated user after command completes (don't delete)
|
|
14
16
|
* --keep-alive, -k Keep isolation environment alive after command exits
|
|
15
17
|
* --auto-remove-docker-container Automatically remove docker container after exit (disabled by default)
|
|
16
18
|
*/
|
|
@@ -36,6 +38,9 @@ function parseArgs(args) {
|
|
|
36
38
|
detached: false, // Run in detached mode
|
|
37
39
|
session: null, // Session name
|
|
38
40
|
image: null, // Docker image
|
|
41
|
+
user: false, // Create isolated user
|
|
42
|
+
userName: null, // Optional custom username for isolated user
|
|
43
|
+
keepUser: false, // Keep isolated user after command completes (don't delete)
|
|
39
44
|
keepAlive: false, // Keep environment alive after command exits
|
|
40
45
|
autoRemoveDockerContainer: false, // Auto-remove docker container after exit
|
|
41
46
|
};
|
|
@@ -175,6 +180,35 @@ function parseOption(args, index, options) {
|
|
|
175
180
|
return 1;
|
|
176
181
|
}
|
|
177
182
|
|
|
183
|
+
// --isolated-user or -u [optional-username] - creates isolated user with same permissions
|
|
184
|
+
if (arg === '--isolated-user' || arg === '-u') {
|
|
185
|
+
options.user = true;
|
|
186
|
+
// Check if next arg is an optional username (not starting with -)
|
|
187
|
+
if (index + 1 < args.length && !args[index + 1].startsWith('-')) {
|
|
188
|
+
// Check if next arg looks like a username (not a command)
|
|
189
|
+
const nextArg = args[index + 1];
|
|
190
|
+
// If next arg matches username format, consume it
|
|
191
|
+
if (/^[a-zA-Z0-9_-]+$/.test(nextArg) && nextArg.length <= 32) {
|
|
192
|
+
options.userName = nextArg;
|
|
193
|
+
return 2;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return 1;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// --isolated-user=<value>
|
|
200
|
+
if (arg.startsWith('--isolated-user=')) {
|
|
201
|
+
options.user = true;
|
|
202
|
+
options.userName = arg.split('=')[1];
|
|
203
|
+
return 1;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// --keep-user - keep isolated user after command completes
|
|
207
|
+
if (arg === '--keep-user') {
|
|
208
|
+
options.keepUser = true;
|
|
209
|
+
return 1;
|
|
210
|
+
}
|
|
211
|
+
|
|
178
212
|
// --keep-alive or -k
|
|
179
213
|
if (arg === '--keep-alive' || arg === '-k') {
|
|
180
214
|
options.keepAlive = true;
|
|
@@ -241,6 +275,34 @@ function validateOptions(options) {
|
|
|
241
275
|
'--auto-remove-docker-container option is only valid with --isolated docker'
|
|
242
276
|
);
|
|
243
277
|
}
|
|
278
|
+
|
|
279
|
+
// User isolation validation
|
|
280
|
+
if (options.user) {
|
|
281
|
+
// User isolation is not supported with Docker (Docker has its own user mechanism)
|
|
282
|
+
if (options.isolated === 'docker') {
|
|
283
|
+
throw new Error(
|
|
284
|
+
'--isolated-user is not supported with Docker isolation. Docker uses its own user namespace for isolation.'
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
// Validate custom username if provided
|
|
288
|
+
if (options.userName) {
|
|
289
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(options.userName)) {
|
|
290
|
+
throw new Error(
|
|
291
|
+
`Invalid username format for --isolated-user: "${options.userName}". Username should contain only letters, numbers, hyphens, and underscores.`
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
if (options.userName.length > 32) {
|
|
295
|
+
throw new Error(
|
|
296
|
+
`Username too long for --isolated-user: "${options.userName}". Maximum length is 32 characters.`
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Keep-user validation
|
|
303
|
+
if (options.keepUser && !options.user) {
|
|
304
|
+
throw new Error('--keep-user option is only valid with --isolated-user');
|
|
305
|
+
}
|
|
244
306
|
}
|
|
245
307
|
|
|
246
308
|
/**
|