Skip to content

Commit 50f4440

Browse files
authored
Enforce inline shell wrapper payload matching [AI] (#80978)
* fix: enforce inline shell wrapper payload matching * addressing review-skill * addressing codex review * addressing codex review * addressing claude review * addressing claude review * fix: complete shell wrapper allowlist handling * addressing codex review * addressing codex review * addressing codex review * addressing codex review * addressing codex review * addressing ci * docs: add changelog entry for PR merge
1 parent b1131cd commit 50f4440

10 files changed

Lines changed: 744 additions & 104 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai
66

77
### Fixes
88

9+
- Enforce inline shell wrapper payload matching [AI]. (#80978) Thanks @pgondhi987.
910
- fix(node-pairing): replace changed pending requests [AI]. (#80894) Thanks @pgondhi987.
1011
- Rate limit Google Chat webhook requests [AI]. (#80974) Thanks @pgondhi987.
1112
- Docker: mount the auth-profile secret key directory so OAuth-backed auth profiles survive container rebuilds. (#80991)

src/infra/exec-approvals-allow-always.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
makeTempDir,
1010
} from "./exec-approvals-test-helpers.js";
1111
import {
12+
analyzeArgvCommand,
13+
evaluateExecAllowlist,
1214
evaluateShellAllowlist,
1315
requiresExecApproval,
1416
resolveAllowAlwaysPatterns,
@@ -293,6 +295,69 @@ describe("resolveAllowAlwaysPatterns", () => {
293295
).toBeNull();
294296
});
295297

298+
it.each([
299+
{
300+
name: "empty PowerShell file argument",
301+
argvPrefix: [],
302+
fileFlag: "-File",
303+
scriptArgs: [""],
304+
expectedArgPattern: "^\x00$",
305+
},
306+
{
307+
name: "PowerShell file alias argument",
308+
argvPrefix: [],
309+
fileFlag: "-fi",
310+
scriptArgs: ["arg"],
311+
expectedArgPattern: "^arg\x00$",
312+
},
313+
{
314+
name: "empty PowerShell file argument after dispatch unwrap",
315+
argvPrefix: ["env"],
316+
fileFlag: "/file",
317+
scriptArgs: [""],
318+
expectedArgPattern: "^\x00$",
319+
},
320+
])(
321+
"persists allow-always patterns for $name",
322+
({ argvPrefix, fileFlag, scriptArgs, expectedArgPattern }) => {
323+
const dir = makeTempDir();
324+
makeExecutable(dir, "env");
325+
makeExecutable(dir, "pwsh");
326+
const scriptPath = path.join(dir, "script.ps1");
327+
fs.writeFileSync(scriptPath, "");
328+
fs.chmodSync(scriptPath, 0o755);
329+
try {
330+
const env = makePathEnv(dir);
331+
const analysis = analyzeArgvCommand({
332+
argv: [...argvPrefix, "pwsh", fileFlag, scriptPath, ...scriptArgs],
333+
cwd: dir,
334+
env,
335+
});
336+
expect(analysis.ok).toBe(true);
337+
338+
const entries = resolveAllowAlwaysPatternEntries({
339+
segments: analysis.segments,
340+
cwd: dir,
341+
env,
342+
platform: "win32",
343+
});
344+
expect(entries).toEqual([{ pattern: scriptPath, argPattern: expectedArgPattern }]);
345+
346+
const result = evaluateExecAllowlist({
347+
analysis,
348+
allowlist: entries,
349+
safeBins: new Set(),
350+
cwd: dir,
351+
env,
352+
platform: "win32",
353+
});
354+
expect(result.allowlistSatisfied).toBe(true);
355+
} finally {
356+
fs.rmSync(dir, { recursive: true, force: true });
357+
}
358+
},
359+
);
360+
296361
it("keeps inline awk programs out of allow-always persistence in strict inline-eval mode", () => {
297362
if (process.platform === "win32") {
298363
return;

0 commit comments

Comments
 (0)