From 93cbc29334b0b69985e6258a7111fe10fef83cb4 Mon Sep 17 00:00:00 2001 From: Aaron Erickson Date: Mon, 16 Mar 2026 18:06:29 -0400 Subject: [PATCH] fix: quote sandbox name in openshell policy commands The sandbox name was interpolated unquoted into shell commands passed to openshell policy set/get. This caused argument parsing failures when the sandbox name or prior shell state introduced unexpected tokens (e.g. 'mkdir' from DGX Spark sudo context). Extracts buildPolicySetCommand/buildPolicyGetCommand helpers that properly quote all arguments, and adds tests to verify. Fixes #46 Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Aaron Erickson --- bin/lib/policies.js | 22 ++++++++++++++++++++-- test/policies.test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/bin/lib/policies.js b/bin/lib/policies.js index 575fcdee27..2f7d1a4209 100644 --- a/bin/lib/policies.js +++ b/bin/lib/policies.js @@ -69,6 +69,20 @@ function parseCurrentPolicy(raw) { return raw.slice(sep + 3).trim(); } +/** + * Build the openshell policy set command with properly quoted arguments. + */ +function buildPolicySetCommand(policyFile, sandboxName) { + return `openshell policy set --policy "${policyFile}" --wait "${sandboxName}"`; +} + +/** + * Build the openshell policy get command with properly quoted arguments. + */ +function buildPolicyGetCommand(sandboxName) { + return `openshell policy get --full "${sandboxName}" 2>/dev/null`; +} + function applyPreset(sandboxName, presetName) { const presetContent = loadPreset(presetName); if (!presetContent) { @@ -86,7 +100,7 @@ function applyPreset(sandboxName, presetName) { let rawPolicy = ""; try { rawPolicy = runCapture( - `openshell policy get --full ${sandboxName} 2>/dev/null`, + buildPolicyGetCommand(sandboxName), { ignoreError: true } ); } catch {} @@ -146,7 +160,7 @@ function applyPreset(sandboxName, presetName) { fs.writeFileSync(tmpFile, merged, "utf-8"); try { - run(`openshell policy set --policy "${tmpFile}" --wait ${sandboxName}`); + run(buildPolicySetCommand(tmpFile, sandboxName)); console.log(` Applied preset: ${presetName}`); } finally { fs.unlinkSync(tmpFile); @@ -175,6 +189,10 @@ module.exports = { listPresets, loadPreset, getPresetEndpoints, + extractPresetEntries, + parseCurrentPolicy, + buildPolicySetCommand, + buildPolicyGetCommand, applyPreset, getAppliedPresets, }; diff --git a/test/policies.test.js b/test/policies.test.js index 3d8f13c380..ec1a021217 100644 --- a/test/policies.test.js +++ b/test/policies.test.js @@ -65,6 +65,32 @@ describe("policies", () => { }); }); + describe("buildPolicySetCommand", () => { + it("quotes sandbox name to prevent argument splitting", () => { + const cmd = policies.buildPolicySetCommand("/tmp/policy.yaml", "my-assistant"); + assert.equal(cmd, 'openshell policy set --policy "/tmp/policy.yaml" --wait "my-assistant"'); + }); + + it("handles sandbox names with spaces", () => { + const cmd = policies.buildPolicySetCommand("/tmp/policy.yaml", "my sandbox"); + assert.ok(cmd.includes('"my sandbox"'), "sandbox name must be quoted"); + }); + + it("places --wait before the sandbox name", () => { + const cmd = policies.buildPolicySetCommand("/tmp/policy.yaml", "test-box"); + const waitIdx = cmd.indexOf("--wait"); + const nameIdx = cmd.indexOf('"test-box"'); + assert.ok(waitIdx < nameIdx, "--wait must come before sandbox name"); + }); + }); + + describe("buildPolicyGetCommand", () => { + it("quotes sandbox name", () => { + const cmd = policies.buildPolicyGetCommand("my-assistant"); + assert.equal(cmd, 'openshell policy get --full "my-assistant" 2>/dev/null'); + }); + }); + describe("preset YAML schema", () => { it("no preset has rules at NetworkPolicyRuleDef level", () => { // rules must be inside endpoints, not as sibling of endpoints/binaries