mirror of https://github.com/openclaw/openclaw.git
fix(exec): close dispatch-wrapper boundary drift
This commit is contained in:
parent
adf4eb487b
commit
2fc95a7cfc
|
|
@ -305,6 +305,7 @@ Docs: https://docs.openclaw.ai
|
|||
- Media/mime unknown-kind handling: return `undefined` (not `"unknown"`) for missing/unrecognized MIME kinds and use document-size fallback caps for unknown remote media, preventing phantom `<media:unknown>` Signal events from being treated as real messages. (#39199) Thanks @nicolasgrasset.
|
||||
- Nodes/system.run allow-always persistence: honor shell comment semantics during allowlist analysis so `#`-tailed payloads that never execute are not persisted as trusted follow-up commands. Thanks @tdjackey for reporting.
|
||||
- Signal/inbound attachment fan-in: forward all successfully fetched inbound attachments through `MediaPaths`/`MediaUrls`/`MediaTypes` (instead of only the first), and improve multi-attachment placeholder summaries in mention-gated pending history. (#39212) Thanks @joeykrug.
|
||||
- Nodes/system.run dispatch-wrapper boundary: keep shell-wrapper approval classification active at the depth boundary so `env` wrapper stacks cannot reach `/bin/sh -c` execution without the expected approval gate. Thanks @tdjackey for reporting.
|
||||
|
||||
## 2026.3.2
|
||||
|
||||
|
|
|
|||
|
|
@ -509,7 +509,9 @@ function hasEnvManipulationBeforeShellWrapperInternal(
|
|||
depth: number,
|
||||
envManipulationSeen: boolean,
|
||||
): boolean {
|
||||
if (depth >= MAX_DISPATCH_WRAPPER_DEPTH) {
|
||||
// The wrapper found exactly at the configured dispatch depth boundary still needs
|
||||
// to participate in approval classification; only paths beyond that boundary fail closed.
|
||||
if (depth > MAX_DISPATCH_WRAPPER_DEPTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -607,7 +609,9 @@ function extractShellWrapperCommandInternal(
|
|||
rawCommand: string | null,
|
||||
depth: number,
|
||||
): ShellWrapperCommand {
|
||||
if (depth >= MAX_DISPATCH_WRAPPER_DEPTH) {
|
||||
// The shell wrapper reached at the boundary depth is still semantically relevant.
|
||||
// Only deeper wrapper stacks should be dropped as overflow.
|
||||
if (depth > MAX_DISPATCH_WRAPPER_DEPTH) {
|
||||
return { isWrapper: false, command: null };
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,17 @@ describe("system run command helpers", () => {
|
|||
"echo hi",
|
||||
]),
|
||||
).toBe("echo hi");
|
||||
expect(
|
||||
extractShellCommandFromArgv([
|
||||
"/usr/bin/env",
|
||||
"/usr/bin/env",
|
||||
"/usr/bin/env",
|
||||
"/usr/bin/env",
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
"echo hi",
|
||||
]),
|
||||
).toBe("echo hi");
|
||||
});
|
||||
|
||||
test("extractShellCommandFromArgv supports fish and pwsh wrappers", () => {
|
||||
|
|
|
|||
|
|
@ -858,6 +858,46 @@ describe("handleSystemRunInvoke mac app exec host routing", () => {
|
|||
expectApprovalRequiredDenied({ sendNodeEvent, sendInvokeResult });
|
||||
});
|
||||
|
||||
it("denies env-wrapped shell payloads at the dispatch depth boundary", async () => {
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
}
|
||||
const { runCommand, sendInvokeResult, sendNodeEvent } = createInvokeSpies({
|
||||
runCommand: vi.fn(async () => {
|
||||
throw new Error("runCommand should not be called for depth-boundary shell wrappers");
|
||||
}),
|
||||
});
|
||||
|
||||
await withTempApprovalsHome({
|
||||
approvals: createAllowlistOnMissApprovals({
|
||||
agents: {
|
||||
main: {
|
||||
allowlist: [{ pattern: "/usr/bin/env" }],
|
||||
},
|
||||
},
|
||||
}),
|
||||
run: async ({ tempHome }) => {
|
||||
const marker = path.join(tempHome, "depth4-pwned.txt");
|
||||
await runSystemInvoke({
|
||||
preferMacAppExecHost: false,
|
||||
command: buildNestedEnvShellCommand({
|
||||
depth: 4,
|
||||
payload: `echo PWNED > ${marker}`,
|
||||
}),
|
||||
security: "allowlist",
|
||||
ask: "on-miss",
|
||||
runCommand,
|
||||
sendInvokeResult,
|
||||
sendNodeEvent,
|
||||
});
|
||||
expect(fs.existsSync(marker)).toBe(false);
|
||||
},
|
||||
});
|
||||
|
||||
expect(runCommand).not.toHaveBeenCalled();
|
||||
expectApprovalRequiredDenied({ sendNodeEvent, sendInvokeResult });
|
||||
});
|
||||
|
||||
it("denies nested env shell payloads when wrapper depth is exceeded", async () => {
|
||||
if (process.platform === "win32") {
|
||||
return;
|
||||
|
|
|
|||
Loading…
Reference in New Issue