Merge branch 'main' into codex/cortex-openclaw-integration
This commit is contained in:
commit
8629a13aba
@ -43,6 +43,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Security/agent: reject public spawned-run lineage fields and keep workspace inheritance on the internal spawned-session path so external `agent` callers can no longer override the gateway workspace boundary. (`GHSA-2rqg-gjgv-84jm`)(#43801) Thanks @tdjackey and @vincentkoc.
|
||||
- Security/session_status: enforce sandbox session-tree visibility and shared agent-to-agent access guards before reading or mutating target session state, so sandboxed subagents can no longer inspect parent session metadata or write parent model overrides via `session_status`. (`GHSA-wcxr-59v9-rxr8`)(#43754) Thanks @tdjackey and @vincentkoc.
|
||||
- Security/agent tools: mark `nodes` as explicitly owner-only and document/test that `canvas` remains a shared trusted-operator surface unless a real boundary bypass exists.
|
||||
- Security/exec approvals: fail closed for Ruby approval flows that use `-r`, `--require`, or `-I` so approval-backed commands no longer bind only the main script while extra local code-loading flags remain outside the reviewed file snapshot.
|
||||
- Security/device pairing: cap issued and verified device-token scopes to each paired device's approved scope baseline so stale or overbroad tokens cannot exceed approved access. (`GHSA-2pwv-x786-56f8`)(#43686) Thanks @tdjackey and @vincentkoc.
|
||||
- Models/secrets: enforce source-managed SecretRef markers in generated `models.json` so runtime-resolved provider secrets are not persisted when runtime projection is skipped. (#43759) Thanks @joshavant.
|
||||
- Security/WebSocket preauth: shorten unauthenticated handshake retention and reject oversized pre-auth frames before application-layer parsing to reduce pre-pairing exposure on unsupported public deployments. (`GHSA-jv4g-m82p-2j93`)(#44089) (`GHSA-xwx2-ppv2-wx98`)(#44089) Thanks @ez-lbz and @vincentkoc.
|
||||
|
||||
@ -1384,6 +1384,7 @@ validate_changelog_merge_hygiene() {
|
||||
|
||||
prepare_gates() {
|
||||
local pr="$1"
|
||||
local skip_test="${2:-false}"
|
||||
enter_worktree "$pr" false
|
||||
|
||||
checkout_prep_branch "$pr"
|
||||
@ -1418,7 +1419,9 @@ prepare_gates() {
|
||||
run_quiet_logged "pnpm build" ".local/gates-build.log" pnpm build
|
||||
run_quiet_logged "pnpm check" ".local/gates-check.log" pnpm check
|
||||
|
||||
if [ "$docs_only" = "true" ]; then
|
||||
if [ "$skip_test" = "true" ]; then
|
||||
echo "Test skipped (--no-test). Full suite deferred to Test phase."
|
||||
elif [ "$docs_only" = "true" ]; then
|
||||
echo "Docs-only change detected with high confidence; skipping pnpm test."
|
||||
else
|
||||
run_quiet_logged "pnpm test" ".local/gates-test.log" pnpm test
|
||||
@ -1987,7 +1990,7 @@ main() {
|
||||
prepare_validate_commit "$pr"
|
||||
;;
|
||||
prepare-gates)
|
||||
prepare_gates "$pr"
|
||||
prepare_gates "$pr" "${3:-false}"
|
||||
;;
|
||||
prepare-push)
|
||||
prepare_push "$pr"
|
||||
|
||||
@ -1,13 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "Usage: scripts/pr-prepare <init|validate-commit|gates|push|run> <PR>"
|
||||
if [ "$#" -lt 2 ]; then
|
||||
echo "Usage: scripts/pr-prepare <init|validate-commit|gates|push|run> <PR> [--no-test]"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
mode="$1"
|
||||
pr="$2"
|
||||
shift 2
|
||||
no_test=false
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--no-test) no_test=true ;;
|
||||
esac
|
||||
done
|
||||
script_dir="$(cd "$(dirname "$0")" && pwd)"
|
||||
base="$script_dir/pr"
|
||||
if common_git_dir=$(git -C "$script_dir" rev-parse --path-format=absolute --git-common-dir 2>/dev/null); then
|
||||
@ -25,7 +32,11 @@ case "$mode" in
|
||||
exec "$base" prepare-validate-commit "$pr"
|
||||
;;
|
||||
gates)
|
||||
exec "$base" prepare-gates "$pr"
|
||||
if [ "$no_test" = "true" ]; then
|
||||
exec "$base" prepare-gates "$pr" true
|
||||
else
|
||||
exec "$base" prepare-gates "$pr"
|
||||
fi
|
||||
;;
|
||||
push)
|
||||
exec "$base" prepare-push "$pr"
|
||||
|
||||
@ -548,6 +548,52 @@ describe("hardenApprovedExecutionPaths", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects ruby require preloads that approval cannot bind completely", () => {
|
||||
withFakeRuntimeBin({
|
||||
binName: "ruby",
|
||||
run: () => {
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-ruby-require-"));
|
||||
try {
|
||||
fs.writeFileSync(path.join(tmp, "safe.rb"), 'puts "SAFE"\n');
|
||||
const prepared = buildSystemRunApprovalPlan({
|
||||
command: ["ruby", "-r", "attacker", "./safe.rb"],
|
||||
cwd: tmp,
|
||||
});
|
||||
expect(prepared).toEqual({
|
||||
ok: false,
|
||||
message:
|
||||
"SYSTEM_RUN_DENIED: approval cannot safely bind this interpreter/runtime command",
|
||||
});
|
||||
} finally {
|
||||
fs.rmSync(tmp, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects ruby load-path flags that can redirect module resolution after approval", () => {
|
||||
withFakeRuntimeBin({
|
||||
binName: "ruby",
|
||||
run: () => {
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-ruby-load-path-"));
|
||||
try {
|
||||
fs.writeFileSync(path.join(tmp, "safe.rb"), 'puts "SAFE"\n');
|
||||
const prepared = buildSystemRunApprovalPlan({
|
||||
command: ["ruby", "-I.", "./safe.rb"],
|
||||
cwd: tmp,
|
||||
});
|
||||
expect(prepared).toEqual({
|
||||
ok: false,
|
||||
message:
|
||||
"SYSTEM_RUN_DENIED: approval cannot safely bind this interpreter/runtime command",
|
||||
});
|
||||
} finally {
|
||||
fs.rmSync(tmp, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects shell payloads that hide mutable interpreter scripts", () => {
|
||||
withFakeRuntimeBin({
|
||||
binName: "node",
|
||||
|
||||
@ -134,6 +134,8 @@ const NODE_OPTIONS_WITH_FILE_VALUE = new Set([
|
||||
"--require",
|
||||
]);
|
||||
|
||||
const RUBY_UNSAFE_APPROVAL_FLAGS = new Set(["-I", "-r", "--require"]);
|
||||
|
||||
const POSIX_SHELL_OPTIONS_WITH_VALUE = new Set([
|
||||
"--init-file",
|
||||
"--rcfile",
|
||||
@ -604,6 +606,33 @@ function resolveDenoRunScriptOperandIndex(params: {
|
||||
});
|
||||
}
|
||||
|
||||
function hasRubyUnsafeApprovalFlag(argv: string[]): boolean {
|
||||
let afterDoubleDash = false;
|
||||
for (let i = 1; i < argv.length; i += 1) {
|
||||
const token = argv[i]?.trim() ?? "";
|
||||
if (!token) {
|
||||
continue;
|
||||
}
|
||||
if (afterDoubleDash) {
|
||||
return false;
|
||||
}
|
||||
if (token === "--") {
|
||||
afterDoubleDash = true;
|
||||
continue;
|
||||
}
|
||||
if (token === "-I" || token === "-r") {
|
||||
return true;
|
||||
}
|
||||
if (token.startsWith("-I") || token.startsWith("-r")) {
|
||||
return true;
|
||||
}
|
||||
if (RUBY_UNSAFE_APPROVAL_FLAGS.has(token.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function isMutableScriptRunner(executable: string): boolean {
|
||||
return GENERIC_MUTABLE_SCRIPT_RUNNERS.has(executable) || isInterpreterLikeSafeBin(executable);
|
||||
}
|
||||
@ -642,6 +671,9 @@ function resolveMutableFileOperandIndex(argv: string[], cwd: string | undefined)
|
||||
return unwrapped.baseIndex + denoIndex;
|
||||
}
|
||||
}
|
||||
if (executable === "ruby" && hasRubyUnsafeApprovalFlag(unwrapped.argv)) {
|
||||
return null;
|
||||
}
|
||||
if (!isMutableScriptRunner(executable)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user