mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-03 02:21:33 +00:00
Reject recognizable POSIX and Windows shell source before QuickJS execution while preserving valid JavaScript, TypeScript, syntax errors, standard globals, and real hoisted bindings. Add real-worker regression coverage and adversarial cross-platform stress proof. Fixes #113069.
217 lines
6.9 KiB
TypeScript
217 lines
6.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
CODE_MODE_SHELL_SOURCE_ERROR,
|
|
isShellLikeCodeModeSource,
|
|
} from "./code-mode-shell-source.js";
|
|
|
|
describe("isShellLikeCodeModeSource", () => {
|
|
it.each([
|
|
"ls",
|
|
"ls -1",
|
|
"ls -la /workspace/",
|
|
"ls -la /workspace",
|
|
"ls /var/log",
|
|
"pwd",
|
|
"pwd;",
|
|
"pwd; // inspect the workspace",
|
|
"# inspect the workspace\npwd",
|
|
"# inspect the workspace\n# then report it\npwd",
|
|
"#!/bin/sh\npwd",
|
|
"#!/usr/bin/env bash\nset -euo pipefail\npwd",
|
|
"pwd\nls -la /workspace",
|
|
"pwd;ls -la /workspace",
|
|
"pwd && ls /workspace",
|
|
"pwd||ls /workspace",
|
|
"echo hello",
|
|
"cat /workspace/HEARTBEAT.md",
|
|
"find /workspace -maxdepth 1 -type f | head -50",
|
|
"echo listing; ls /workspace/ 2>&1 || echo failed",
|
|
"ls /workspace/ > /tmp/wlist.txt 2>&1; cat /tmp/wlist.txt",
|
|
"/bin/ls /workspace/",
|
|
"/usr/bin/find /workspace",
|
|
"/usr/local/bin/python3 script.py",
|
|
"sh -c 'ls /workspace/'",
|
|
"bash -lc 'pwd'",
|
|
"export FOO=bar",
|
|
"export PATH=/workspace/bin",
|
|
"env FOO=bar node script.js",
|
|
"NODE_ENV=test npm test",
|
|
"NODE_ENV=test\nnpm test",
|
|
"FOO=bar\r\n npm test",
|
|
"FOO=bar ./gradlew test",
|
|
"FOO=bar BAR=baz node --version",
|
|
'GREETING="hello world" npm test',
|
|
"GREETING='hello world' ./gradlew test",
|
|
"EMPTY= npm test",
|
|
"// inspect the workspace\npwd",
|
|
"/* inspect the workspace */ pwd;",
|
|
"// use a clean environment\nNODE_ENV=test npm test",
|
|
"git status",
|
|
'git status; const note = "git";',
|
|
'git status; const note = "function git";',
|
|
"ls -1; const metadata = { ls: true };",
|
|
"ls -1; const note = 'function ls';",
|
|
"ls -1; let ls = 7;",
|
|
"pwd; class/**/pwd {}",
|
|
"npm test",
|
|
"npx vitest run",
|
|
"pnpm test src/agents/code-mode.test.ts",
|
|
"rg code-mode src/agents",
|
|
"sudo ls /workspace",
|
|
"docker ps",
|
|
"kubectl get pods",
|
|
"make test",
|
|
"set -euo pipefail",
|
|
"exit",
|
|
"logout",
|
|
"if [ -d /workspace ]; then pwd; fi",
|
|
"if [[ -d /workspace ]]; then pwd; fi",
|
|
"while test -d /workspace; do pwd; done",
|
|
'for file in /workspace/*; do echo "$file"; done',
|
|
'for ((i=0; i<3; i++)); do echo "$i"; done',
|
|
"function task { pwd; }",
|
|
"source ./env",
|
|
"command ls",
|
|
"whoami",
|
|
"date",
|
|
"uname -a",
|
|
"go test ./...",
|
|
"cargo test",
|
|
"sort /workspace/file",
|
|
"wc -l file",
|
|
"jq . file.json",
|
|
"exec ls",
|
|
"cut -d : file",
|
|
"uniq -c file",
|
|
"custom-tool --format=json",
|
|
"some_cli /workspace/file",
|
|
"ls > output",
|
|
"ls>output",
|
|
"ls >output",
|
|
"ls >> output",
|
|
"ls>>output",
|
|
"ls 2> error",
|
|
"ls < input",
|
|
"cat<input",
|
|
"cat <input",
|
|
"python3 script.py",
|
|
"node script.js",
|
|
"powershell -Command Get-Location",
|
|
"pwsh -Command Get-Location",
|
|
"./script.sh --verbose",
|
|
"./gradlew test",
|
|
".\\gradlew.bat test",
|
|
".\\script.ps1",
|
|
"C:\\workspace\\run.cmd /q",
|
|
"C:\\workspace\\script.ps1 -Verbose",
|
|
"./configure --prefix=/workspace",
|
|
"../bin/task --verbose",
|
|
"../scripts/run.bash",
|
|
"/workspace/run.sh --verbose",
|
|
"/opt/homebrew/bin/tool --version",
|
|
"~/bin/task",
|
|
"~/scripts/run.zsh",
|
|
" ls -la /workspace/ ",
|
|
])("rejects the shell command %j", (source) => {
|
|
expect(isShellLikeCodeModeSource(source)).toBe(true);
|
|
});
|
|
|
|
it.each([
|
|
"",
|
|
" ",
|
|
"true",
|
|
"true;",
|
|
"false",
|
|
"false;",
|
|
"null;",
|
|
"42;",
|
|
'"ls /workspace";',
|
|
"return 7;",
|
|
"return -1;",
|
|
"return /foo/.test('foo');",
|
|
"return true || false;",
|
|
"Infinity -1; return 42;",
|
|
"eval; return typeof eval;",
|
|
"Date -1; return 7;",
|
|
"if (true) { return -1; }",
|
|
"while (false) { return 7; }",
|
|
"for (let i = 0; i < 3; i++) { if (i === 2) { return i; } }",
|
|
"function task() { return 7; } return task();",
|
|
"const value = ;",
|
|
"let value = ;",
|
|
"value || fallback",
|
|
"value && fallback",
|
|
"value | mask",
|
|
"value > limit",
|
|
"/workspace/.test(value)",
|
|
"/foo/.test('foo')",
|
|
"ls('workspace')",
|
|
"ls .call(null)",
|
|
"ls ?.('workspace')",
|
|
"ls = 7",
|
|
"ls + count",
|
|
"ls - count",
|
|
"ls -1; function ls() {}",
|
|
"ls -1; function/**/ls() {}",
|
|
"ls -la /workspace; function ls() {}",
|
|
"echo('hello')",
|
|
"echo `hello`; function echo(parts) { return parts[0]; }",
|
|
"pwd?.()",
|
|
"pwd; function pwd() {}",
|
|
"pwd\nfunction pwd() {}",
|
|
"pwd; var { pwd } = { pwd: 7 }; return pwd;",
|
|
"pwd; var [pwd] = [7]; return pwd;",
|
|
"pwd; var { nested: { pwd } } = { nested: { pwd: 7 } }; return pwd;",
|
|
"pwd; for (var pwd of [7]) {} return pwd;",
|
|
"pwd; var other = 1, pwd = 7; return pwd;",
|
|
"pwd; function* pwd() { yield 7; }",
|
|
"pwd; function/**/pwd() {}",
|
|
"pwd; function/**/\u002a/**/pwd() { yield 7; }",
|
|
"pwd; var/**/{ pwd } = { pwd: 7 }; return pwd;",
|
|
"node -version; function/**/node() {}; var version = 1;",
|
|
"ls > limit; function ls() {} var limit = 1;",
|
|
"test instanceof Function; function test() {}",
|
|
"git instanceof Function; function git() {}",
|
|
"export const answer = 7;",
|
|
"export abstract class Example {}",
|
|
"export import Alias = require('./module');",
|
|
"export declare namespace Example {}",
|
|
"export as namespace Example;",
|
|
"export = Example;",
|
|
"export async function read() {}",
|
|
"export { answer };",
|
|
"export * from './types';",
|
|
"export interface Result { value: number }",
|
|
"export enum Color { Red, Green }",
|
|
'const result = await tools.callValue("openclaw:core:exec", { command: "ls" }); return result;',
|
|
'return await tools.callValue("openclaw:core:read", { path: "/workspace" });',
|
|
"console.log(await tools.callValue('openclaw:core:read', { path: '/workspace' }));",
|
|
"// shell documentation: ls /workspace\nreturn 7;",
|
|
"// shell documentation: ls /workspace\nconst answer = 7; return answer;",
|
|
"/* typed module */ export interface Result { value: number }",
|
|
])("preserves the JavaScript or TypeScript source %j", (source) => {
|
|
expect(isShellLikeCodeModeSource(source)).toBe(false);
|
|
});
|
|
|
|
it.each([
|
|
{
|
|
source: "node -1; var node: number = 7;",
|
|
preparedSource: "node -1; var node = 7;",
|
|
},
|
|
{
|
|
source: "node --version; var node: number = 2, version = 1;",
|
|
preparedSource: "node--; version; var node = 2, version = 1;",
|
|
},
|
|
])("preserves transpiled command-like TypeScript: $source", ({ source, preparedSource }) => {
|
|
expect(isShellLikeCodeModeSource(source, preparedSource)).toBe(false);
|
|
});
|
|
|
|
it("explains how to execute a real catalog tool without retrying shell source", () => {
|
|
expect(CODE_MODE_SHELL_SOURCE_ERROR).toContain("JavaScript or TypeScript");
|
|
expect(CODE_MODE_SHELL_SOURCE_ERROR).toContain("not shell");
|
|
expect(CODE_MODE_SHELL_SOURCE_ERROR).toContain("tools.callValue");
|
|
expect(CODE_MODE_SHELL_SOURCE_ERROR).toContain("ALL_TOOLS");
|
|
expect(CODE_MODE_SHELL_SOURCE_ERROR).toContain("Do not retry");
|
|
});
|
|
});
|