impel-cli 0.17.17 → 0.18.1

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": "impel-cli",
3
- "version": "0.17.17",
3
+ "version": "0.18.1",
4
4
  "description": "Prepare isolated Claude and Codex workspaces for every accessible Impel tenant",
5
5
  "type": "module",
6
6
  "bin": {
package/src/apps.js CHANGED
@@ -209,7 +209,11 @@ export function managedAppIdentity(target, tenantId = null, tenantName = null) {
209
209
  // slow open path (and thus a full config rewrite) after a CLI update.
210
210
  // 19: Windows global installs bake the stable %LOCALAPPDATA% entry point into
211
211
  // hook/auth/MCP artifacts instead of the npm-prefix bin path.
212
- export const CURRENT_CONFIG_VERSION = 19;
212
+ // 20: managed Claude sandbox disabled by default; the rewrite strips the old
213
+ // strict sandbox keys from already-provisioned profiles.
214
+ // 21: Windows Codex hooks use the pinned client's PowerShell execution
215
+ // contract instead of cmd.exe quoting that failed every managed hook.
216
+ export const CURRENT_CONFIG_VERSION = 21;
213
217
 
214
218
  // Identifies the bundle-BUILDING logic — the asar patches, plist rewrites,
215
219
  // helper rebranding, and signing. A vendored bundle is rebuilt only when this
@@ -1,3 +1,7 @@
1
+ // Every sandbox key impel-cli has ever written into a managed profile. The
2
+ // active policy (below) sets only a subset; apply/restore use this full list to
3
+ // strip keys the policy no longer owns, so a profile provisioned by an older,
4
+ // stricter CLI is fully cleaned up rather than left with inert leftovers.
1
5
  const MANAGED_SANDBOX_KEYS = Object.freeze([
2
6
  "enabled",
3
7
  "failIfUnavailable",
@@ -18,20 +22,18 @@ function sameJsonValue(left, right) {
18
22
  }
19
23
 
20
24
  /**
21
- * Claude Code's strict, auto-allow sandbox profile.
25
+ * Impel's Claude sandbox policy: disabled.
22
26
  *
23
- * Native Windows does not implement Claude's sandbox. Keep it enabled so the
24
- * same profile becomes effective in WSL, but do not make native Windows fail
25
- * at startup solely because the platform cannot initialize it.
27
+ * Managed installs run Bash with the same unrestricted network and filesystem
28
+ * access as a plain Claude Code install. Only `enabled: false` is asserted; all
29
+ * other sandbox keys are left to the user and actively stripped from profiles
30
+ * an older, stricter CLI wrote (see MANAGED_SANDBOX_KEYS). The `platform`
31
+ * argument is retained for signature compatibility with callers and tests.
26
32
  */
33
+ // eslint-disable-next-line no-unused-vars
27
34
  export function impelClaudeSandboxPolicy(platform = process.platform) {
28
35
  return {
29
- enabled: true,
30
- failIfUnavailable: platform !== "win32",
31
- autoAllowBashIfSandboxed: true,
32
- allowUnsandboxedCommands: false,
33
- excludedCommands: [],
34
- ...(platform === "darwin" ? { enableWeakerNetworkIsolation: true } : {}),
36
+ enabled: false,
35
37
  };
36
38
  }
37
39
 
@@ -48,8 +50,13 @@ function applyImpelClaudeNetworkPermission(permissions) {
48
50
  export function applyImpelClaudeSandbox(settings, platform = process.platform) {
49
51
  const current = isJsonObject(settings.sandbox) ? settings.sandbox : {};
50
52
  const policy = impelClaudeSandboxPolicy(platform);
53
+ // Drop every key impel-cli has ever managed before re-applying the policy, so
54
+ // strict keys written by an older CLI (failIfUnavailable, allowUnsandboxed…)
55
+ // don't survive as leftovers alongside the new enabled:false.
56
+ const preserved = { ...current };
57
+ for (const key of MANAGED_SANDBOX_KEYS) delete preserved[key];
51
58
  settings.sandbox = {
52
- ...current,
59
+ ...preserved,
53
60
  ...policy,
54
61
  };
55
62
  settings.permissions = applyImpelClaudeNetworkPermission(settings.permissions);
@@ -141,10 +141,10 @@ function shellQuote(value) {
141
141
  return `'${String(value).replaceAll("'", `'"'"'`)}'`;
142
142
  }
143
143
 
144
- function windowsCommandQuote(value) {
144
+ function powershellQuote(value) {
145
145
  const text = String(value);
146
- if (/[\r\n\0%]/u.test(text)) throw new Error("cannot safely write this Windows hook command");
147
- return `"${text.replaceAll('"', '""')}"`;
146
+ if (/[\r\n\0]/u.test(text)) throw new Error("cannot safely write this Windows hook command");
147
+ return `'${text.replaceAll("'", "''")}'`;
148
148
  }
149
149
 
150
150
  function canonicalize(value) {
@@ -209,10 +209,11 @@ export function ensureCodexSessionHooks(codexHome, tenantId, surface = "codex_cl
209
209
  const document = readJsonObject(hooksPath);
210
210
  const invocation = managedInvocation("codex", surface, tenantId);
211
211
  const unixCommand = [invocation.command, ...invocation.args].map(shellQuote).join(" ");
212
- const windowsTokens = [invocation.command, ...invocation.args].map(windowsCommandQuote).join(" ");
213
- // Codex executes commandWindows with `cmd.exe /C`. The outer quote preserves
214
- // a quoted executable path when it contains spaces.
215
- const windowsCommand = `"${windowsTokens}"`;
212
+ const windowsTokens = [invocation.command, ...invocation.args].map(powershellQuote).join(" ");
213
+ // Codex runs commandWindows through the session's normal Windows shell,
214
+ // which is PowerShell. A quoted executable is only invoked when prefixed by
215
+ // the call operator; cmd.exe's doubled outer-quote form is invalid here.
216
+ const windowsCommand = `& ${windowsTokens}`;
216
217
  const handler = {
217
218
  type: "command",
218
219
  command: unixCommand,