Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 40 additions & 80 deletions bin/lib/onboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -3091,62 +3091,33 @@ async function _setupPolicies(sandboxName) {
}
}
} else {
console.log("");
console.log(" Available policy presets:");
allPresets.forEach((p) => {
const marker = applied.includes(p.name) || suggestions.includes(p.name) ? "●" : "○";
const suggested = suggestions.includes(p.name) ? " (suggested)" : "";
console.log(` ${marker} ${p.name} — ${p.description}${suggested}`);
});
console.log("");

const answer = await prompt(
` Apply suggested presets (${suggestions.join(", ")})? [Y/n/list]: `,
);

if (answer.toLowerCase() === "n") {
console.log(" Skipping policy presets.");
return;
const { selectAndMerge } = require("./policy-wizard-static");
let mergedYaml;
try {
mergedYaml = await selectAndMerge();
} catch (err) {
if (err && err.isCancellation) {
console.log(" Policy selection cancelled by user.");
return;
}
console.error(` Policy selection failed: ${err && err.stack ? err.stack : err}`);
process.exit(1);
}

if (!waitForSandboxReady(sandboxName)) {
console.error(` Sandbox '${sandboxName}' was not ready for policy application.`);
process.exit(1);
}

if (answer.toLowerCase() === "list") {
// Let user pick
const picks = await prompt(" Enter preset names (comma-separated): ");
const selected = picks
.split(",")
.map((s) => s.trim())
.filter(Boolean);
for (const name of selected) {
try {
policies.applyPreset(sandboxName, name);
} catch (err) {
const message = err && err.message ? err.message : String(err);
if (message.includes("Unimplemented")) {
console.error(" OpenShell policy updates are not supported by this gateway build.");
console.error(" This is a known issue tracked in NemoClaw #536.");
}
throw err;
}
}
} else {
// Apply suggested
for (const name of suggestions) {
try {
policies.applyPreset(sandboxName, name);
} catch (err) {
const message = err && err.message ? err.message : String(err);
if (message.includes("Unimplemented")) {
console.error(" OpenShell policy updates are not supported by this gateway build.");
console.error(" This is a known issue tracked in NemoClaw #536.");
}
throw err;
}
try {
policies.applyPresetFromContent(sandboxName, mergedYaml);
} catch (err) {
const message = err && err.message ? err.message : String(err);
if (message.includes("Unimplemented")) {
console.error(" OpenShell policy updates are not supported by this gateway build.");
console.error(" This is a known issue tracked in NemoClaw #536.");
}
throw err;
}
}

Expand Down Expand Up @@ -3251,47 +3222,36 @@ async function setupPoliciesWithSelection(sandboxName, options = {}) {
return chosen;
}

console.log("");
console.log(" Available policy presets:");
allPresets.forEach((p) => {
const marker = applied.includes(p.name) ? "●" : "○";
const suggested = suggestions.includes(p.name) ? " (suggested)" : "";
console.log(` ${marker} ${p.name} — ${p.description}${suggested}`);
});
console.log("");

const answer = await prompt(
` Apply suggested presets (${suggestions.join(", ")})? [Y/n/list]: `,
);

if (answer.toLowerCase() === "n") {
console.log(" Skipping policy presets.");
return [];
}

let interactiveChoice = suggestions;
if (answer.toLowerCase() === "list") {
const custom = await prompt(" Enter preset names (comma-separated): ");
interactiveChoice = parsePolicyPresetEnv(custom);
}

const knownPresets = new Set(allPresets.map((p) => p.name));
const invalidPresets = interactiveChoice.filter((name) => !knownPresets.has(name));
if (invalidPresets.length > 0) {
console.error(` Unknown policy preset(s): ${invalidPresets.join(", ")}`);
const { selectAndMerge } = require("./policy-wizard-static");
let mergedYaml;
try {
mergedYaml = await selectAndMerge();
} catch (err) {
if (err && err.isCancellation) {
console.log(" Policy selection cancelled by user.");
return [];
}
console.error(` Policy selection failed: ${err && err.stack ? err.stack : err}`);
process.exit(1);
}

if (onSelection) onSelection(interactiveChoice);
if (onSelection) onSelection(["custom"]);
if (!waitForSandboxReady(sandboxName)) {
console.error(` Sandbox '${sandboxName}' was not ready for policy application.`);
process.exit(1);
}

for (const name of interactiveChoice) {
policies.applyPreset(sandboxName, name);
try {
policies.applyPresetFromContent(sandboxName, mergedYaml);
} catch (err) {
const message = err && err.message ? err.message : String(err);
if (message.includes("Unimplemented")) {
console.error(" OpenShell policy updates are not supported by this gateway build.");
console.error(" This is a known issue tracked in NemoClaw #536.");
}
throw err;
}
return interactiveChoice;
return ["custom"];
}

// ── Dashboard ────────────────────────────────────────────────────
Expand Down
43 changes: 43 additions & 0 deletions bin/lib/policies.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,48 @@ function mergePresetIntoPolicy(currentPolicy, presetEntries) {

return YAML.stringify(output);
}
function applyPresetFromContent(sandboxName, content) {
const isRfc1123Label = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(sandboxName);
if (!sandboxName || sandboxName.length > 63 || !isRfc1123Label) {
throw new Error(
`Invalid or truncated sandbox name: '${sandboxName}'. ` +
`Names must be 1-63 chars, lowercase alphanumeric, with optional internal hyphens.`,
);
}

const presetEntries = extractPresetEntries(content);
if (!presetEntries) throw new Error("Provided policy content has no network_policies section.");

let rawPolicy = "";
try {
rawPolicy = runCapture(buildPolicyGetCommand(sandboxName), { ignoreError: true });
} catch { /* ignored */ }

const merged = mergePresetIntoPolicy(rawPolicy, presetEntries);
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "nemoclaw-policy-"));
const tmpFile = path.join(tmpDir, "policy.yaml");
fs.writeFileSync(tmpFile, merged, { encoding: "utf-8", mode: 0o600 });

try {
run(buildPolicySetCommand(tmpFile, sandboxName));

const presetName = YAML.parse(content)?.preset?.name;
if (presetName) {
const sandbox = registry.getSandbox(sandboxName);
if (sandbox) {
const pols = sandbox.policies || [];
if (!pols.includes(presetName)) {
pols.push(presetName);
}
registry.updateSandbox(sandboxName, { policies: pols });
}
}
} finally {
try { fs.unlinkSync(tmpFile); } catch { /* ignored */ }
try { fs.rmdirSync(tmpDir); } catch { /* ignored */ }
}
}

function applyPreset(sandboxName, presetName) {
// Guard against truncated sandbox names — WSL can truncate hyphenated
// names during argument parsing, e.g. "my-assistant" → "m"
Expand Down Expand Up @@ -299,5 +341,6 @@ module.exports = {
buildPolicyGetCommand,
mergePresetIntoPolicy,
applyPreset,
applyPresetFromContent,
getAppliedPresets,
};
Loading