Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
16 changes: 16 additions & 0 deletions src/lib/shields/permissive-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ const TEMP_FILE_PREFIX = "nemoclaw-permissive-runtime";
* the bug class this helper exists for is path removal on a live sandbox,
* not policy shape changes.
*
* The live `landlock` stanza is carried through as well when the live policy
* has one (#8461). OpenShell applies Landlock at startup and rejects any
* later policy that changes it, so the emitted document must restate the
* value the sandbox is already running rather than the static base's.
*
* Background (#3942, #3957, #3168): OpenShell refuses to remove a
* `filesystem_policy.read_only` or `filesystem_policy.read_write` entry
* on a live sandbox. The static `openclaw-sandbox-permissive.yaml`
Expand Down Expand Up @@ -144,6 +149,17 @@ export function buildRuntimePermissivePolicy(
fsPolicy.read_write = [...baseRw];
fsPolicy.read_only = [...baseRo];

// OpenShell applies Landlock at sandbox startup and rejects a policy whose
// stanza differs from the one the sandbox started with. An agent that ships
// no permissive policy of its own falls back to the OpenClaw document. Its
// `best_effort` then contradicts a baseline such as Deep Agents Code's
// `strict`, and OpenShell refuses the policy (#8461). Carry the live stanza
// through so the emitted document proposes no Landlock change. This can only
// ever restate what the sandbox is already running.
if (live?.landlock !== undefined) {
base.landlock = live.landlock;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const yaml = composeManagedMcpPolicies(YAML.stringify(base), managedMcpPolicies);
if (deps.writeTempPolicy) {
try {
Expand Down
49 changes: 49 additions & 0 deletions test/permissive-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,55 @@ describe("buildRuntimePermissivePolicy (#3942)", () => {
expect(out).toBe(basePath);
});

it("carries the live landlock stanza so a startup-sealed field is not changed (#8461)", () => {
// Deep Agents Code starts with `strict` but ships no permissive policy of
// its own, so the base is the OpenClaw document with `best_effort`.
const liveYaml = YAML.stringify({
filesystem_policy: { read_only: ["/etc"], read_write: ["/tmp"] },
landlock: { compatibility: "strict" },
});

const out = buildRuntimePermissivePolicy("/unused-base.yaml", {
livePolicyYaml: liveYaml,
readBasePolicy: () => BASE_PERMISSIVE,
});
trackTempForCleanup(out, "/unused-base.yaml");

const result = YAML.parse(fs.readFileSync(out, "utf-8"));
expect(result.landlock).toEqual({ compatibility: "strict" });
});

it("carries a live landlock stanza that already equals the base (#8461)", () => {
const liveYaml = YAML.stringify({
filesystem_policy: { read_only: ["/etc"], read_write: ["/tmp"] },
landlock: { compatibility: "best_effort" },
});

const out = buildRuntimePermissivePolicy("/unused-base.yaml", {
livePolicyYaml: liveYaml,
readBasePolicy: () => BASE_PERMISSIVE,
});
trackTempForCleanup(out, "/unused-base.yaml");

const result = YAML.parse(fs.readFileSync(out, "utf-8"));
expect(result.landlock).toEqual({ compatibility: "best_effort" });
});

it("keeps the base landlock stanza when the live policy carries none (#8461)", () => {
const liveYaml = YAML.stringify({
filesystem_policy: { read_only: ["/etc"], read_write: ["/tmp"] },
});

const out = buildRuntimePermissivePolicy("/unused-base.yaml", {
livePolicyYaml: liveYaml,
readBasePolicy: () => BASE_PERMISSIVE,
});
trackTempForCleanup(out, "/unused-base.yaml");

const result = YAML.parse(fs.readFileSync(out, "utf-8"));
expect(result.landlock).toEqual({ compatibility: "best_effort" });
});

it("returns the static base path when live policy has no filesystem_policy section", () => {
const basePath = "/path/to/static.yaml";
const liveYaml = YAML.stringify({ landlock: { compatibility: "best_effort" } });
Expand Down
Loading