mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-15 09:56:06 +00:00
* fix(security): serialize exec approval mutations * fix(security): preserve additive approval writes * test(cli): expect normalized approval shape * fix(security): preserve exec approval compatibility * test(security): exercise locked approval initialization * test(security): mock serialized approval helpers * test(exec): derive enforced command path from plan * fix(gateway): always return approval CAS conflicts * fix(macos): serialize exec approvals writes * fix(security): repair approval build errors * fix(security): serialize exec approval mutations * fix(security): fail closed on approval persistence errors * test(security): cover detached approval persistence failures * fix(security): harden exec approval state * style(macos): format exec approval sources * fix(security): complete exec approval hardening Co-authored-by: Coy Geek <65363919+coygeek@users.noreply.github.com> * fix(macos): preserve approved login-shell semantics * fix(macos): keep login shell approvals one-shot * fix(security): linearize exec authorization Co-authored-by: Coy Geek <65363919+coygeek@users.noreply.github.com> * fix(security): preserve durable approval basis Co-authored-by: Coy Geek <65363919+coygeek@users.noreply.github.com> * fix(security): bind exec grants to current policy Co-authored-by: Coy Geek <65363919+coygeek@users.noreply.github.com> * test(security): fix exec revocation fixtures * test(security): align gateway approval fixtures * fix(macos): return approval decisions * chore(i18n): sync native approval strings * test(security): align approval hardening fixtures * test(node): authorize completed event fixture * test(security): fix approval decision fixtures * test(security): await durable approval visibility * fix(exec): preserve concurrent approval grants * fix(exec): address exact-head CI failures * fix(exec): preserve concurrent approval promotions * fix(exec): make Swift shutdown state explicit * test(macos): handle approval read failures * fix(macos): harden approval socket paths * fix(macos): preserve exact shell payload bytes * test(macos): make approval fixtures explicit * test(macos): fix approval suite compilation * fix(macos): bound approval socket JSONL reads * chore: move exec approval note to release process * chore: move exec approval note to release process --------- Co-authored-by: Coy Geek <65363919+coygeek@users.noreply.github.com>
168 lines
5.6 KiB
Swift
168 lines
5.6 KiB
Swift
import Foundation
|
|
|
|
enum ExecCommandToken {
|
|
static func basenameLower(_ token: String) -> String {
|
|
let trimmed = token.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !trimmed.isEmpty else { return "" }
|
|
let normalized = trimmed.replacingOccurrences(of: "\\", with: "/")
|
|
return normalized.split(separator: "/").last.map { String($0).lowercased() } ?? normalized.lowercased()
|
|
}
|
|
}
|
|
|
|
enum ExecEnvInvocationUnwrapper {
|
|
static let maxWrapperDepth = 4
|
|
|
|
struct UnwrapResult {
|
|
let command: [String]
|
|
let usesModifiers: Bool
|
|
}
|
|
|
|
private static func isEnvAssignment(_ token: String) -> Bool {
|
|
let pattern = #"^[A-Za-z_][A-Za-z0-9_]*=.*"#
|
|
return token.range(of: pattern, options: .regularExpression) != nil
|
|
}
|
|
|
|
static func unwrap(_ command: [String]) -> [String]? {
|
|
self.unwrapWithMetadata(command)?.command
|
|
}
|
|
|
|
static func unwrapWithMetadata(_ command: [String]) -> UnwrapResult? {
|
|
var idx = 1
|
|
var expectsOptionValue = false
|
|
var usesModifiers = false
|
|
while idx < command.count {
|
|
let token = command[idx].trimmingCharacters(in: .whitespacesAndNewlines)
|
|
if token.isEmpty {
|
|
return nil
|
|
}
|
|
if expectsOptionValue {
|
|
expectsOptionValue = false
|
|
usesModifiers = true
|
|
idx += 1
|
|
continue
|
|
}
|
|
if token == "--" {
|
|
idx += 1
|
|
break
|
|
}
|
|
if token == "-" {
|
|
usesModifiers = true
|
|
idx += 1
|
|
break
|
|
}
|
|
if self.isEnvAssignment(token) {
|
|
usesModifiers = true
|
|
idx += 1
|
|
continue
|
|
}
|
|
if token.hasPrefix("-"), token != "-" {
|
|
let lower = token.lowercased()
|
|
let flag = lower.split(separator: "=", maxSplits: 1).first.map(String.init) ?? lower
|
|
if ExecEnvOptions.flagOnly.contains(flag) {
|
|
usesModifiers = true
|
|
idx += 1
|
|
continue
|
|
}
|
|
if ExecEnvOptions.withValue.contains(flag) {
|
|
usesModifiers = true
|
|
if !lower.contains("=") {
|
|
expectsOptionValue = true
|
|
}
|
|
idx += 1
|
|
continue
|
|
}
|
|
if lower.hasPrefix("-u") ||
|
|
lower.hasPrefix("-c") ||
|
|
lower.hasPrefix("-s") ||
|
|
lower.hasPrefix("--unset=") ||
|
|
lower.hasPrefix("--chdir=") ||
|
|
lower.hasPrefix("--split-string=") ||
|
|
lower.hasPrefix("--default-signal=") ||
|
|
lower.hasPrefix("--ignore-signal=") ||
|
|
lower.hasPrefix("--block-signal=")
|
|
{
|
|
usesModifiers = true
|
|
idx += 1
|
|
continue
|
|
}
|
|
return nil
|
|
}
|
|
break
|
|
}
|
|
guard !expectsOptionValue,
|
|
idx < command.count,
|
|
!command[idx].trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
else { return nil }
|
|
return UnwrapResult(command: Array(command[idx...]), usesModifiers: usesModifiers)
|
|
}
|
|
|
|
static func unwrapDispatchWrappersForResolution(_ command: [String]) -> [String] {
|
|
var current = command
|
|
var depth = 0
|
|
while depth < self.maxWrapperDepth {
|
|
guard let token = current.first?.trimmingCharacters(in: .whitespacesAndNewlines), !token.isEmpty else {
|
|
break
|
|
}
|
|
guard ExecCommandToken.basenameLower(token) == "env" else {
|
|
break
|
|
}
|
|
guard let unwrapped = unwrapWithMetadata(current), !unwrapped.command.isEmpty else {
|
|
break
|
|
}
|
|
if unwrapped.usesModifiers {
|
|
break
|
|
}
|
|
current = unwrapped.command
|
|
depth += 1
|
|
}
|
|
return current
|
|
}
|
|
|
|
private static func unwrapTransparentEnvInvocation(_ command: [String]) -> [String]? {
|
|
var idx = 1
|
|
while idx < command.count {
|
|
let token = command[idx].trimmingCharacters(in: .whitespacesAndNewlines)
|
|
if token.isEmpty {
|
|
return nil
|
|
}
|
|
if token == "--" {
|
|
idx += 1
|
|
break
|
|
}
|
|
if token == "-" {
|
|
return nil
|
|
}
|
|
if self.isEnvAssignment(token) {
|
|
return nil
|
|
}
|
|
if token.hasPrefix("-"), token != "-" {
|
|
return nil
|
|
}
|
|
break
|
|
}
|
|
guard idx < command.count,
|
|
!command[idx].trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
|
|
else { return nil }
|
|
return Array(command[idx...])
|
|
}
|
|
|
|
static func unwrapTransparentDispatchWrappersForResolution(_ command: [String]) -> [String] {
|
|
var current = command
|
|
var depth = 0
|
|
while depth < self.maxWrapperDepth {
|
|
guard let token = current.first?.trimmingCharacters(in: .whitespacesAndNewlines), !token.isEmpty else {
|
|
break
|
|
}
|
|
guard ExecCommandToken.basenameLower(token) == "env" else {
|
|
break
|
|
}
|
|
guard let unwrapped = unwrapTransparentEnvInvocation(current), !unwrapped.isEmpty else {
|
|
break
|
|
}
|
|
current = unwrapped
|
|
depth += 1
|
|
}
|
|
return current
|
|
}
|
|
}
|